Skip to content

Commit b47d021

Browse files
feat: Add Centrifuge cancelation flow support (#16)
* Update forge-std, add cancelation interface * Rename ERC and rename fund token * Add cancel deposit request test cases * Add claim cancel redeem test cases * Add more asserts * Address feedback * fix: Update test to work (#17) * fix: update test to work * chore: rm unneeded skip * Address PR feedback * Order * Add request id param * Fix alignment --------- Co-authored-by: Lucas Manuel <lucasmanuel.tech@gmail.com>
1 parent 6a35ada commit b47d021

File tree

4 files changed

+596
-181
lines changed

4 files changed

+596
-181
lines changed

script/staging/test/StagingDeployment.t.sol

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ contract MainnetStagingDeploymentTests is StagingDeploymentTestBase {
197197
mainnetController.withdrawERC4626(Ethereum.SUSDS, 10e18);
198198
vm.stopPrank();
199199

200-
assertEq(usds.balanceOf(address(almProxy)), startingBalance + 10e18);
200+
assertEq(usds.balanceOf(address(almProxy)), startingBalance + 10e18);
201201

202202
assertGe(IERC4626(Ethereum.SUSDS).balanceOf(address(almProxy)), 0); // Interest earned
203203
}
@@ -214,7 +214,7 @@ contract MainnetStagingDeploymentTests is StagingDeploymentTestBase {
214214

215215
assertGe(usds.balanceOf(address(almProxy)), startingBalance + 10e18); // Interest earned
216216

217-
assertEq(IERC4626(Ethereum.SUSDS).balanceOf(address(almProxy)), 0);
217+
assertEq(IERC4626(Ethereum.SUSDS).balanceOf(address(almProxy)), 0);
218218
}
219219

220220
function test_depositAndWithdrawUsdsFromAave() public {
@@ -266,20 +266,20 @@ contract MainnetStagingDeploymentTests is StagingDeploymentTestBase {
266266

267267
_simulateUsdeBurn(10e18 - 1);
268268

269-
assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6 - 1); // Rounding not captured
270-
269+
assertEq(usdc.balanceOf(address(almProxy)), startingBalance + 10e6 - 1); // Rounding not captured
270+
271271
assertGe(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0); // Interest earned
272272
}
273273

274274
function test_mintDepositCooldownSharesBurnUsde() public {
275-
uint256 startingBalance = usdc.balanceOf(address(almProxy));
276-
277275
vm.startPrank(relayerSafe);
278276
mainnetController.mintUSDS(10e18);
279277
mainnetController.swapUSDSToUSDC(10e6);
280278
mainnetController.prepareUSDeMint(10e6);
281279
vm.stopPrank();
282280

281+
uint256 startingBalance = usdc.balanceOf(address(almProxy));
282+
283283
_simulateUsdeMint(10e6);
284284

285285
vm.startPrank(relayerSafe);
@@ -288,23 +288,28 @@ contract MainnetStagingDeploymentTests is StagingDeploymentTestBase {
288288
uint256 usdeAmount = mainnetController.cooldownSharesSUSDe(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)));
289289
skip(7 days);
290290
mainnetController.unstakeSUSDe();
291-
mainnetController.prepareUSDeBurn(usdeAmount);
291+
292+
// Handle situation where usde balance of ALM Proxy is higher than max rate limit
293+
uint256 maxBurnAmount = rateLimits.getCurrentRateLimit(mainnetController.LIMIT_USDE_BURN());
294+
uint256 burnAmount = usdeAmount > maxBurnAmount ? maxBurnAmount : usdeAmount;
295+
mainnetController.prepareUSDeBurn(burnAmount);
296+
292297
vm.stopPrank();
293298

294-
_simulateUsdeBurn(usdeAmount);
299+
_simulateUsdeBurn(burnAmount);
295300

296301
assertGe(usdc.balanceOf(address(almProxy)), startingBalance + 10e6 - 1); // Interest earned (rounding)
297-
298-
assertEq(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0);
302+
303+
assertEq(IERC4626(Ethereum.SUSDE).balanceOf(address(almProxy)), 0);
299304
}
300305

301306
/**********************************************************************************************/
302307
/**** Helper functions ***/
303308
/**********************************************************************************************/
304309

305-
// NOTE: In reality these actions are performed by the signer submitting an order with an
306-
// EIP712 signature which is verified by the ethenaMinter contract,
307-
// minting/burning USDe into the ALMProxy. Also, for the purposes of this test,
310+
// NOTE: In reality these actions are performed by the signer submitting an order with an
311+
// EIP712 signature which is verified by the ethenaMinter contract,
312+
// minting/burning USDe into the ALMProxy. Also, for the purposes of this test,
308313
// minting/burning is done 1:1 with USDC.
309314

310315
// TODO: Try doing ethena minting with EIP-712 signatures (vm.sign)
@@ -313,8 +318,8 @@ contract MainnetStagingDeploymentTests is StagingDeploymentTestBase {
313318
vm.prank(Ethereum.ETHENA_MINTER);
314319
usdc.transferFrom(address(almProxy), Ethereum.ETHENA_MINTER, amount);
315320
deal(
316-
Ethereum.USDE,
317-
address(almProxy),
321+
Ethereum.USDE,
322+
address(almProxy),
318323
IERC20(Ethereum.USDE).balanceOf(address(almProxy)) + amount * 1e12
319324
);
320325
}
@@ -496,7 +501,7 @@ contract BaseStagingDeploymentTests is StagingDeploymentTestBase {
496501

497502
assertGe(usdcBase.balanceOf(address(baseAlmProxy)), 10e6); // Interest earned
498503

499-
assertEq(IERC20(MORPHO_VAULT_USDC).balanceOf(address(baseAlmProxy)), 0);
504+
assertEq(IERC20(MORPHO_VAULT_USDC).balanceOf(address(baseAlmProxy)), 0);
500505

501506
baseController.transferUSDCToCCTP(1e6 - 1, CCTPForwarder.DOMAIN_ID_CIRCLE_ETHEREUM); // Account for potential rounding
502507
vm.stopPrank();

src/MainnetController.sol

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { IPool as IAavePool } from "aave-v3-origin/src/core/contracts/interfaces
66

77
import { IERC20 } from "forge-std/interfaces/IERC20.sol";
88
import { IERC4626 } from "forge-std/interfaces/IERC4626.sol";
9+
import { IERC7540 } from "forge-std/interfaces/IERC7540.sol";
910

1011
import { IMetaMorpho, Id, MarketAllocation } from "metamorpho/interfaces/IMetaMorpho.sol";
1112

@@ -34,18 +35,20 @@ interface IDaiUsdsLike {
3435
function usdsToDai(address usr, uint256 wad) external;
3536
}
3637

37-
interface IERC7540 is IERC4626 {
38-
function requestDeposit(uint256 assets, address controller, address owner)
39-
external returns (uint256);
40-
function requestRedeem(uint256 shares, address controller, address owner)
41-
external returns (uint256);
42-
}
43-
4438
interface IEthenaMinterLike {
4539
function setDelegatedSigner(address delegateSigner) external;
4640
function removeDelegatedSigner(address delegateSigner) external;
4741
}
4842

43+
interface ICentrifugeToken is IERC7540 {
44+
function cancelDepositRequest(uint256 requestId, address controller) external;
45+
function cancelRedeemRequest(uint256 requestId, address controller) external;
46+
function claimCancelDepositRequest(uint256 requestId, address receiver, address controller)
47+
external returns (uint256 assets);
48+
function claimCancelRedeemRequest(uint256 requestId, address receiver, address controller)
49+
external returns (uint256 shares);
50+
}
51+
4952
interface IMapleTokenLike is IERC4626 {
5053
function requestRedeem(uint256 shares, address receiver) external;
5154
function removeShares(uint256 shares, address receiver) external;
@@ -388,7 +391,7 @@ contract MainnetController is AccessControl {
388391
// Claim shares from the vault to the proxy
389392
proxy.doCall(
390393
token,
391-
abi.encodeCall(IERC7540(token).mint, (shares, address(proxy)))
394+
abi.encodeCall(IERC4626(token).mint, (shares, address(proxy)))
392395
);
393396
}
394397

@@ -422,6 +425,72 @@ contract MainnetController is AccessControl {
422425
);
423426
}
424427

428+
/**********************************************************************************************/
429+
/*** Relayer Centrifuge functions ***/
430+
/**********************************************************************************************/
431+
432+
// NOTE: These cancelation methods are compatible with ERC-7887
433+
434+
uint256 CENTRIFUGE_REQUEST_ID = 0;
435+
436+
function cancelCentrifugeDepositRequest(address token)
437+
external
438+
onlyRole(RELAYER)
439+
rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_DEPOSIT, token))
440+
{
441+
// NOTE: While the cancelation is pending, no new deposit request can be submitted
442+
proxy.doCall(
443+
token,
444+
abi.encodeCall(
445+
ICentrifugeToken(token).cancelDepositRequest,
446+
(CENTRIFUGE_REQUEST_ID, address(proxy))
447+
)
448+
);
449+
}
450+
451+
function claimCentrifugeCancelDepositRequest(address token)
452+
external
453+
onlyRole(RELAYER)
454+
rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_DEPOSIT, token))
455+
{
456+
proxy.doCall(
457+
token,
458+
abi.encodeCall(
459+
ICentrifugeToken(token).claimCancelDepositRequest,
460+
(CENTRIFUGE_REQUEST_ID, address(proxy), address(proxy))
461+
)
462+
);
463+
}
464+
465+
function cancelCentrifugeRedeemRequest(address token)
466+
external
467+
onlyRole(RELAYER)
468+
rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_REDEEM, token))
469+
{
470+
// NOTE: While the cancelation is pending, no new redeem request can be submitted
471+
proxy.doCall(
472+
token,
473+
abi.encodeCall(
474+
ICentrifugeToken(token).cancelRedeemRequest,
475+
(CENTRIFUGE_REQUEST_ID, address(proxy))
476+
)
477+
);
478+
}
479+
480+
function claimCentrifugeCancelRedeemRequest(address token)
481+
external
482+
onlyRole(RELAYER)
483+
rateLimitExists(RateLimitHelpers.makeAssetKey(LIMIT_7540_REDEEM, token))
484+
{
485+
proxy.doCall(
486+
token,
487+
abi.encodeCall(
488+
ICentrifugeToken(token).claimCancelRedeemRequest,
489+
(CENTRIFUGE_REQUEST_ID, address(proxy), address(proxy))
490+
)
491+
);
492+
}
493+
425494
/**********************************************************************************************/
426495
/*** Relayer Aave functions ***/
427496
/**********************************************************************************************/

0 commit comments

Comments
 (0)