Skip to content

Commit 3dd606c

Browse files
committed
feat: Add Curve support, remove Morpho allocator logic (SC-938) (#23)
* feat: add basic curve deposit/withdraw * feat: Refactor to use internal functions and modifiers (#24) * feat: refactor to use internal functions and modifiers * fix: use view * fix: move helpers * fix: rm todo * feat: Add DaiUsds swaps (SC-940) (#22) * feat: add dai usds swaps * fix: rm rate limits * test: add testing for failure modes * feat: tests all passing * fix: update broken staging test * test: add invalid order coverage, cleanup * fix: rm morpho functionality * fix: rm morpho * feat: tests passing * fix: rm console * feat: refactor to use rlusd pool * feat: refactor to use new slippages, remove tokens params * feat: add remove liquidity working * feat: tests passing * fix: rm console * fix: update remaining fixes * fix: reorder some code, add zero slippage test coverage * fix: update test names * feat: add simplified calculation * fix: rm unused function * fix: formatting * fix: update slippage * fix: comment * fix: cache param * fix: update spacing
1 parent 67898c2 commit 3dd606c

File tree

5 files changed

+993
-450
lines changed

5 files changed

+993
-450
lines changed

src/MainnetController.sol

Lines changed: 214 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,31 @@ interface IBuidlRedeemLike {
2929
function redeem(uint256 usdcAmount) external;
3030
}
3131

32+
interface ICurvePoolLike {
33+
function add_liquidity(
34+
uint256[] memory amounts,
35+
uint256 minMintAmount,
36+
address receiver
37+
) external;
38+
function coins(uint256 index) external returns (address);
39+
function exchange(
40+
int128 inputIndex,
41+
int128 outputIndex,
42+
uint256 amountIn,
43+
uint256 minAmountOut,
44+
address receiver
45+
) external returns (uint256 tokensOut);
46+
function N_COINS() external view returns (uint256);
47+
function remove_liquidity(
48+
uint256 burnAmount,
49+
uint256[] memory minAmounts,
50+
address receiver
51+
) external;
52+
function stored_rates() external view returns (uint256[] memory);
53+
}
54+
3255
interface IDaiUsdsLike {
33-
function dai() external view returns(address);
56+
function dai() external view returns (address);
3457
function daiToUsds(address usr, uint256 wad) external;
3558
function usdsToDai(address usr, uint256 wad) external;
3659
}
@@ -57,7 +80,7 @@ interface IMapleTokenLike is IERC4626 {
5780
interface IPSMLike {
5881
function buyGemNoFee(address usr, uint256 usdcAmount) external returns (uint256 usdsAmount);
5982
function fill() external returns (uint256 wad);
60-
function gem() external view returns(address);
83+
function gem() external view returns (address);
6184
function sellGemNoFee(address usr, uint256 usdcAmount) external returns (uint256 usdsAmount);
6285
function to18ConversionFactor() external view returns (uint256);
6386
}
@@ -79,7 +102,7 @@ interface IUSTBLike is IERC20 {
79102
}
80103

81104
interface IVaultLike {
82-
function buffer() external view returns(address);
105+
function buffer() external view returns (address);
83106
function draw(uint256 usdsAmount) external;
84107
function wipe(uint256 usdsAmount) external;
85108
}
@@ -98,8 +121,8 @@ contract MainnetController is AccessControl {
98121
uint256 usdcAmount
99122
);
100123

124+
event MaxSlippageSet(address indexed pool, uint256 maxSlippage);
101125
event MintRecipientSet(uint32 indexed destinationDomain, bytes32 mintRecipient);
102-
103126
event RelayerRemoved(address indexed relayer);
104127

105128
/**********************************************************************************************/
@@ -117,6 +140,9 @@ contract MainnetController is AccessControl {
117140
bytes32 public constant LIMIT_AAVE_WITHDRAW = keccak256("LIMIT_AAVE_WITHDRAW");
118141
bytes32 public constant LIMIT_ASSET_TRANSFER = keccak256("LIMIT_ASSET_TRANSFER");
119142
bytes32 public constant LIMIT_BUIDL_REDEEM_CIRCLE = keccak256("LIMIT_BUIDL_REDEEM_CIRCLE");
143+
bytes32 public constant LIMIT_CURVE_DEPOSIT = keccak256("LIMIT_CURVE_DEPOSIT");
144+
bytes32 public constant LIMIT_CURVE_SWAP = keccak256("LIMIT_CURVE_SWAP");
145+
bytes32 public constant LIMIT_CURVE_WITHDRAW = keccak256("LIMIT_CURVE_WITHDRAW");
120146
bytes32 public constant LIMIT_MAPLE_REDEEM = keccak256("LIMIT_MAPLE_REDEEM");
121147
bytes32 public constant LIMIT_SUPERSTATE_REDEEM = keccak256("LIMIT_SUPERSTATE_REDEEM");
122148
bytes32 public constant LIMIT_SUPERSTATE_SUBSCRIBE = keccak256("LIMIT_SUPERSTATE_SUBSCRIBE");
@@ -149,6 +175,8 @@ contract MainnetController is AccessControl {
149175

150176
uint256 public immutable psmTo18ConversionFactor;
151177

178+
mapping(address pool => uint256 maxSlippage) public maxSlippages; // 1e18 precision
179+
152180
mapping(uint32 destinationDomain => bytes32 mintRecipient) public mintRecipients;
153181

154182
/**********************************************************************************************/
@@ -198,6 +226,12 @@ contract MainnetController is AccessControl {
198226
emit MintRecipientSet(destinationDomain, mintRecipient);
199227
}
200228

229+
function setMaxSlippage(address pool, uint256 maxSlippage) external {
230+
_checkRole(DEFAULT_ADMIN_ROLE);
231+
maxSlippages[pool] = maxSlippage;
232+
emit MaxSlippageSet(pool, maxSlippage);
233+
}
234+
201235
/**********************************************************************************************/
202236
/*** Freezer functions ***/
203237
/**********************************************************************************************/
@@ -511,6 +545,182 @@ contract MainnetController is AccessControl {
511545
);
512546
}
513547

548+
/**********************************************************************************************/
549+
/*** Relayer Curve StableSwap functions ***/
550+
/**********************************************************************************************/
551+
552+
function swapCurve(
553+
address pool,
554+
uint256 inputIndex,
555+
uint256 outputIndex,
556+
uint256 amountIn,
557+
uint256 minAmountOut
558+
)
559+
external returns (uint256 amountOut)
560+
{
561+
_checkRole(RELAYER);
562+
563+
uint256 maxSlippage = maxSlippages[pool];
564+
565+
require(maxSlippage != 0, "MainnetController/max-slippage-not-set");
566+
567+
ICurvePoolLike curvePool = ICurvePoolLike(pool);
568+
569+
// Normalized to provide 36 decimal precision when multiplied by asset amount
570+
uint256[] memory rates = curvePool.stored_rates();
571+
572+
// Below code is simplified from the following:
573+
// valueIn = amountIn * rates[inputIndex] / 1e18 // 18 decimal precision, USD
574+
// tokensOut = valueIn * 1e18 / rates[outputIndex] // Token precision, token amount
575+
// result = tokensOut * maxSlippage / 1e18
576+
uint256 minimumMinAmountOut = (amountIn * rates[inputIndex] / rates[outputIndex])
577+
* maxSlippage
578+
/ 1e18;
579+
580+
require(
581+
minAmountOut >= minimumMinAmountOut,
582+
"MainnetController/min-amount-not-met"
583+
);
584+
585+
rateLimits.triggerRateLimitDecrease(
586+
RateLimitHelpers.makeAssetKey(LIMIT_CURVE_SWAP, pool),
587+
amountIn * rates[inputIndex] / 1e18
588+
);
589+
590+
_approve(curvePool.coins(inputIndex), pool, amountIn);
591+
592+
amountOut = abi.decode(
593+
proxy.doCall(
594+
pool,
595+
abi.encodeCall(
596+
curvePool.exchange,
597+
(
598+
int128(int256(inputIndex)), // Assuming safe cast because of 8 token max
599+
int128(int256(outputIndex)), // Assuming safe cast because of 8 token max
600+
amountIn,
601+
minAmountOut,
602+
address(proxy)
603+
)
604+
)
605+
),
606+
(uint256)
607+
);
608+
}
609+
610+
function addLiquidityCurve(
611+
address pool,
612+
uint256[] memory depositAmounts,
613+
uint256 minLpAmount
614+
)
615+
external returns (uint256 shares)
616+
{
617+
_checkRole(RELAYER);
618+
619+
uint256 maxSlippage = maxSlippages[pool];
620+
621+
require(maxSlippage != 0, "MainnetController/max-slippage-not-set");
622+
623+
ICurvePoolLike curvePool = ICurvePoolLike(pool);
624+
625+
uint256 numCoins = curvePool.N_COINS();
626+
627+
require(depositAmounts.length == numCoins, "MainnetController/invalid-deposit-amounts");
628+
629+
// Normalized to provide 36 decimal precision when multiplied by asset amount
630+
uint256[] memory rates = curvePool.stored_rates();
631+
632+
// Aggregate the value of the deposited assets (e.g. USD)
633+
uint256 valueDeposited;
634+
for (uint256 i = 0; i < depositAmounts.length; i++) {
635+
_approve(curvePool.coins(i), pool, depositAmounts[i]);
636+
valueDeposited += depositAmounts[i] * rates[i] / 1e18;
637+
}
638+
639+
// Ensure minimum LP amount expected is greater than max slippage amount
640+
// (assumes that the pool assets are pegged to the same value (e.g. USD))
641+
require(
642+
minLpAmount >= valueDeposited * maxSlippage / 1e18,
643+
"MainnetController/min-amount-not-met"
644+
);
645+
646+
// Reduce the rate limit by the aggregated underlying asset value of the deposit (e.g. USD)
647+
rateLimits.triggerRateLimitDecrease(
648+
RateLimitHelpers.makeAssetKey(LIMIT_CURVE_DEPOSIT, pool),
649+
valueDeposited
650+
);
651+
652+
shares = abi.decode(
653+
proxy.doCall(
654+
pool,
655+
abi.encodeCall(
656+
curvePool.add_liquidity,
657+
(depositAmounts, minLpAmount, address(proxy))
658+
)
659+
),
660+
(uint256)
661+
);
662+
}
663+
664+
function removeLiquidityCurve(
665+
address pool,
666+
uint256 lpBurnAmount,
667+
uint256[] memory minWithdrawAmounts
668+
)
669+
external returns (uint256[] memory withdrawnTokens)
670+
{
671+
_checkRole(RELAYER);
672+
673+
uint256 maxSlippage = maxSlippages[pool];
674+
675+
require(maxSlippage != 0, "MainnetController/max-slippage-not-set");
676+
677+
ICurvePoolLike curvePool = ICurvePoolLike(pool);
678+
679+
uint256 numCoins = curvePool.N_COINS();
680+
681+
require(
682+
minWithdrawAmounts.length == numCoins,
683+
"MainnetController/invalid-min-withdraw-amounts"
684+
);
685+
686+
// Normalized to provide 36 decimal precision when multiplied by asset amount
687+
uint256[] memory rates = curvePool.stored_rates();
688+
689+
// Aggregate the minimum values of the withdrawn assets (e.g. USD)
690+
uint256 valueMinWithdrawn;
691+
for (uint256 i = 0; i < numCoins; i++) {
692+
valueMinWithdrawn += minWithdrawAmounts[i] * rates[i] / 1e18;
693+
}
694+
695+
// Check that the aggregated minimums are greater than the max slippage amount
696+
require(
697+
valueMinWithdrawn >= lpBurnAmount * maxSlippage / 1e18,
698+
"MainnetController/min-amount-not-met"
699+
);
700+
701+
withdrawnTokens = abi.decode(
702+
proxy.doCall(
703+
pool,
704+
abi.encodeCall(
705+
curvePool.remove_liquidity,
706+
(lpBurnAmount, minWithdrawAmounts, address(proxy))
707+
)
708+
),
709+
(uint256[])
710+
);
711+
712+
// Aggregate value withdrawn to reduce the rate limit
713+
uint256 valueWithdrawn;
714+
for (uint256 i = 0; i < withdrawnTokens.length; i++) {
715+
valueWithdrawn += withdrawnTokens[i] * rates[i] / 1e18;
716+
}
717+
718+
rateLimits.triggerRateLimitDecrease(
719+
RateLimitHelpers.makeAssetKey(LIMIT_CURVE_WITHDRAW, pool),
720+
valueWithdrawn
721+
);
722+
}
723+
514724
/**********************************************************************************************/
515725
/*** Relayer Ethena functions ***/
516726
/**********************************************************************************************/
@@ -611,41 +821,6 @@ contract MainnetController is AccessControl {
611821
);
612822
}
613823

614-
/**********************************************************************************************/
615-
/*** Relayer Morpho functions ***/
616-
/**********************************************************************************************/
617-
618-
function setSupplyQueueMorpho(address morphoVault, Id[] memory newSupplyQueue) external {
619-
_checkRole(RELAYER);
620-
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_4626_DEPOSIT, morphoVault));
621-
622-
proxy.doCall(
623-
morphoVault,
624-
abi.encodeCall(IMetaMorpho(morphoVault).setSupplyQueue, (newSupplyQueue))
625-
);
626-
}
627-
628-
function updateWithdrawQueueMorpho(address morphoVault, uint256[] calldata indexes) external {
629-
_checkRole(RELAYER);
630-
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_4626_DEPOSIT, morphoVault));
631-
proxy.doCall(
632-
morphoVault,
633-
abi.encodeCall(IMetaMorpho(morphoVault).updateWithdrawQueue, (indexes))
634-
);
635-
}
636-
637-
function reallocateMorpho(address morphoVault, MarketAllocation[] calldata allocations)
638-
external
639-
{
640-
_checkRole(RELAYER);
641-
_rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_4626_DEPOSIT, morphoVault));
642-
643-
proxy.doCall(
644-
morphoVault,
645-
abi.encodeCall(IMetaMorpho(morphoVault).reallocate, (allocations))
646-
);
647-
}
648-
649824
/**********************************************************************************************/
650825
/*** Relayer Superstate functions ***/
651826
/**********************************************************************************************/

0 commit comments

Comments
 (0)