Skip to content

Commit 21ab866

Browse files
Add actual liquidity delta to Allocated event (#146)
* Add actual liquidity delta to Allocated event * Fix tests for allocate event * Comment out total assets cap assertion in EtherFiARM smoke test * Comment tests * Updated allocate Natspec --------- Co-authored-by: Clément <[email protected]>
1 parent d3ae468 commit 21ab866

File tree

3 files changed

+57
-39
lines changed

3 files changed

+57
-39
lines changed

src/contracts/AbstractARM.sol

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable {
147147
event MarketAdded(address indexed market);
148148
event MarketRemoved(address indexed market);
149149
event ARMBufferUpdated(uint256 armBuffer);
150-
event Allocated(address indexed market, int256 assets);
150+
event Allocated(address indexed market, int256 targetLiquidityDelta, int256 actualLiquidityDelta);
151151

152152
constructor(
153153
address _token0,
@@ -905,59 +905,73 @@ abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable {
905905

906906
/// @notice Deposit or withdraw liquidity assets to/from the active lending market
907907
/// to match the ARM's liquidity buffer which is a percentage of the available assets.
908+
/// The buffer excludes liquidity assets reserved for the ARM's withdrawal queue. That is, more
909+
/// liquidity assets will be withdrawn from the lending market if the ARM's liquidity asset balance
910+
/// does not cover the buffer, which can be zero, and the ARM's outstanding withdrawals.
908911
/// Will revert if there is no active lending market set.
909-
/// @return liquidityDelta The actual liquidity less target liquidity before
910-
/// the deposit/withdrawal to/from the active lending market.
911-
function allocate() external returns (int256 liquidityDelta) {
912+
/// @return targetLiquidityDelta the desired amount that is deposited/withdrawn to/from the lending market.
913+
/// A positive value is the liquidity assets that should be deposited to the lending market.
914+
/// A negative value is the desired liquidity assets that should be withdrawn from the lending market.
915+
/// @return actualLiquidityDelta the actual amount that is deposited/withdrawn to/from the lending market.
916+
/// A positive value is the liquidity assets that were deposited to the lending market.
917+
/// A negative value is the liquidity assets that were withdrawn from the lending market. This can be less than
918+
/// the `targetLiquidityDelta`, or even zero, if there is high utilization in the lending market.
919+
function allocate() external returns (int256 targetLiquidityDelta, int256 actualLiquidityDelta) {
912920
require(activeMarket != address(0), "ARM: no active market");
913921

914-
liquidityDelta = _allocate();
922+
return _allocate();
915923
}
916924

917-
function _allocate() internal returns (int256 liquidityDelta) {
925+
function _allocate() internal returns (int256 targetLiquidityDelta, int256 actualLiquidityDelta) {
918926
(uint256 availableAssets, uint256 outstandingWithdrawals) = _availableAssets();
919-
if (availableAssets == 0) return 0;
927+
if (availableAssets == 0) return (0, 0);
928+
uint256 targetArmLiquidity = availableAssets * armBuffer / 1e18;
920929

921-
int256 armLiquidity = SafeCast.toInt256(IERC20(liquidityAsset).balanceOf(address(this)))
930+
// The current liquidity available in swap is the liquidity asset balance less
931+
// any outstanding withdrawals from the ARM's withdrawal queue
932+
int256 currentArmLiquidity = SafeCast.toInt256(IERC20(liquidityAsset).balanceOf(address(this)))
922933
- SafeCast.toInt256(outstandingWithdrawals);
923-
uint256 targetArmLiquidity = availableAssets * armBuffer / 1e18;
924934

925-
liquidityDelta = armLiquidity - SafeCast.toInt256(targetArmLiquidity);
935+
targetLiquidityDelta = currentArmLiquidity - SafeCast.toInt256(targetArmLiquidity);
926936

927937
// Load the active lending market address from storage to save gas
928938
address activeMarketMem = activeMarket;
929939

930940
// The allocateThreshold prevents the ARM from constantly depositing and withdrawing if there are rounding issues
931-
if (liquidityDelta > allocateThreshold) {
941+
if (targetLiquidityDelta > allocateThreshold) {
932942
// We have too much liquidity in the ARM, we need to deposit some to the active lending market
933943

934-
uint256 depositAmount = SafeCast.toUint256(liquidityDelta);
944+
uint256 depositAmount = SafeCast.toUint256(targetLiquidityDelta);
935945

936946
IERC20(liquidityAsset).approve(activeMarketMem, depositAmount);
937947
IERC4626(activeMarketMem).deposit(depositAmount, address(this));
938-
} else if (liquidityDelta < 0) {
948+
949+
actualLiquidityDelta = SafeCast.toInt256(depositAmount);
950+
} else if (targetLiquidityDelta < 0) {
939951
// We have too little liquidity in the ARM, we need to withdraw some from the active lending market
940952

941953
uint256 availableMarketAssets = IERC4626(activeMarketMem).maxWithdraw(address(this));
942-
uint256 desiredWithdrawAmount = SafeCast.toUint256(-liquidityDelta);
954+
uint256 desiredWithdrawAmount = SafeCast.toUint256(-targetLiquidityDelta);
943955

944956
if (availableMarketAssets < desiredWithdrawAmount) {
945957
// Not enough assets in the market so redeem as much as possible.
946958
// maxRedeem is used instead of balanceOf as we want to redeem as much as possible without failing.
947959
// redeem of the ARM's balance can fail if the lending market is highly utilized or temporarily paused.
948960
// Redeem and not withdrawal is used to avoid leaving a small amount of assets in the market.
949961
uint256 shares = IERC4626(activeMarketMem).maxRedeem(address(this));
950-
if (shares <= minSharesToRedeem) return liquidityDelta;
962+
if (shares <= minSharesToRedeem) return (targetLiquidityDelta, 0);
951963
// This should not fail according to the ERC-4626 spec as maxRedeem was used earlier
952964
// but it depends on the 4626 implementation of the lending market.
953965
// It may fail if the market is highly utilized and not compliant with 4626.
954-
IERC4626(activeMarketMem).redeem(shares, address(this), address(this));
966+
uint256 redeemedAssets = IERC4626(activeMarketMem).redeem(shares, address(this), address(this));
967+
actualLiquidityDelta = -SafeCast.toInt256(redeemedAssets);
955968
} else {
956969
IERC4626(activeMarketMem).withdraw(desiredWithdrawAmount, address(this), address(this));
970+
actualLiquidityDelta = -SafeCast.toInt256(desiredWithdrawAmount);
957971
}
958972
}
959973

960-
emit Allocated(activeMarketMem, liquidityDelta);
974+
emit Allocated(activeMarketMem, targetLiquidityDelta, actualLiquidityDelta);
961975
}
962976

963977
////////////////////////////////////////////////////

test/fork/OriginARM/AllocateWithAdapter.sol

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,15 @@ contract Fork_Concrete_OriginARM_AllocateWithAdapter_Test_ is Fork_Shared_Test {
5151
assertEq(market.balanceOf(address(siloMarket)), 0, "shares before");
5252
assertApproxEqAbs(originARM.totalAssets(), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "totalAssets before");
5353
uint256 expectedShares = market.convertToShares(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY);
54+
int256 expectedLiquidityDelta = (DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY).toInt256();
5455

5556
// Expected event
5657
vm.expectEmit(address(market));
5758
emit IERC4626.Deposit(
5859
address(siloMarket), address(siloMarket), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, expectedShares
5960
);
6061
vm.expectEmit(address(originARM));
61-
emit AbstractARM.Allocated(address(siloMarket), (DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY).toInt256());
62+
emit AbstractARM.Allocated(address(siloMarket), expectedLiquidityDelta, expectedLiquidityDelta);
6263

6364
// Main call
6465
originARM.allocate();
@@ -83,16 +84,16 @@ contract Fork_Concrete_OriginARM_AllocateWithAdapter_Test_ is Fork_Shared_Test {
8384
assertApproxEqAbs(marketBalanceBefore, sharesBefore, 1, "shares before");
8485
assertApproxEqAbs(originARM.totalAssets(), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "totalAssets before");
8586

86-
int256 expectedAmount = getLiquidityDelta();
87-
uint256 expectedShares = market.previewWithdraw(abs(expectedAmount));
87+
int256 expectedLiquidityDelta = getLiquidityDelta();
88+
uint256 expectedShares = market.previewWithdraw(abs(expectedLiquidityDelta));
8889

8990
// Expected event
9091
vm.expectEmit(address(market));
9192
emit IERC4626.Withdraw(
92-
address(siloMarket), address(originARM), address(siloMarket), abs(expectedAmount), expectedShares
93+
address(siloMarket), address(originARM), address(siloMarket), abs(expectedLiquidityDelta), expectedShares
9394
);
9495
vm.expectEmit(address(originARM));
95-
emit AbstractARM.Allocated(address(siloMarket), expectedAmount);
96+
emit AbstractARM.Allocated(address(siloMarket), expectedLiquidityDelta, expectedLiquidityDelta);
9697

9798
// Main call
9899
originARM.allocate();
@@ -117,17 +118,17 @@ contract Fork_Concrete_OriginARM_AllocateWithAdapter_Test_ is Fork_Shared_Test {
117118
assertApproxEqAbs(marketBalanceBefore, sharesBefore, 1, "shares before");
118119
assertApproxEqAbs(originARM.totalAssets(), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "totalAssets before");
119120

120-
int256 expectedAmount = getLiquidityDelta();
121-
uint256 expectedShares = market.previewWithdraw(abs(expectedAmount));
122-
assertApproxEqAbs(abs(expectedAmount), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "expectedAmount");
121+
int256 expectedLiquidityDelta = getLiquidityDelta();
122+
uint256 expectedShares = market.previewWithdraw(abs(expectedLiquidityDelta));
123+
assertApproxEqAbs(abs(expectedLiquidityDelta), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "expectedLiquidityDelta");
123124

124125
// Expected event
125126
vm.expectEmit(address(market));
126127
emit IERC4626.Withdraw(
127-
address(siloMarket), address(originARM), address(siloMarket), abs(expectedAmount), expectedShares
128+
address(siloMarket), address(originARM), address(siloMarket), abs(expectedLiquidityDelta), expectedShares
128129
);
129130
vm.expectEmit(address(originARM));
130-
emit AbstractARM.Allocated(address(siloMarket), expectedAmount);
131+
emit AbstractARM.Allocated(address(siloMarket), expectedLiquidityDelta, expectedLiquidityDelta);
131132

132133
// Main call
133134
originARM.allocate();
@@ -156,14 +157,15 @@ contract Fork_Concrete_OriginARM_AllocateWithAdapter_Test_ is Fork_Shared_Test {
156157

157158
uint256 expectedShares = siloMarket.maxRedeem(address(originARM));
158159
uint256 expectedAmount = market.convertToAssets(expectedShares);
160+
int256 expectedLiquidityDelta = getLiquidityDelta();
159161

160162
// Expected event
161163
vm.expectEmit(address(market));
162164
emit IERC4626.Withdraw(
163165
address(siloMarket), address(originARM), address(siloMarket), expectedAmount, expectedShares
164166
);
165167
vm.expectEmit(address(originARM));
166-
emit AbstractARM.Allocated(address(siloMarket), getLiquidityDelta());
168+
emit AbstractARM.Allocated(address(siloMarket), expectedLiquidityDelta, expectedLiquidityDelta + 1 ether + 1);
167169
// Main call
168170
originARM.allocate();
169171

test/fork/OriginARM/AllocateWithoutAdapter.sol

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ contract Fork_Concrete_OriginARM_AllocateWithoutAdapter_Test_ is Fork_Shared_Tes
4444
assertEq(market.balanceOf(address(originARM)), 0, "shares before");
4545
assertApproxEqAbs(originARM.totalAssets(), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "totalAssets before");
4646
uint256 expectedShares = market.convertToShares(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY);
47+
int256 expectedLiquidityDelta = (DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY).toInt256();
4748

4849
// Expected event
4950
vm.expectEmit(address(market));
5051
emit IERC4626.Deposit(address(originARM), address(originARM), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, expectedShares);
5152
vm.expectEmit(address(originARM));
52-
emit AbstractARM.Allocated(address(market), (DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY).toInt256());
53+
emit AbstractARM.Allocated(address(market), expectedLiquidityDelta, expectedLiquidityDelta);
5354

5455
// Main call
5556
originARM.allocate();
@@ -74,16 +75,16 @@ contract Fork_Concrete_OriginARM_AllocateWithoutAdapter_Test_ is Fork_Shared_Tes
7475
assertApproxEqAbs(marketBalanceBefore, sharesBefore, 1, "shares before");
7576
assertApproxEqAbs(originARM.totalAssets(), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "totalAssets before");
7677

77-
int256 expectedAmount = getLiquidityDelta();
78-
uint256 expectedShares = market.previewWithdraw(abs(expectedAmount));
78+
int256 expectedLiquidityDelta = getLiquidityDelta();
79+
uint256 expectedShares = market.previewWithdraw(abs(expectedLiquidityDelta));
7980

8081
// Expected event
8182
vm.expectEmit(address(market));
8283
emit IERC4626.Withdraw(
83-
address(originARM), address(originARM), address(originARM), abs(expectedAmount), expectedShares
84+
address(originARM), address(originARM), address(originARM), abs(expectedLiquidityDelta), expectedShares
8485
);
8586
vm.expectEmit(address(originARM));
86-
emit AbstractARM.Allocated(address(market), expectedAmount);
87+
emit AbstractARM.Allocated(address(market), expectedLiquidityDelta, expectedLiquidityDelta);
8788

8889
// Main call
8990
originARM.allocate();
@@ -108,17 +109,17 @@ contract Fork_Concrete_OriginARM_AllocateWithoutAdapter_Test_ is Fork_Shared_Tes
108109
assertApproxEqAbs(marketBalanceBefore, sharesBefore, 1, "shares before");
109110
assertApproxEqAbs(originARM.totalAssets(), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "totalAssets before");
110111

111-
int256 expectedAmount = getLiquidityDelta();
112-
uint256 expectedShares = market.previewWithdraw(abs(expectedAmount));
113-
assertApproxEqAbs(abs(expectedAmount), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "expectedAmount");
112+
int256 expectedLiquidityDelta = getLiquidityDelta();
113+
uint256 expectedShares = market.previewWithdraw(abs(expectedLiquidityDelta));
114+
assertApproxEqAbs(abs(expectedLiquidityDelta), DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY, 1, "expectedLiquidityDelta");
114115

115116
// Expected event
116117
vm.expectEmit(address(market));
117118
emit IERC4626.Withdraw(
118-
address(originARM), address(originARM), address(originARM), abs(expectedAmount), expectedShares
119+
address(originARM), address(originARM), address(originARM), abs(expectedLiquidityDelta), expectedShares
119120
);
120121
vm.expectEmit(address(originARM));
121-
emit AbstractARM.Allocated(address(market), expectedAmount);
122+
emit AbstractARM.Allocated(address(market), expectedLiquidityDelta, expectedLiquidityDelta);
122123

123124
// Main call
124125
originARM.allocate();
@@ -147,14 +148,15 @@ contract Fork_Concrete_OriginARM_AllocateWithoutAdapter_Test_ is Fork_Shared_Tes
147148

148149
uint256 expectedShares = market.maxRedeem(address(originARM));
149150
uint256 expectedAmount = market.convertToAssets(expectedShares);
151+
int256 expectedLiquidityDelta = getLiquidityDelta();
150152

151153
// Expected event
152154
vm.expectEmit(address(market));
153155
emit IERC4626.Withdraw(
154156
address(originARM), address(originARM), address(originARM), expectedAmount - 1, expectedShares
155157
);
156158
vm.expectEmit(address(originARM));
157-
emit AbstractARM.Allocated(address(market), getLiquidityDelta());
159+
emit AbstractARM.Allocated(address(market), expectedLiquidityDelta, expectedLiquidityDelta + 1 ether);
158160
// Main call
159161
originARM.allocate();
160162

0 commit comments

Comments
 (0)