From e556c1a7a0ec7f6d700b47841eb586f5f4801406 Mon Sep 17 00:00:00 2001 From: Jeroen <1748621+hieronx@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:52:48 +0100 Subject: [PATCH] Add escrow migration spell (#418) * Add escrow migration spell * Archive deployed spell * Add escrow migration spell (#83) * Add escrow migration spell * Archive deployed spell --- ...Ce97352C1469884EEF3547Ec9362329FE78Cf0.sol | 55 ++++++++++++++++ ...97352C1469884EEF3547Ec9362329FE78Cf0.t.sol | 63 +++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 deployments/mainnet/spells/002_0xa3Ce97352C1469884EEF3547Ec9362329FE78Cf0.sol create mode 100644 deployments/mainnet/spells/002_0xa3Ce97352C1469884EEF3547Ec9362329FE78Cf0.t.sol diff --git a/deployments/mainnet/spells/002_0xa3Ce97352C1469884EEF3547Ec9362329FE78Cf0.sol b/deployments/mainnet/spells/002_0xa3Ce97352C1469884EEF3547Ec9362329FE78Cf0.sol new file mode 100644 index 00000000..233a3303 --- /dev/null +++ b/deployments/mainnet/spells/002_0xa3Ce97352C1469884EEF3547Ec9362329FE78Cf0.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.26; + +import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +interface RootLike { + function relyContract(address, address) external; + function executeScheduledRely(address) external; + function delay() external view returns (uint64); +} + +interface AuthLike { + function rely(address) external; + function deny(address) external; +} + +interface OldEscrowLike { + function approve(address token, address spender, uint256 value) external; +} + +// Spell to migrate the escrow balances +contract Spell { + bool public done; + string public constant description = "Liquidity Pool escrow migration spell"; + + address public constant LP_MULTISIG = 0xD9D30ab47c0f096b0AA67e9B8B1624504a63e7FD; + address public constant OLD_DELAYED_ADMIN = 0x2559998026796Ca6fd057f3aa66F2d6ecdEd9028; + address public constant OLD_ROOT = 0x498016d30Cd5f0db50d7ACE329C07313a0420502; + address public constant OLD_ESCROW = 0xd595E1483c507E74E2E6A3dE8e7D08d8f6F74936; + address public constant NEW_ESCROW = 0x0000000005F458Fd6ba9EEb5f365D83b7dA913dD; + address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + + function cast() public { + require(!done, "spell-already-cast"); + done = true; + execute(); + } + + function execute() internal { + RootLike root = RootLike(OLD_ROOT); + root.relyContract(OLD_ESCROW, address(this)); + + migrateBalance(USDC); + + AuthLike(OLD_ROOT).deny(address(this)); + AuthLike(OLD_ESCROW).deny(address(this)); + } + + function migrateBalance(address token) internal { + OldEscrowLike escrow = OldEscrowLike(OLD_ESCROW); + escrow.approve(token, address(this), type(uint256).max); + IERC20(token).transferFrom(OLD_ESCROW, NEW_ESCROW, IERC20(token).balanceOf(OLD_ESCROW)); + escrow.approve(token, address(this), 0); + } +} diff --git a/deployments/mainnet/spells/002_0xa3Ce97352C1469884EEF3547Ec9362329FE78Cf0.t.sol b/deployments/mainnet/spells/002_0xa3Ce97352C1469884EEF3547Ec9362329FE78Cf0.t.sol new file mode 100644 index 00000000..a34eb135 --- /dev/null +++ b/deployments/mainnet/spells/002_0xa3Ce97352C1469884EEF3547Ec9362329FE78Cf0.t.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.26; + +import {ICentrifugeRouter} from "src/interfaces/ICentrifugeRouter.sol"; +import "forge-std/Test.sol"; +import "src/002.sol"; + +interface DelayedAdminLike { + function scheduleRely(address spell) external; +} + +contract SpellTest is Test { + Spell spell; + + // Details related to pending redemption that is failing + address investor1 = 0x32f5eF78AA9C7b8882D748331AdcFe0dfA4f1a14; + address investor2 = 0xbe19e6AdF267248beE015dd3fbBa363E12ca8cE6; + address vault = 0xa7607A638df0117E6718b93f8cFf53503A815D2f; + ICentrifugeRouter router = ICentrifugeRouter(0x2F445BA946044C5F508a63eEaF7EAb673c69a1F4); + + IERC20 usdc; + + function setUp() public { + spell = new Spell(); + usdc = IERC20(spell.USDC()); + } + + function testCastSuccessful() public { + assertEq(usdc.balanceOf(spell.OLD_ESCROW()), 143360978110); + assertEq(usdc.balanceOf(spell.NEW_ESCROW()), 0); + + vm.expectRevert(bytes("SafeTransferLib/safe-transfer-from-failed")); + vm.prank(investor1); + router.claimRedeem(vault, investor1, investor1); + + vm.expectRevert(bytes("SafeTransferLib/safe-transfer-from-failed")); + vm.prank(investor2); + router.claimRedeem(vault, investor2, investor2); + + castSpell(); + + assertEq(usdc.balanceOf(spell.OLD_ESCROW()), 0); + assertEq(usdc.balanceOf(spell.NEW_ESCROW()), 143360978110); + + vm.prank(investor1); + router.claimRedeem(vault, investor1, investor1); + + vm.prank(investor2); + router.claimRedeem(vault, investor2, investor2); + } + + function castSpell() internal { + // Admin submits a tx to delayedAdmin in order to rely spell -> to be done manually before spell cast + DelayedAdminLike delayedAdmin = DelayedAdminLike(spell.OLD_DELAYED_ADMIN()); + vm.prank(spell.LP_MULTISIG()); + delayedAdmin.scheduleRely(address(spell)); + + // Warp to the time when the spell can be cast -> current block + delay + vm.warp(block.timestamp + RootLike(spell.OLD_ROOT()).delay()); + RootLike(spell.OLD_ROOT()).executeScheduledRely(address(spell)); // --> to be called after delay has passed + spell.cast(); + } +}