From 2ff2f26c8342d2ec2d1a90fbe449ff34cd3485c1 Mon Sep 17 00:00:00 2001 From: DhairyaSethi <55102840+DhairyaSethi@users.noreply.github.com> Date: Thu, 12 Dec 2024 00:55:53 +0530 Subject: [PATCH] feat: ccip 1.5.1 upgrade --- .gitmodules | 4 + ...hereum_GHOCCIP151Upgrade_20241209_after.md | 19 + lib/ccip | 1 + remappings.txt | 1 + ...eV3Arbitrum_GHOCCIP151Upgrade_20241209.sol | 104 ++- ...3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol | 462 ++++++++++++- ...AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol | 627 ++++++++++++++++++ ...eV3Ethereum_GHOCCIP151Upgrade_20241209.sol | 77 ++- ...3Ethereum_GHOCCIP151Upgrade_20241209.t.sol | 492 +++++++++++++- .../utils/CCIPUtils.sol | 17 +- src/interfaces/IGhoToken.sol | 41 +- src/interfaces/ccip/IEVM2EVMOffRamp.sol | 20 +- src/interfaces/ccip/IEVM2EVMOnRamp.sol | 72 ++ src/interfaces/ccip/IInternal.sol | 5 + src/interfaces/ccip/IRateLimiter.sol | 19 + src/interfaces/ccip/IRouter.sol | 46 +- src/interfaces/ccip/ITokenAdminRegistry.sol | 48 ++ src/interfaces/ccip/ITypeAndVersion.sol | 6 + src/interfaces/ccip/tokenPool/IPool.sol | 32 + .../IUpgradeableBurnMintTokenPool.sol | 219 ++++++ .../IUpgradeableLockReleaseTokenPool.sol | 267 ++++++++ 21 files changed, 2544 insertions(+), 35 deletions(-) create mode 100644 diffs/AaveV3Ethereum_GHOCCIP151Upgrade_20241209_before_AaveV3Ethereum_GHOCCIP151Upgrade_20241209_after.md create mode 160000 lib/ccip create mode 100644 src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol create mode 100644 src/interfaces/ccip/IEVM2EVMOnRamp.sol create mode 100644 src/interfaces/ccip/IRateLimiter.sol create mode 100644 src/interfaces/ccip/ITokenAdminRegistry.sol create mode 100644 src/interfaces/ccip/ITypeAndVersion.sol create mode 100644 src/interfaces/ccip/tokenPool/IPool.sol create mode 100644 src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol create mode 100644 src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol diff --git a/.gitmodules b/.gitmodules index f7316a1d6..df7437c38 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "lib/aave-helpers"] path = lib/aave-helpers url = https://github.com/bgd-labs/aave-helpers +[submodule "lib/ccip"] + path = lib/ccip + url = https://github.com/aave/ccip + branch = feat/1_5_1_token_pool diff --git a/diffs/AaveV3Ethereum_GHOCCIP151Upgrade_20241209_before_AaveV3Ethereum_GHOCCIP151Upgrade_20241209_after.md b/diffs/AaveV3Ethereum_GHOCCIP151Upgrade_20241209_before_AaveV3Ethereum_GHOCCIP151Upgrade_20241209_after.md new file mode 100644 index 000000000..95861275d --- /dev/null +++ b/diffs/AaveV3Ethereum_GHOCCIP151Upgrade_20241209_before_AaveV3Ethereum_GHOCCIP151Upgrade_20241209_after.md @@ -0,0 +1,19 @@ +## Emodes changed + +### EMode: ETH correlated(id: 1) + + + +### EMode: sUSDe Stablecoins(id: 2) + + + +### EMode: rsETH LST main(id: 3) + + + +## Raw diff + +```json +{} +``` \ No newline at end of file diff --git a/lib/ccip b/lib/ccip new file mode 160000 index 000000000..5f84e6eaa --- /dev/null +++ b/lib/ccip @@ -0,0 +1 @@ +Subproject commit 5f84e6eaa1393ce19c50c3dfbbc026a77d1998c7 diff --git a/remappings.txt b/remappings.txt index 9fefc7eea..8f62b1c9f 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,3 +3,4 @@ aave-helpers/=lib/aave-helpers/ aave-v3-origin/=lib/aave-helpers/lib/aave-address-book/lib/aave-v3-origin/src/ forge-std/=lib/aave-helpers/lib/forge-std/src/ solidity-utils/=lib/aave-helpers/lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src +aave-ccip/=lib/ccip/contracts/src/v0.8/ccip diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol index ec61d0ecc..218894346 100644 --- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol +++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol @@ -2,6 +2,22 @@ pragma solidity ^0.8.0; import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGenericExecutor.sol'; +import {ProxyAdmin} from 'solidity-utils/contracts/transparent-proxy/ProxyAdmin.sol'; +import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol'; +import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol'; +import {IUpgradeableBurnMintTokenPool_1_4, IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol'; +import {IGhoToken} from 'src/interfaces/IGhoToken.sol'; +import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol'; + +contract Burner { + function burn(address token, uint256 amount) external { + IGhoToken(token).burn(amount); + } +} + /** * @title GHO CCIP 1.5.1 Upgrade * @author Aave Labs @@ -9,7 +25,93 @@ import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGen * - Discussion: TODO */ contract AaveV3Arbitrum_GHOCCIP151Upgrade_20241209 is IProposalGenericExecutor { + uint64 public constant ETH_CHAIN_SELECTOR = 5009297550715157269; + + // https://arbiscan.io/address/0x39AE1032cF4B334a1Ed41cdD0833bdD7c7E7751E + ITokenAdminRegistry public constant TOKEN_ADMIN_REGISTRY = + ITokenAdminRegistry(0x39AE1032cF4B334a1Ed41cdD0833bdD7c7E7751E); + + // https://arbiscan.io/address/0xF168B83598516A532a85995b52504a2Fa058C068 + IUpgradeableBurnMintTokenPool_1_4 public constant EXISTING_TOKEN_POOL = + IUpgradeableBurnMintTokenPool_1_4(0xF168B83598516A532a85995b52504a2Fa058C068); // MiscArbitrum.GHO_CCIP_TOKEN_POOL; -> not using since this will be overwritten with new token pool address + IUpgradeableBurnMintTokenPool_1_5_1 public immutable NEW_TOKEN_POOL; + + // https://etherscan.io/address/0x9Ec9F9804733df96D1641666818eFb5198eC50f0 + address public constant EXISTING_REMOTE_POOL_ETH = 0x9Ec9F9804733df96D1641666818eFb5198eC50f0; // ProxyPool on ETH + address public immutable NEW_REMOTE_POOL_ETH; + + ProxyAdmin public constant PROXY_ADMIN = ProxyAdmin(MiscArbitrum.PROXY_ADMIN); + IGhoToken public constant GHO = IGhoToken(AaveV3ArbitrumAssets.GHO_UNDERLYING); + + constructor(address newTokenPoolArb, address newTokenPoolEth) { + NEW_TOKEN_POOL = IUpgradeableBurnMintTokenPool_1_5_1(newTokenPoolArb); + NEW_REMOTE_POOL_ETH = newTokenPoolEth; + } + function execute() external { - // custom code goes here + _acceptOwnership(); + _migrateLiquidity(); + _setupAndRegisterNewPool(); + } + + // pre-req - chainlink transfers gho token pool ownership on token admin registry + function _acceptOwnership() internal { + NEW_TOKEN_POOL.acceptOwnership(); + TOKEN_ADMIN_REGISTRY.acceptAdminRole(AaveV3ArbitrumAssets.GHO_UNDERLYING); + } + + function _migrateLiquidity() internal { + // bucketLevel === bridgedAmount + (uint256 bucketCapacity, uint256 bucketLevel) = GHO.getFacilitatorBucket( + address(EXISTING_TOKEN_POOL) + ); + + GHO.addFacilitator(address(NEW_TOKEN_POOL), 'CCIP v1.5.1 TokenPool', uint128(bucketCapacity)); + NEW_TOKEN_POOL.transferLiquidity(address(EXISTING_TOKEN_POOL), bucketLevel); + + address existingTokenPoolImpl = PROXY_ADMIN.getProxyImplementation( + TransparentUpgradeableProxy(payable(address(EXISTING_TOKEN_POOL))) + ); + PROXY_ADMIN.upgrade( + TransparentUpgradeableProxy(payable(address(EXISTING_TOKEN_POOL))), + address(new Burner()) + ); + Burner(address(EXISTING_TOKEN_POOL)).burn(address(GHO), bucketLevel); + PROXY_ADMIN.upgrade( + TransparentUpgradeableProxy(payable(address(EXISTING_TOKEN_POOL))), + existingTokenPoolImpl + ); + + GHO.removeFacilitator(address(EXISTING_TOKEN_POOL)); + } + + function _setupAndRegisterNewPool() internal { + IRateLimiter.Config memory emptyRateLimiterConfig = IRateLimiter.Config({ + isEnabled: false, + capacity: 0, + rate: 0 + }); + + IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate[] + memory chains = new IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate[](1); + + bytes[] memory remotePoolAddresses = new bytes[](2); + remotePoolAddresses[0] = abi.encode(EXISTING_REMOTE_POOL_ETH); + remotePoolAddresses[1] = abi.encode(NEW_REMOTE_POOL_ETH); + + chains[0] = IUpgradeableBurnMintTokenPool_1_5_1.ChainUpdate({ + remoteChainSelector: ETH_CHAIN_SELECTOR, + remotePoolAddresses: remotePoolAddresses, + remoteTokenAddress: abi.encode(MiscEthereum.GHO_TOKEN), + outboundRateLimiterConfig: emptyRateLimiterConfig, + inboundRateLimiterConfig: emptyRateLimiterConfig + }); + + // setup new pool + NEW_TOKEN_POOL.applyChainUpdates(new uint64[](0), chains); + NEW_TOKEN_POOL.setRateLimitAdmin(EXISTING_TOKEN_POOL.getRateLimitAdmin()); // GhoCcipSteward + + // register new pool + TOKEN_ADMIN_REGISTRY.setPool(address(GHO), address(NEW_TOKEN_POOL)); } } diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol index dd23bd9e9..deb7bf5f0 100644 --- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol +++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol @@ -1,32 +1,482 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import 'forge-std/Test.sol'; + +import {IGhoToken} from 'src/interfaces/IGhoToken.sol'; +import {IClient} from 'src/interfaces/ccip/IClient.sol'; +import {IInternal} from 'src/interfaces/ccip/IInternal.sol'; +import {IRouter} from 'src/interfaces/ccip/IRouter.sol'; +import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol'; +import {IEVM2EVMOnRamp} from 'src/interfaces/ccip/IEVM2EVMOnRamp.sol'; +import {ITypeAndVersion} from 'src/interfaces/ccip/ITypeAndVersion.sol'; +import {IEVM2EVMOffRamp_1_5} from 'src/interfaces/ccip/IEVM2EVMOffRamp.sol'; +import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol'; +import {IPool as IPool_CCIP} from 'src/interfaces/ccip/tokenPool/IPool.sol'; +import {IUpgradeableBurnMintTokenPool_1_4, IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol'; + +import {ProtocolV3TestBase} from 'aave-helpers/src/ProtocolV3TestBase.sol'; import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {GovernanceV3Arbitrum} from 'aave-address-book/GovernanceV3Arbitrum.sol'; -import 'forge-std/Test.sol'; -import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol'; +import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol'; +import {UpgradeableBurnMintTokenPool} from 'aave-ccip/pools/GHO/UpgradeableBurnMintTokenPool.sol'; + +import {CCIPUtils} from './utils/CCIPUtils.sol'; import {AaveV3Arbitrum_GHOCCIP151Upgrade_20241209} from './AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol'; /** * @dev Test for AaveV3Arbitrum_GHOCCIP151Upgrade_20241209 * command: FOUNDRY_PROFILE=arbitrum forge test --match-path=src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.t.sol -vv */ -contract AaveV3Arbitrum_GHOCCIP151Upgrade_20241209_Test is ProtocolV3TestBase { +contract AaveV3Arbitrum_GHOCCIP151Upgrade_20241209_Base is ProtocolV3TestBase { + struct CCIPSendParams { + address sender; + uint256 amount; + CCIPUtils.PoolVersion poolVersion; + } + + uint64 internal constant ETH_CHAIN_SELECTOR = 5009297550715157269; + uint64 internal constant ARB_CHAIN_SELECTOR = 4949039107694359620; + + IGhoToken internal constant GHO = IGhoToken(AaveV3ArbitrumAssets.GHO_UNDERLYING); + ITokenAdminRegistry internal constant TOKEN_ADMIN_REGISTRY = + ITokenAdminRegistry(0x39AE1032cF4B334a1Ed41cdD0833bdD7c7E7751E); + address internal constant ETH_PROXY_POOL = 0x9Ec9F9804733df96D1641666818eFb5198eC50f0; + IEVM2EVMOnRamp internal constant ON_RAMP = + IEVM2EVMOnRamp(0x67761742ac8A21Ec4D76CA18cbd701e5A6F3Bef3); + IEVM2EVMOffRamp_1_5 internal constant OFF_RAMP = + IEVM2EVMOffRamp_1_5(0x91e46cc5590A4B9182e47f40006140A7077Dec31); + IRouter internal constant ROUTER = IRouter(0x141fa059441E0ca23ce184B6A78bafD2A517DdE8); + address internal constant GHO_CCIP_STEWARD = 0xb329CEFF2c362F315900d245eC88afd24C4949D5; + + IUpgradeableBurnMintTokenPool_1_4 internal constant EXISTING_TOKEN_POOL = + IUpgradeableBurnMintTokenPool_1_4(MiscArbitrum.GHO_CCIP_TOKEN_POOL); // will be changed to 1.5.1 after AIP execution + IUpgradeableBurnMintTokenPool_1_5_1 internal NEW_TOKEN_POOL; + AaveV3Arbitrum_GHOCCIP151Upgrade_20241209 internal proposal; - function setUp() public { + address internal NEW_REMOTE_POOL_ETH = makeAddr('LockReleaseTokenPool 1.5.1'); + address internal alice = makeAddr('alice'); + address internal bob = makeAddr('bob'); + address internal carol = makeAddr('carol'); + + event Burned(address indexed sender, uint256 amount); + event Minted(address indexed sender, address indexed recipient, uint256 amount); + event CCIPSendRequested(IInternal.EVM2EVMMessage message); + + function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('arbitrum'), 283036001); - proposal = new AaveV3Arbitrum_GHOCCIP151Upgrade_20241209(); + NEW_TOKEN_POOL = IUpgradeableBurnMintTokenPool_1_5_1(_deployNewTokenPoolArb()); + proposal = new AaveV3Arbitrum_GHOCCIP151Upgrade_20241209( + address(NEW_TOKEN_POOL), + NEW_REMOTE_POOL_ETH + ); + + // pre-req - chainlink transfers gho token pool ownership on token admin registry + vm.prank(TOKEN_ADMIN_REGISTRY.owner()); + TOKEN_ADMIN_REGISTRY.transferAdminRole(address(GHO), GovernanceV3Arbitrum.EXECUTOR_LVL_1); + + _validateConstants(); } /** * @dev executes the generic test suite including e2e and config snapshots */ - function test_defaultProposalExecution() public { + function skip_test_defaultProposalExecution() public { defaultTest( 'AaveV3Arbitrum_GHOCCIP151Upgrade_20241209', AaveV3Arbitrum.POOL, address(proposal) ); } + + function _deployNewTokenPoolArb() private returns (address) { + IUpgradeableBurnMintTokenPool_1_4 existingTokenPool = IUpgradeableBurnMintTokenPool_1_4( + MiscArbitrum.GHO_CCIP_TOKEN_POOL + ); + address newTokenPoolImpl = address( + new UpgradeableBurnMintTokenPool( + existingTokenPool.getToken(), + IGhoToken(existingTokenPool.getToken()).decimals(), + existingTokenPool.getArmProxy(), + existingTokenPool.getAllowListEnabled() + ) + ); + return + address( + new TransparentUpgradeableProxy( + newTokenPoolImpl, + address(MiscArbitrum.PROXY_ADMIN), + abi.encodeCall( + IUpgradeableBurnMintTokenPool_1_5_1.initialize, + ( + GovernanceV3Arbitrum.EXECUTOR_LVL_1, // owner + existingTokenPool.getAllowList(), + existingTokenPool.getRouter() + ) + ) + ) + ); + } + + function _validateConstants() private view { + assertEq(address(proposal.TOKEN_ADMIN_REGISTRY()), address(TOKEN_ADMIN_REGISTRY)); + assertEq(proposal.ETH_CHAIN_SELECTOR(), ETH_CHAIN_SELECTOR); + assertEq(address(proposal.EXISTING_TOKEN_POOL()), address(EXISTING_TOKEN_POOL)); + assertEq(address(proposal.EXISTING_REMOTE_POOL_ETH()), ETH_PROXY_POOL); + assertEq(address(proposal.NEW_TOKEN_POOL()), address(NEW_TOKEN_POOL)); + assertEq(address(proposal.NEW_REMOTE_POOL_ETH()), NEW_REMOTE_POOL_ETH); + + assertEq(TOKEN_ADMIN_REGISTRY.typeAndVersion(), 'TokenAdminRegistry 1.5.0'); + assertEq(NEW_TOKEN_POOL.typeAndVersion(), 'BurnMintTokenPool 1.5.1'); + assertEq(EXISTING_TOKEN_POOL.typeAndVersion(), 'BurnMintTokenPool 1.4.0'); + assertEq( + ITypeAndVersion(EXISTING_TOKEN_POOL.getProxyPool()).typeAndVersion(), + 'BurnMintTokenPoolAndProxy 1.5.0' + ); + assertEq(ON_RAMP.typeAndVersion(), 'EVM2EVMOnRamp 1.5.0'); + assertEq(OFF_RAMP.typeAndVersion(), 'EVM2EVMOffRamp 1.5.0'); + + assertEq(ROUTER.typeAndVersion(), 'Router 1.2.0'); + assertEq(EXISTING_TOKEN_POOL.getRouter(), address(ROUTER)); + + assertEq(ROUTER.getOnRamp(ETH_CHAIN_SELECTOR), address(ON_RAMP)); + assertTrue(ROUTER.isOffRamp(ETH_CHAIN_SELECTOR, address(OFF_RAMP))); + } + + function _getTokenMessage( + CCIPSendParams memory params + ) internal returns (IClient.EVM2AnyMessage memory, IInternal.EVM2EVMMessage memory) { + IClient.EVM2AnyMessage memory message = CCIPUtils.generateMessage(params.sender, 1); + message.tokenAmounts[0] = IClient.EVMTokenAmount({ + token: AaveV3ArbitrumAssets.GHO_UNDERLYING, + amount: params.amount + }); + + uint256 feeAmount = ROUTER.getFee(ETH_CHAIN_SELECTOR, message); + deal(params.sender, feeAmount); + + IInternal.EVM2EVMMessage memory eventArg = CCIPUtils.messageToEvent( + CCIPUtils.MessageToEventParams({ + message: message, + router: ROUTER, + sourceChainSelector: ARB_CHAIN_SELECTOR, + feeTokenAmount: feeAmount, + originalSender: params.sender, + sourceToken: AaveV3ArbitrumAssets.GHO_UNDERLYING, + destinationToken: MiscEthereum.GHO_TOKEN, + poolVersion: params.poolVersion + }) + ); + + return (message, eventArg); + } + + function _getStaticParams(address tokenPool) internal view returns (bytes memory) { + IUpgradeableBurnMintTokenPool_1_4 ghoTokenPool = IUpgradeableBurnMintTokenPool_1_4(tokenPool); + return + abi.encode( + ghoTokenPool.getToken(), + ghoTokenPool.getAllowListEnabled(), + ghoTokenPool.getAllowList(), + ghoTokenPool.getRouter() + ); + } + + function _getDynamicParams(address tokenPool) internal view returns (bytes memory) { + IUpgradeableBurnMintTokenPool_1_4 ghoTokenPool = IUpgradeableBurnMintTokenPool_1_4(tokenPool); + return + abi.encode( + ghoTokenPool.owner(), + ghoTokenPool.getSupportedChains(), + ghoTokenPool.getAllowListEnabled(), + ghoTokenPool.getRateLimitAdmin() + ); + } + + function _tokenBucketToConfig( + IRateLimiter.TokenBucket memory bucket + ) internal pure returns (IRateLimiter.Config memory) { + return + IRateLimiter.Config({ + isEnabled: bucket.isEnabled, + capacity: bucket.capacity, + rate: bucket.rate + }); + } + + function _getDisabledConfig() internal pure returns (IRateLimiter.Config memory) { + return IRateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}); + } + + function _getImplementation(address proxy) internal view returns (address) { + bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); + return address(uint160(uint256(vm.load(proxy, slot)))); + } + + function _readInitialized(address proxy) internal view returns (uint8) { + return uint8(uint256(vm.load(proxy, bytes32(0)))); + } + + function assertEq( + IRateLimiter.TokenBucket memory bucket, + IRateLimiter.Config memory config + ) internal pure { + assertEq(abi.encode(_tokenBucketToConfig(bucket)), abi.encode(config)); + } +} + +contract AaveV3Arbitrum_GHOCCIP151Upgrade_20241209_SetupAndProposalActions is + AaveV3Arbitrum_GHOCCIP151Upgrade_20241209_Base +{ + function setUp() public override { + super.setUp(); + } + + function test_tokenPoolOwnershipTransfer() public { + assertFalse( + TOKEN_ADMIN_REGISTRY.isAdministrator(address(GHO), GovernanceV3Arbitrum.EXECUTOR_LVL_1) + ); + ITokenAdminRegistry.TokenConfig memory tokenConfig = TOKEN_ADMIN_REGISTRY.getTokenConfig( + address(GHO) + ); + assertNotEq(tokenConfig.administrator, GovernanceV3Arbitrum.EXECUTOR_LVL_1); + assertEq(tokenConfig.pendingAdministrator, GovernanceV3Arbitrum.EXECUTOR_LVL_1); + assertEq(tokenConfig.tokenPool, EXISTING_TOKEN_POOL.getProxyPool()); + + executePayload(vm, address(proposal)); + + tokenConfig = TOKEN_ADMIN_REGISTRY.getTokenConfig(address(GHO)); + assertEq(tokenConfig.administrator, GovernanceV3Arbitrum.EXECUTOR_LVL_1); + assertEq(tokenConfig.pendingAdministrator, address(0)); + assertEq(tokenConfig.tokenPool, address(NEW_TOKEN_POOL)); + } + + function test_tokenPoolLiquidityMigration() public { + IGhoToken.Facilitator memory existingFacilitator = GHO.getFacilitator( + address(EXISTING_TOKEN_POOL) + ); + IGhoToken.Facilitator memory newFacilitator = GHO.getFacilitator(address(NEW_TOKEN_POOL)); + + assertEq(bytes(newFacilitator.label).length, 0); + assertEq(newFacilitator.bucketCapacity, 0); + assertEq(newFacilitator.bucketLevel, 0); + + assertEq(existingFacilitator.label, 'CCIP TokenPool'); + assertGt(existingFacilitator.bucketCapacity, 0); + assertGt(existingFacilitator.bucketLevel, 0); + + executePayload(vm, address(proposal)); + + newFacilitator = GHO.getFacilitator(address(NEW_TOKEN_POOL)); + + assertEq(newFacilitator.label, 'CCIP v1.5.1 TokenPool'); + assertEq(newFacilitator.bucketCapacity, existingFacilitator.bucketCapacity); + assertEq(newFacilitator.bucketLevel, existingFacilitator.bucketLevel); + + existingFacilitator = GHO.getFacilitator(address(EXISTING_TOKEN_POOL)); + + assertEq(bytes(existingFacilitator.label).length, 0); + assertEq(existingFacilitator.bucketCapacity, 0); + assertEq(existingFacilitator.bucketLevel, 0); + } + + function test_newTokenPoolSetupAndRegistration() public { + bytes memory staticParams = _getStaticParams(address(EXISTING_TOKEN_POOL)); + bytes memory dynamicParams = _getDynamicParams(address(EXISTING_TOKEN_POOL)); + assertEq(EXISTING_TOKEN_POOL.getRateLimitAdmin(), GHO_CCIP_STEWARD); + + assertEq(TOKEN_ADMIN_REGISTRY.getPool(address(GHO)), EXISTING_TOKEN_POOL.getProxyPool()); + + executePayload(vm, address(proposal)); + + assertEq(staticParams, _getStaticParams(address(NEW_TOKEN_POOL))); + assertEq(dynamicParams, _getDynamicParams(address(NEW_TOKEN_POOL))); + assertEq(NEW_TOKEN_POOL.getRateLimitAdmin(), GHO_CCIP_STEWARD); + assertEq(NEW_TOKEN_POOL.getRemotePools(ETH_CHAIN_SELECTOR).length, 2); + assertTrue(NEW_TOKEN_POOL.isRemotePool(ETH_CHAIN_SELECTOR, abi.encode(ETH_PROXY_POOL))); + assertTrue(NEW_TOKEN_POOL.isRemotePool(ETH_CHAIN_SELECTOR, abi.encode(NEW_REMOTE_POOL_ETH))); + assertEq(NEW_TOKEN_POOL.getRemoteToken(ETH_CHAIN_SELECTOR), abi.encode(MiscEthereum.GHO_TOKEN)); + assertEq(NEW_TOKEN_POOL.getSupportedChains().length, 1); + assertTrue(NEW_TOKEN_POOL.isSupportedChain(ETH_CHAIN_SELECTOR)); + assertEq( + NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(ETH_CHAIN_SELECTOR), + _getDisabledConfig() + ); + assertEq( + NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(ETH_CHAIN_SELECTOR), + _getDisabledConfig() + ); + assertEq(NEW_TOKEN_POOL.getRateLimitAdmin(), GHO_CCIP_STEWARD); // sanity check + + assertEq(TOKEN_ADMIN_REGISTRY.getPool(address(GHO)), address(NEW_TOKEN_POOL)); + } + + function test_newTokenPoolInitialization() public { + vm.expectRevert('Initializable: contract is already initialized'); + NEW_TOKEN_POOL.initialize(makeAddr('owner'), new address[](0), makeAddr('router')); + assertEq(_readInitialized(address(NEW_TOKEN_POOL)), 1); + assertEq(_readInitialized(_getImplementation(address(NEW_TOKEN_POOL))), 255); + } +} + +contract AaveV3Arbitrum_GHOCCIP151Upgrade_20241209_PostUpgrade is + AaveV3Arbitrum_GHOCCIP151Upgrade_20241209_Base +{ + function setUp() public override { + super.setUp(); + + executePayload(vm, address(proposal)); + } + + function test_sendMessageSucceedsAndRoutesViaNewPool() public { + uint256 amount = 100_000e18; + + deal(address(GHO), alice, amount); + vm.prank(alice); + GHO.approve(address(ROUTER), amount); + + uint256 aliceBalance = GHO.balanceOf(alice); + uint256 bucketLevel = GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel; + + ( + IClient.EVM2AnyMessage memory message, + IInternal.EVM2EVMMessage memory eventArg + ) = _getTokenMessage( + CCIPSendParams({amount: amount, sender: alice, poolVersion: CCIPUtils.PoolVersion.V1_5_1}) + ); + + vm.expectEmit(address(NEW_TOKEN_POOL)); // new token pool + emit Burned(address(ON_RAMP), amount); + + vm.expectEmit(address(ON_RAMP)); + emit CCIPSendRequested(eventArg); + + vm.prank(alice); + ROUTER.ccipSend{value: eventArg.feeTokenAmount}(ETH_CHAIN_SELECTOR, message); + + assertEq(GHO.balanceOf(alice), aliceBalance - amount); + assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel - amount); + } + + // existing pool can no longer on ramp + function test_lockOrBurnRevertsOnExistingPool() public { + uint256 amount = 100_000e18; + + // prank router.gho.transferFrom(user, EXISTING_TOKEN_POOL, amount) + deal(address(GHO), address(EXISTING_TOKEN_POOL), amount); + + vm.prank(EXISTING_TOKEN_POOL.getProxyPool()); + // underflow expected at GHO.burn() => bucketLevel - amount + vm.expectRevert(stdError.arithmeticError); + EXISTING_TOKEN_POOL.lockOrBurn( + alice, + abi.encode(alice), + amount, + ETH_CHAIN_SELECTOR, + new bytes(0) + ); + } + + // on-ramp via new pool + function test_lockOrBurnSucceedsOnNewPool() public { + uint256 amount = 100_000e18; + + // prank router.gho.transferFrom(user, NEW_TOKEN_POOL, amount) + deal(address(GHO), address(NEW_TOKEN_POOL), amount); + + uint256 bucketLevel = GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel; + + vm.expectEmit(address(NEW_TOKEN_POOL)); + emit Burned(address(ON_RAMP), amount); + + vm.prank(address(ON_RAMP)); + NEW_TOKEN_POOL.lockOrBurn( + IPool_CCIP.LockOrBurnInV1({ + receiver: abi.encode(alice), + remoteChainSelector: ETH_CHAIN_SELECTOR, + originalSender: alice, + amount: amount, + localToken: address(GHO) + }) + ); + + assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel - amount); + assertEq(GHO.balanceOf(address(NEW_TOKEN_POOL)), 0); // dealt amount is burned + } + + // existing pool can no longer off ramp + function test_releaseOrMintRevertsOnExistingPool() public { + uint256 amount = 100_000e18; + + vm.prank(EXISTING_TOKEN_POOL.getProxyPool()); + vm.expectRevert('FACILITATOR_BUCKET_CAPACITY_EXCEEDED'); + EXISTING_TOKEN_POOL.releaseOrMint( + abi.encode(alice), + alice, + amount, + ETH_CHAIN_SELECTOR, + new bytes(0) + ); + } + + // off-ramp messages sent from new eth token pool (v1.5.1) + function test_releaseOrMintSucceedsOnNewPoolOffRampedViaNewTokenPoolEth() public { + uint256 amount = 100_000e18; + + uint256 bucketLevel = GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel; + uint256 aliceBalance = GHO.balanceOf(alice); + + vm.expectEmit(address(NEW_TOKEN_POOL)); + emit Minted(address(OFF_RAMP), alice, amount); + + vm.prank(address(OFF_RAMP)); + NEW_TOKEN_POOL.releaseOrMint( + IPool_CCIP.ReleaseOrMintInV1({ + originalSender: abi.encode(alice), + remoteChainSelector: ETH_CHAIN_SELECTOR, + receiver: alice, + amount: amount, + localToken: address(GHO), + sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ETH)), + sourcePoolData: new bytes(0), + offchainTokenData: new bytes(0) + }) + ); + + assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel + amount); + assertEq(GHO.balanceOf(alice), aliceBalance + amount); + } + + // off-ramp messages sent from existing eth token pool (v1.4) ie ProxyPool + function test_releaseOrMintSucceedsOnNewPoolOffRampedViaExistingTokenPoolEth() public { + uint256 amount = 100_000e18; + + uint256 bucketLevel = GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel; + uint256 aliceBalance = GHO.balanceOf(alice); + + vm.expectEmit(address(NEW_TOKEN_POOL)); + emit Minted(address(OFF_RAMP), alice, amount); + + vm.prank(address(OFF_RAMP)); + NEW_TOKEN_POOL.releaseOrMint( + IPool_CCIP.ReleaseOrMintInV1({ + originalSender: abi.encode(alice), + remoteChainSelector: ETH_CHAIN_SELECTOR, + receiver: alice, + amount: amount, + localToken: address(GHO), + sourcePoolAddress: abi.encode(address(ETH_PROXY_POOL)), + sourcePoolData: new bytes(0), + offchainTokenData: new bytes(0) + }) + ); + + assertEq(GHO.getFacilitator(address(NEW_TOKEN_POOL)).bucketLevel, bucketLevel + amount); + assertEq(GHO.balanceOf(alice), aliceBalance + amount); + } } diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol new file mode 100644 index 000000000..e1d42aa6c --- /dev/null +++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol @@ -0,0 +1,627 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; + +import {IClient} from 'src/interfaces/ccip/IClient.sol'; +import {IInternal} from 'src/interfaces/ccip/IInternal.sol'; +import {IRouter} from 'src/interfaces/ccip/IRouter.sol'; +import {IEVM2EVMOnRamp} from 'src/interfaces/ccip/IEVM2EVMOnRamp.sol'; +import {IEVM2EVMOffRamp_1_5} from 'src/interfaces/ccip/IEVM2EVMOffRamp.sol'; +import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol'; +import {IGhoToken} from 'src/interfaces/IGhoToken.sol'; +import {IUpgradeableLockReleaseTokenPool_1_4, IUpgradeableLockReleaseTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol'; +import {IUpgradeableBurnMintTokenPool_1_4, IUpgradeableBurnMintTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol'; + +import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; +import {GovernanceV3Arbitrum} from 'aave-address-book/GovernanceV3Arbitrum.sol'; + +import {ProtocolV3TestBase} from 'aave-helpers/src/ProtocolV3TestBase.sol'; + +import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol'; +import {UpgradeableLockReleaseTokenPool} from 'aave-ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol'; +import {UpgradeableBurnMintTokenPool} from 'aave-ccip/pools/GHO/UpgradeableBurnMintTokenPool.sol'; + +import {CCIPUtils} from './utils/CCIPUtils.sol'; +import {AaveV3Ethereum_GHOCCIP151Upgrade_20241209} from './AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol'; +import {AaveV3Arbitrum_GHOCCIP151Upgrade_20241209} from './AaveV3Arbitrum_GHOCCIP151Upgrade_20241209.sol'; + +/** + * @dev Test for AaveV3E2E_GHOCCIP151Upgrade_20241209 + * command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20241209_Multi_GHOCCIP151Upgrade/AaveV3E2E_GHOCCIP151Upgrade_20241209.t.sol -vv + */ +contract AaveV3E2E_GHOCCIP151Upgrade_20241209_Base is ProtocolV3TestBase { + struct CCIPSendParams { + IRouter router; + IGhoToken token; + uint256 amount; + uint64 sourceChainSelector; + uint64 destinationChainSelector; + address sender; + CCIPUtils.PoolVersion poolVersion; + } + + struct Common { + IRouter router; + IGhoToken token; + IEVM2EVMOnRamp EVM2EVMOnRamp; + IEVM2EVMOffRamp_1_5 EVM2EVMOffRamp; + ITokenAdminRegistry tokenAdminRegistry; + address proxyPool; + uint64 chainSelector; + uint256 forkId; + } + + struct L1 { + AaveV3Ethereum_GHOCCIP151Upgrade_20241209 proposal; + IUpgradeableLockReleaseTokenPool_1_5_1 newTokenPool; + IUpgradeableLockReleaseTokenPool_1_4 existingTokenPool; + Common c; + } + + struct L2 { + AaveV3Arbitrum_GHOCCIP151Upgrade_20241209 proposal; + IUpgradeableBurnMintTokenPool_1_5_1 newTokenPool; + IUpgradeableBurnMintTokenPool_1_4 existingTokenPool; + Common c; + } + + L1 internal l1; + L2 internal l2; + + address internal alice = makeAddr('alice'); + address internal bob = makeAddr('bob'); + address internal carol = makeAddr('carol'); + + event CCIPSendRequested(IInternal.EVM2EVMMessage message); + event Locked(address indexed sender, uint256 amount); + event Burned(address indexed sender, uint256 amount); + event Released(address indexed sender, address indexed recipient, uint256 amount); + event Minted(address indexed sender, address indexed recipient, uint256 amount); + + function setUp() public virtual { + l1.c.forkId = vm.createFork(vm.rpcUrl('mainnet'), 21366260); + l2.c.forkId = vm.createFork(vm.rpcUrl('arbitrum'), 283036001); + + vm.selectFork(l1.c.forkId); + address newTokenPoolEth = _deployNewTokenPoolEth(); + vm.selectFork(l2.c.forkId); + address newTokenPoolArb = _deployNewTokenPoolArb(); + + vm.selectFork(l1.c.forkId); + l1.proposal = new AaveV3Ethereum_GHOCCIP151Upgrade_20241209(newTokenPoolEth, newTokenPoolArb); + l1.existingTokenPool = IUpgradeableLockReleaseTokenPool_1_4(MiscEthereum.GHO_CCIP_TOKEN_POOL); + l1.newTokenPool = IUpgradeableLockReleaseTokenPool_1_5_1(newTokenPoolEth); + l1.c.router = IRouter(l1.existingTokenPool.getRouter()); + l2.c.chainSelector = l1.existingTokenPool.getSupportedChains()[0]; + l1.c.token = IGhoToken(address(l1.existingTokenPool.getToken())); + l1.c.EVM2EVMOnRamp = IEVM2EVMOnRamp(l1.c.router.getOnRamp(l2.c.chainSelector)); + l1.c.EVM2EVMOffRamp = IEVM2EVMOffRamp_1_5(0xdf615eF8D4C64d0ED8Fd7824BBEd2f6a10245aC9); // new offramp + l1.c.tokenAdminRegistry = ITokenAdminRegistry(0xb22764f98dD05c789929716D677382Df22C05Cb6); + l1.c.proxyPool = l1.existingTokenPool.getProxyPool(); + + vm.selectFork(l2.c.forkId); + l2.proposal = new AaveV3Arbitrum_GHOCCIP151Upgrade_20241209(newTokenPoolArb, newTokenPoolEth); + l2.existingTokenPool = IUpgradeableBurnMintTokenPool_1_4(MiscArbitrum.GHO_CCIP_TOKEN_POOL); + l2.newTokenPool = IUpgradeableBurnMintTokenPool_1_5_1(newTokenPoolArb); + l2.c.router = IRouter(l2.existingTokenPool.getRouter()); + l1.c.chainSelector = l2.existingTokenPool.getSupportedChains()[0]; + l2.c.token = IGhoToken(address(l2.existingTokenPool.getToken())); + l2.c.EVM2EVMOnRamp = IEVM2EVMOnRamp(l2.c.router.getOnRamp(l1.c.chainSelector)); + l2.c.EVM2EVMOffRamp = IEVM2EVMOffRamp_1_5(0x91e46cc5590A4B9182e47f40006140A7077Dec31); // new offramp + l2.c.tokenAdminRegistry = ITokenAdminRegistry(0x39AE1032cF4B334a1Ed41cdD0833bdD7c7E7751E); + l2.c.proxyPool = l2.existingTokenPool.getProxyPool(); + + _validateConfig({upgraded: false}); + + _performPoolTransferCLLPreReq(); // rm once CLL performs this action + } + + function _getTokenMessage( + CCIPSendParams memory params + ) internal returns (IClient.EVM2AnyMessage memory, IInternal.EVM2EVMMessage memory) { + IClient.EVM2AnyMessage memory message = CCIPUtils.generateMessage(params.sender, 1); + message.tokenAmounts[0] = IClient.EVMTokenAmount({ + token: address(params.token), + amount: params.amount + }); + + uint256 feeAmount = params.router.getFee(params.destinationChainSelector, message); + deal(params.sender, feeAmount); + + IInternal.EVM2EVMMessage memory eventArg = CCIPUtils.messageToEvent( + CCIPUtils.MessageToEventParams({ + message: message, + router: params.router, + sourceChainSelector: params.sourceChainSelector, + feeTokenAmount: feeAmount, + originalSender: params.sender, + sourceToken: address(params.token), + destinationToken: address(params.token == l1.c.token ? l2.c.token : l1.c.token), + poolVersion: params.poolVersion + }) + ); + + return (message, eventArg); + } + + function _validateConfig(bool upgraded) internal { + vm.selectFork(l1.c.forkId); + assertEq(l1.c.chainSelector, 5009297550715157269); + assertEq(address(l1.c.token), MiscEthereum.GHO_TOKEN); + assertEq(l1.c.router.typeAndVersion(), 'Router 1.2.0'); + assertEq(l1.c.EVM2EVMOnRamp.typeAndVersion(), 'EVM2EVMOnRamp 1.5.0'); + assertEq(l1.c.EVM2EVMOffRamp.typeAndVersion(), 'EVM2EVMOffRamp 1.5.0'); + + assertEq(l1.existingTokenPool.typeAndVersion(), 'LockReleaseTokenPool 1.4.0'); + assertEq(l1.newTokenPool.typeAndVersion(), 'LockReleaseTokenPool 1.5.1'); + + assertEq(l1.c.tokenAdminRegistry.typeAndVersion(), 'TokenAdminRegistry 1.5.0'); + assertTrue(l1.c.router.isOffRamp(l2.c.chainSelector, address(l1.c.EVM2EVMOffRamp))); + assertEq(l1.c.router.getOnRamp(l2.c.chainSelector), address(l1.c.EVM2EVMOnRamp)); + + // proposal constants + assertEq(address(l1.proposal.TOKEN_ADMIN_REGISTRY()), address(l1.c.tokenAdminRegistry)); + assertEq(l1.proposal.ARB_CHAIN_SELECTOR(), l2.c.chainSelector); + assertEq(address(l1.proposal.EXISTING_TOKEN_POOL()), address(l1.existingTokenPool)); + assertEq(address(l1.proposal.EXISTING_REMOTE_POOL_ARB()), l2.c.proxyPool); + assertEq(address(l1.proposal.NEW_TOKEN_POOL()), address(l1.newTokenPool)); + assertEq(address(l1.proposal.NEW_REMOTE_POOL_ARB()), address(l2.newTokenPool)); + + if (upgraded) { + assertEq(l1.c.tokenAdminRegistry.getPool(MiscEthereum.GHO_TOKEN), address(l1.newTokenPool)); + + assertEq(l1.c.token.balanceOf(address(l1.existingTokenPool)), 0); + // ! todo upgrade existing pool to reset bridgedAmount? not necessary since we reset bridgeLimit + // assertEq(l1.existingTokenPool.getCurrentBridgedAmount(), 0); + + assertGt(l1.c.token.balanceOf(address(l1.newTokenPool)), 0); + assertGt(l1.newTokenPool.getCurrentBridgedAmount(), 0); + } else { + assertEq(l1.c.tokenAdminRegistry.getPool(MiscEthereum.GHO_TOKEN), l1.c.proxyPool); + + assertGt(l1.c.token.balanceOf(address(l1.existingTokenPool)), 0); + assertGt(l1.existingTokenPool.getCurrentBridgedAmount(), 0); + + assertEq(l1.c.token.balanceOf(address(l1.newTokenPool)), 0); + assertEq(l1.newTokenPool.getCurrentBridgedAmount(), 0); + } + + vm.selectFork(l2.c.forkId); + assertEq(l2.c.chainSelector, 4949039107694359620); + assertEq(address(l2.c.token), 0x7dfF72693f6A4149b17e7C6314655f6A9F7c8B33); + assertEq(l2.c.router.typeAndVersion(), 'Router 1.2.0'); + assertEq(l2.c.EVM2EVMOnRamp.typeAndVersion(), 'EVM2EVMOnRamp 1.5.0'); + assertEq(l2.c.EVM2EVMOffRamp.typeAndVersion(), 'EVM2EVMOffRamp 1.5.0'); + assertEq(l2.existingTokenPool.typeAndVersion(), 'BurnMintTokenPool 1.4.0'); + assertEq(l2.newTokenPool.typeAndVersion(), 'BurnMintTokenPool 1.5.1'); + assertEq(l2.c.tokenAdminRegistry.typeAndVersion(), 'TokenAdminRegistry 1.5.0'); + assertTrue(l2.c.router.isOffRamp(l1.c.chainSelector, address(l2.c.EVM2EVMOffRamp))); + + assertEq(l2.c.router.getOnRamp(l1.c.chainSelector), address(l2.c.EVM2EVMOnRamp)); + + // proposal constants + assertEq(address(l2.proposal.TOKEN_ADMIN_REGISTRY()), address(l2.c.tokenAdminRegistry)); + assertEq(l2.proposal.ETH_CHAIN_SELECTOR(), l1.c.chainSelector); + assertEq(address(l2.proposal.EXISTING_TOKEN_POOL()), address(l2.existingTokenPool)); + assertEq(address(l2.proposal.EXISTING_REMOTE_POOL_ETH()), l1.c.proxyPool); + assertEq(address(l2.proposal.NEW_TOKEN_POOL()), address(l2.newTokenPool)); + assertEq(address(l2.proposal.NEW_REMOTE_POOL_ETH()), address(l1.newTokenPool)); + + if (upgraded) { + assertEq( + l2.c.tokenAdminRegistry.getPool(AaveV3ArbitrumAssets.GHO_UNDERLYING), + address(l2.newTokenPool) + ); + assertEq(bytes(l2.c.token.getFacilitator(address(l2.existingTokenPool)).label).length, 0); + assertEq(l2.c.token.getFacilitator(address(l2.newTokenPool)).label, 'CCIP v1.5.1 TokenPool'); + } else { + assertEq( + l2.c.tokenAdminRegistry.getPool(AaveV3ArbitrumAssets.GHO_UNDERLYING), + l2.c.proxyPool + ); + assertEq(l2.c.token.getFacilitator(address(l2.existingTokenPool)).label, 'CCIP TokenPool'); + assertEq(bytes(l2.c.token.getFacilitator(address(l2.newTokenPool)).label).length, 0); + } + } + + function _executeUpgradeAIP() internal { + vm.selectFork(l1.c.forkId); + executePayload(vm, address(l1.proposal)); + vm.selectFork(l2.c.forkId); + executePayload(vm, address(l2.proposal)); + } + + function _performPoolTransferCLLPreReq() private { + vm.selectFork(l1.c.forkId); + vm.prank(l1.c.tokenAdminRegistry.owner()); + l1.c.tokenAdminRegistry.transferAdminRole( + MiscEthereum.GHO_TOKEN, + GovernanceV3Ethereum.EXECUTOR_LVL_1 + ); + + vm.selectFork(l2.c.forkId); + vm.prank(l2.c.tokenAdminRegistry.owner()); + l2.c.tokenAdminRegistry.transferAdminRole( + AaveV3ArbitrumAssets.GHO_UNDERLYING, + GovernanceV3Arbitrum.EXECUTOR_LVL_1 + ); + } + + function _deployNewTokenPoolEth() private returns (address) { + IUpgradeableLockReleaseTokenPool_1_4 existingTokenPool = IUpgradeableLockReleaseTokenPool_1_4( + MiscEthereum.GHO_CCIP_TOKEN_POOL + ); + address newTokenPoolImpl = address( + new UpgradeableLockReleaseTokenPool( + existingTokenPool.getToken(), + IGhoToken(existingTokenPool.getToken()).decimals(), + existingTokenPool.getArmProxy(), + existingTokenPool.getAllowListEnabled(), + existingTokenPool.canAcceptLiquidity() + ) + ); + + return + address( + new TransparentUpgradeableProxy( + newTokenPoolImpl, + MiscEthereum.PROXY_ADMIN, + abi.encodeCall( + IUpgradeableLockReleaseTokenPool_1_5_1.initialize, + ( + GovernanceV3Ethereum.EXECUTOR_LVL_1, // owner + existingTokenPool.getAllowList(), + existingTokenPool.getRouter(), + existingTokenPool.getBridgeLimit() + ) + ) + ) + ); + } + + function _deployNewTokenPoolArb() private returns (address) { + IUpgradeableBurnMintTokenPool_1_4 existingTokenPool = IUpgradeableBurnMintTokenPool_1_4( + MiscArbitrum.GHO_CCIP_TOKEN_POOL + ); + address newTokenPoolImpl = address( + new UpgradeableBurnMintTokenPool( + existingTokenPool.getToken(), + IGhoToken(existingTokenPool.getToken()).decimals(), + existingTokenPool.getArmProxy(), + existingTokenPool.getAllowListEnabled() + ) + ); + return + address( + new TransparentUpgradeableProxy( + newTokenPoolImpl, + address(MiscArbitrum.PROXY_ADMIN), + abi.encodeCall( + IUpgradeableBurnMintTokenPool_1_5_1.initialize, + ( + GovernanceV3Arbitrum.EXECUTOR_LVL_1, // owner + existingTokenPool.getAllowList(), + existingTokenPool.getRouter() + ) + ) + ) + ); + } + + // post upgrade + function _runEthToArb(address user, uint256 amount) internal { + vm.selectFork(l1.c.forkId); + + vm.prank(user); + l1.c.token.approve(address(l1.c.router), amount); + + uint256 tokenPoolBalance = l1.c.token.balanceOf(address(l1.newTokenPool)); + uint256 userBalance = l1.c.token.balanceOf(user); + uint256 bridgedAmount = l1.newTokenPool.getCurrentBridgedAmount(); + + ( + IClient.EVM2AnyMessage memory message, + IInternal.EVM2EVMMessage memory eventArg + ) = _getTokenMessage( + CCIPSendParams({ + router: l1.c.router, + token: l1.c.token, + amount: amount, + sourceChainSelector: l1.c.chainSelector, + destinationChainSelector: l2.c.chainSelector, + sender: user, + poolVersion: CCIPUtils.PoolVersion.V1_5_1 + }) + ); + + vm.expectEmit(address(l1.newTokenPool)); + emit Locked(address(l1.c.EVM2EVMOnRamp), amount); + + vm.expectEmit(address(l1.c.EVM2EVMOnRamp)); + emit CCIPSendRequested(eventArg); + vm.prank(user); + l1.c.router.ccipSend{value: eventArg.feeTokenAmount}(l2.c.chainSelector, message); + + assertEq(l1.c.token.balanceOf(address(l1.newTokenPool)), tokenPoolBalance + amount); + assertEq(l1.c.token.balanceOf(user), userBalance - amount); + assertEq(l1.newTokenPool.getCurrentBridgedAmount(), bridgedAmount + amount); + + // ARB executeMessage + vm.selectFork(l2.c.forkId); + + userBalance = l2.c.token.balanceOf(user); + uint256 bucketLevel = l2.c.token.getFacilitator(address(l2.newTokenPool)).bucketLevel; + + vm.expectEmit(address(l2.newTokenPool)); + emit Minted(address(l2.c.EVM2EVMOffRamp), user, amount); + vm.prank(address(l2.c.EVM2EVMOffRamp)); + l2.c.EVM2EVMOffRamp.executeSingleMessage( + eventArg, + new bytes[](message.tokenAmounts.length), + new uint32[](0) + ); + + assertEq(l2.c.token.balanceOf(user), userBalance + amount); + assertEq(l2.c.token.getFacilitator(address(l2.newTokenPool)).bucketLevel, bucketLevel + amount); + } + + // post upgrade + function _runArbToEth(address user, uint256 amount) internal { + vm.selectFork(l2.c.forkId); + + vm.prank(user); + l2.c.token.approve(address(l2.c.router), amount); + + uint256 userBalance = l2.c.token.balanceOf(user); + uint256 bucketLevel = l2.c.token.getFacilitator(address(l2.newTokenPool)).bucketLevel; + + ( + IClient.EVM2AnyMessage memory message, + IInternal.EVM2EVMMessage memory eventArg + ) = _getTokenMessage( + CCIPSendParams({ + router: l2.c.router, + token: l2.c.token, + amount: amount, + sourceChainSelector: l2.c.chainSelector, + destinationChainSelector: l1.c.chainSelector, + sender: user, + poolVersion: CCIPUtils.PoolVersion.V1_5_1 + }) + ); + + vm.expectEmit(address(l2.newTokenPool)); + emit Burned(address(l2.c.EVM2EVMOnRamp), amount); + + vm.expectEmit(address(l2.c.EVM2EVMOnRamp)); + emit CCIPSendRequested(eventArg); + vm.prank(user); + l2.c.router.ccipSend{value: eventArg.feeTokenAmount}(l1.c.chainSelector, message); + + assertEq(l2.c.token.balanceOf(user), userBalance - amount); + assertEq(l2.c.token.getFacilitator(address(l2.newTokenPool)).bucketLevel, bucketLevel - amount); + + // ETH executeMessage + vm.selectFork(l1.c.forkId); + + uint256 tokenPoolBalance = l1.c.token.balanceOf(address(l1.newTokenPool)); + uint256 bridgedAmount = l1.newTokenPool.getCurrentBridgedAmount(); + userBalance = l1.c.token.balanceOf(user); + + vm.expectEmit(address(l1.newTokenPool)); + emit Released(address(l1.c.EVM2EVMOffRamp), user, amount); + vm.prank(address(l1.c.EVM2EVMOffRamp)); + l1.c.EVM2EVMOffRamp.executeSingleMessage( + eventArg, + new bytes[](message.tokenAmounts.length), + new uint32[](1) // tokenGasOverrides + ); + + assertEq(l1.c.token.balanceOf(address(l1.newTokenPool)), tokenPoolBalance - amount); + assertEq(l1.newTokenPool.getCurrentBridgedAmount(), bridgedAmount - amount); + assertEq(l1.c.token.balanceOf(user), userBalance + amount); + } +} + +contract AaveV3E2E_GHOCCIP151Upgrade_20241209_PostUpgrade is + AaveV3E2E_GHOCCIP151Upgrade_20241209_Base +{ + function setUp() public override { + super.setUp(); + + _executeUpgradeAIP(); + + _validateConfig({upgraded: true}); + } + + function test_E2E_FromEth(uint256 amount) public { + vm.selectFork(l1.c.forkId); + uint256 currentBridgedAmount = l1.newTokenPool.getCurrentBridgedAmount(); + + amount = bound(amount, 1, currentBridgedAmount); + deal(address(l1.c.token), alice, amount); + + _runEthToArb(alice, amount); + _runArbToEth(alice, amount); + } + + function test_E2E_FromArb(uint256 amount) public { + vm.selectFork(l2.c.forkId); + uint256 currentBridgedAmount = l2.c.token.getFacilitator(address(l2.newTokenPool)).bucketLevel; + + amount = bound(amount, 1, currentBridgedAmount); + deal(address(l2.c.token), alice, amount); + + _runArbToEth(alice, amount); + _runEthToArb(alice, amount); + } + + function test_E2E_Multiple() public { + vm.selectFork(l1.c.forkId); + uint256 currentBridgedAmount = l1.newTokenPool.getCurrentBridgedAmount(); + uint256 amount = currentBridgedAmount / 3; + + deal(address(l1.c.token), alice, amount); + deal(address(l1.c.token), bob, amount); + + vm.selectFork(l2.c.forkId); + deal(address(l2.c.token), carol, amount); + + _runEthToArb(alice, amount); + _runEthToArb(bob, amount); + _runArbToEth(alice, amount); + _runArbToEth(carol, amount); + _runEthToArb(carol, amount); + _runArbToEth(bob, amount); + } +} + +// sendMsg => upgrade => executeMsg +contract AaveV3E2E_GHOCCIP151Upgrade_20241209_InFlightUpgrade is + AaveV3E2E_GHOCCIP151Upgrade_20241209_Base +{ + function setUp() public override { + super.setUp(); + } + + function test_E2E_InFlightMsg_FromEth() public { + vm.selectFork(l1.c.forkId); + + uint256 amount = 100_000e18; + deal(address(l1.c.token), alice, amount); + + vm.prank(alice); + l1.c.token.approve(address(l1.c.router), amount); + + uint256 tokenPoolBalance = l1.c.token.balanceOf(address(l1.existingTokenPool)); + uint256 aliceBalance = l1.c.token.balanceOf(alice); + uint256 bridgedAmount = l1.existingTokenPool.getCurrentBridgedAmount(); + + ( + IClient.EVM2AnyMessage memory message, + IInternal.EVM2EVMMessage memory eventArg + ) = _getTokenMessage( + CCIPSendParams({ + router: l1.c.router, + token: l1.c.token, + amount: amount, + sourceChainSelector: l1.c.chainSelector, + destinationChainSelector: l2.c.chainSelector, + sender: alice, + poolVersion: CCIPUtils.PoolVersion.V1_5_0 // existing token pool + }) + ); + + // message sent from existing token pool, pre-AIP-execution + vm.expectEmit(address(l1.existingTokenPool)); + emit Locked(l1.c.proxyPool, amount); + vm.expectEmit(l1.c.proxyPool); + emit Locked(address(l1.c.EVM2EVMOnRamp), amount); + + vm.expectEmit(address(l1.c.EVM2EVMOnRamp)); + emit CCIPSendRequested(eventArg); + vm.prank(alice); + l1.c.router.ccipSend{value: eventArg.feeTokenAmount}(l2.c.chainSelector, message); + + assertEq(l1.c.token.balanceOf(address(l1.existingTokenPool)), tokenPoolBalance + amount); + assertEq(l1.c.token.balanceOf(alice), aliceBalance - amount); + assertEq(l1.existingTokenPool.getCurrentBridgedAmount(), bridgedAmount + amount); + + _executeUpgradeAIP(); // token pools upgraded + + // ARB executeMessage + vm.selectFork(l2.c.forkId); + + aliceBalance = l2.c.token.balanceOf(alice); + uint256 bucketLevel = l2.c.token.getFacilitator(address(l2.newTokenPool)).bucketLevel; + + vm.expectEmit(address(l2.newTokenPool)); + emit Minted(address(l2.c.EVM2EVMOffRamp), alice, amount); + vm.prank(address(l2.c.EVM2EVMOffRamp)); + l2.c.EVM2EVMOffRamp.executeSingleMessage( + eventArg, // pre-upgrade message + new bytes[](message.tokenAmounts.length), + new uint32[](0) + ); + + assertEq(l2.c.token.balanceOf(alice), aliceBalance + amount); + assertEq(l2.c.token.getFacilitator(address(l2.newTokenPool)).bucketLevel, bucketLevel + amount); + + // send tokens back to eth + _runArbToEth(alice, amount); + } + + function test_E2E_InFlightMsg_FromArb() public { + vm.selectFork(l2.c.forkId); + + uint256 amount = 100_000e18; + deal(address(l2.c.token), alice, amount); + + vm.prank(alice); + l2.c.token.approve(address(l2.c.router), amount); + + uint256 aliceBalance = l2.c.token.balanceOf(alice); + uint256 bucketLevel = l2.c.token.getFacilitator(address(l2.existingTokenPool)).bucketLevel; + + ( + IClient.EVM2AnyMessage memory message, + IInternal.EVM2EVMMessage memory eventArg + ) = _getTokenMessage( + CCIPSendParams({ + router: l2.c.router, + token: l2.c.token, + amount: amount, + sourceChainSelector: l2.c.chainSelector, + destinationChainSelector: l1.c.chainSelector, + sender: alice, + poolVersion: CCIPUtils.PoolVersion.V1_5_0 // existing token pool + }) + ); + + // message sent from existing token pool, pre-AIP-execution + vm.expectEmit(address(l2.existingTokenPool)); + emit Burned(l2.c.proxyPool, amount); + vm.expectEmit(l2.c.proxyPool); + emit Burned(address(l2.c.EVM2EVMOnRamp), amount); + + vm.expectEmit(address(l2.c.EVM2EVMOnRamp)); + emit CCIPSendRequested(eventArg); + vm.prank(alice); + l2.c.router.ccipSend{value: eventArg.feeTokenAmount}(l1.c.chainSelector, message); + + assertEq(l2.c.token.balanceOf(alice), aliceBalance - amount); + assertEq( + l2.c.token.getFacilitator(address(l2.existingTokenPool)).bucketLevel, + bucketLevel - amount + ); + + _executeUpgradeAIP(); // token pools upgraded + + // ETH executeMessage + vm.selectFork(l1.c.forkId); + + uint256 tokenPoolBalance = l1.c.token.balanceOf(address(l1.newTokenPool)); + uint256 bridgedAmount = l1.newTokenPool.getCurrentBridgedAmount(); + aliceBalance = l1.c.token.balanceOf(alice); + + vm.expectEmit(address(l1.newTokenPool)); + emit Released(address(l1.c.EVM2EVMOffRamp), alice, amount); + vm.prank(address(l1.c.EVM2EVMOffRamp)); + l1.c.EVM2EVMOffRamp.executeSingleMessage( + eventArg, // pre-upgrade message + new bytes[](message.tokenAmounts.length), + new uint32[](1) // tokenGasOverrides + ); + + assertEq(l1.c.token.balanceOf(address(l1.newTokenPool)), tokenPoolBalance - amount); + assertEq(l1.newTokenPool.getCurrentBridgedAmount(), bridgedAmount - amount); + assertEq(l1.c.token.balanceOf(alice), aliceBalance + amount); + + // send tokens back to arb + _runEthToArb(alice, amount); + } +} diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol index 4ffc72b93..1385fb8b4 100644 --- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol +++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol @@ -2,6 +2,12 @@ pragma solidity ^0.8.0; import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGenericExecutor.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol'; +import {IUpgradeableLockReleaseTokenPool_1_4, IUpgradeableLockReleaseTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol'; +import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol'; + /** * @title GHO CCIP 1.5.1 Upgrade * @author Aave Labs @@ -9,9 +15,76 @@ import {IProposalGenericExecutor} from 'aave-helpers/src/interfaces/IProposalGen * - Discussion: TODO */ contract AaveV3Ethereum_GHOCCIP151Upgrade_20241209 is IProposalGenericExecutor { - uint64 internal constant ARB_CHAIN_SELECTOR = 4949039107694359620; + uint64 public constant ARB_CHAIN_SELECTOR = 4949039107694359620; + + // https://etherscan.io/address/0xb22764f98dD05c789929716D677382Df22C05Cb6 + ITokenAdminRegistry public constant TOKEN_ADMIN_REGISTRY = + ITokenAdminRegistry(0xb22764f98dD05c789929716D677382Df22C05Cb6); + + // https://etherscan.io/address/0x5756880B6a1EAba0175227bf02a7E87c1e02B28C + IUpgradeableLockReleaseTokenPool_1_4 public constant EXISTING_TOKEN_POOL = + IUpgradeableLockReleaseTokenPool_1_4(0x5756880B6a1EAba0175227bf02a7E87c1e02B28C); // MiscEthereum.GHO_CCIP_TOKEN_POOL; -> not using since this will be overwritten with new token pool address + IUpgradeableLockReleaseTokenPool_1_5_1 public immutable NEW_TOKEN_POOL; + + // https://arbiscan.io/address/0x26329558f08cbb40d6a4CCA0E0C67b29D64A8c50 + address public constant EXISTING_REMOTE_POOL_ARB = 0x26329558f08cbb40d6a4CCA0E0C67b29D64A8c50; // ProxyPool on Arb + address public immutable NEW_REMOTE_POOL_ARB; + + constructor(address newTokenPoolEth, address newTokenPoolArb) { + NEW_TOKEN_POOL = IUpgradeableLockReleaseTokenPool_1_5_1(newTokenPoolEth); + NEW_REMOTE_POOL_ARB = newTokenPoolArb; + } function execute() external { - // custom code goes here + _acceptOwnership(); + _migrateLiquidity(); + _setupAndRegisterNewPool(); + } + + // pre-req - chainlink transfers gho token pool ownership on token admin registry + function _acceptOwnership() internal { + NEW_TOKEN_POOL.acceptOwnership(); + TOKEN_ADMIN_REGISTRY.acceptAdminRole(MiscEthereum.GHO_TOKEN); + } + + function _migrateLiquidity() internal { + EXISTING_TOKEN_POOL.setRebalancer(address(NEW_TOKEN_POOL)); + NEW_TOKEN_POOL.transferLiquidity({ + from: address(EXISTING_TOKEN_POOL), + amount: EXISTING_TOKEN_POOL.getCurrentBridgedAmount() + }); + + // disable existing pool + EXISTING_TOKEN_POOL.setBridgeLimit(0); + } + + function _setupAndRegisterNewPool() internal { + bytes[] memory remotePoolAddresses = new bytes[](2); + remotePoolAddresses[0] = abi.encode(EXISTING_REMOTE_POOL_ARB); + remotePoolAddresses[1] = abi.encode(NEW_REMOTE_POOL_ARB); + IRateLimiter.Config memory emptyRateLimiterConfig = IRateLimiter.Config({ + isEnabled: false, + capacity: 0, + rate: 0 + }); + + IUpgradeableLockReleaseTokenPool_1_5_1.ChainUpdate[] + memory chains = new IUpgradeableLockReleaseTokenPool_1_5_1.ChainUpdate[](1); + + chains[0] = IUpgradeableLockReleaseTokenPool_1_5_1.ChainUpdate({ + remoteChainSelector: ARB_CHAIN_SELECTOR, + remotePoolAddresses: remotePoolAddresses, + remoteTokenAddress: abi.encode(AaveV3ArbitrumAssets.GHO_UNDERLYING), + outboundRateLimiterConfig: emptyRateLimiterConfig, + inboundRateLimiterConfig: emptyRateLimiterConfig + }); + + // setup new pool + NEW_TOKEN_POOL.applyChainUpdates(new uint64[](0), chains); + NEW_TOKEN_POOL.setRateLimitAdmin(EXISTING_TOKEN_POOL.getRateLimitAdmin()); // GhoCcipSteward + NEW_TOKEN_POOL.setBridgeLimitAdmin(EXISTING_TOKEN_POOL.getBridgeLimitAdmin()); // GhoCcipSteward + + // register new pool + TOKEN_ADMIN_REGISTRY.setPool(MiscEthereum.GHO_TOKEN, address(NEW_TOKEN_POOL)); } } diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol index 4e5857fe4..342a80551 100644 --- a/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol +++ b/src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol @@ -1,32 +1,512 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import 'forge-std/Test.sol'; + +import {IGhoToken} from 'src/interfaces/IGhoToken.sol'; +import {IClient} from 'src/interfaces/ccip/IClient.sol'; +import {IInternal} from 'src/interfaces/ccip/IInternal.sol'; +import {IRouter} from 'src/interfaces/ccip/IRouter.sol'; +import {IRateLimiter} from 'src/interfaces/ccip/IRateLimiter.sol'; +import {IEVM2EVMOnRamp} from 'src/interfaces/ccip/IEVM2EVMOnRamp.sol'; +import {ITypeAndVersion} from 'src/interfaces/ccip/ITypeAndVersion.sol'; +import {IEVM2EVMOffRamp_1_5} from 'src/interfaces/ccip/IEVM2EVMOffRamp.sol'; +import {ITokenAdminRegistry} from 'src/interfaces/ccip/ITokenAdminRegistry.sol'; +import {IPool as IPool_CCIP} from 'src/interfaces/ccip/tokenPool/IPool.sol'; +import {IUpgradeableLockReleaseTokenPool_1_4, IUpgradeableLockReleaseTokenPool_1_5_1} from 'src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol'; + +import {ProtocolV3TestBase} from 'aave-helpers/src/ProtocolV3TestBase.sol'; import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; +import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; -import 'forge-std/Test.sol'; -import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/src/ProtocolV3TestBase.sol'; +import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent-proxy/TransparentUpgradeableProxy.sol'; +import {UpgradeableLockReleaseTokenPool} from 'aave-ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol'; + +import {CCIPUtils} from './utils/CCIPUtils.sol'; import {AaveV3Ethereum_GHOCCIP151Upgrade_20241209} from './AaveV3Ethereum_GHOCCIP151Upgrade_20241209.sol'; /** * @dev Test for AaveV3Ethereum_GHOCCIP151Upgrade_20241209 * command: FOUNDRY_PROFILE=mainnet forge test --match-path=src/20241209_Multi_GHOCCIP151Upgrade/AaveV3Ethereum_GHOCCIP151Upgrade_20241209.t.sol -vv */ -contract AaveV3Ethereum_GHOCCIP151Upgrade_20241209_Test is ProtocolV3TestBase { +contract AaveV3Ethereum_GHOCCIP151Upgrade_20241209_Base is ProtocolV3TestBase { + struct CCIPSendParams { + address sender; + uint256 amount; + CCIPUtils.PoolVersion poolVersion; + } + + uint64 internal constant ETH_CHAIN_SELECTOR = 5009297550715157269; + uint64 internal constant ARB_CHAIN_SELECTOR = 4949039107694359620; + + IGhoToken internal constant GHO = IGhoToken(MiscEthereum.GHO_TOKEN); + ITokenAdminRegistry internal constant TOKEN_ADMIN_REGISTRY = + ITokenAdminRegistry(0xb22764f98dD05c789929716D677382Df22C05Cb6); + address internal constant ARB_PROXY_POOL = 0x26329558f08cbb40d6a4CCA0E0C67b29D64A8c50; + IEVM2EVMOnRamp internal constant ON_RAMP = + IEVM2EVMOnRamp(0x69eCC4E2D8ea56E2d0a05bF57f4Fd6aEE7f2c284); + IEVM2EVMOffRamp_1_5 internal constant OFF_RAMP = + IEVM2EVMOffRamp_1_5(0xdf615eF8D4C64d0ED8Fd7824BBEd2f6a10245aC9); + IRouter internal constant ROUTER = IRouter(0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D); + address internal constant GHO_CCIP_STEWARD = 0x101Efb7b9Beb073B1219Cd5473a7C8A2f2EB84f4; + + IUpgradeableLockReleaseTokenPool_1_4 internal constant EXISTING_TOKEN_POOL = + IUpgradeableLockReleaseTokenPool_1_4(MiscEthereum.GHO_CCIP_TOKEN_POOL); // will be changed to 1.5.1 after AIP execution + IUpgradeableLockReleaseTokenPool_1_5_1 internal NEW_TOKEN_POOL; + AaveV3Ethereum_GHOCCIP151Upgrade_20241209 internal proposal; - function setUp() public { + address internal NEW_REMOTE_POOL_ARB = makeAddr('BurnMintTokenPool 1.5.1'); + address internal alice = makeAddr('alice'); + address internal bob = makeAddr('bob'); + address internal carol = makeAddr('carol'); + + event Locked(address indexed sender, uint256 amount); + event Released(address indexed sender, address indexed recipient, uint256 amount); + event CCIPSendRequested(IInternal.EVM2EVMMessage message); + + error BridgeLimitExceeded(uint256 limit); + + function setUp() public virtual { vm.createSelectFork(vm.rpcUrl('mainnet'), 21366260); - proposal = new AaveV3Ethereum_GHOCCIP151Upgrade_20241209(); + NEW_TOKEN_POOL = IUpgradeableLockReleaseTokenPool_1_5_1(_deployNewTokenPoolEth()); + proposal = new AaveV3Ethereum_GHOCCIP151Upgrade_20241209( + address(NEW_TOKEN_POOL), + NEW_REMOTE_POOL_ARB + ); + + // pre-req - chainlink transfers gho token pool ownership on token admin registry + vm.prank(TOKEN_ADMIN_REGISTRY.owner()); + TOKEN_ADMIN_REGISTRY.transferAdminRole( + MiscEthereum.GHO_TOKEN, + GovernanceV3Ethereum.EXECUTOR_LVL_1 + ); + + _validateConstants(); } /** * @dev executes the generic test suite including e2e and config snapshots */ - function test_defaultProposalExecution() public { + function skip_test_defaultProposalExecution() public { defaultTest( 'AaveV3Ethereum_GHOCCIP151Upgrade_20241209', AaveV3Ethereum.POOL, address(proposal) ); } + + function _deployNewTokenPoolEth() private returns (address) { + IUpgradeableLockReleaseTokenPool_1_4 existingTokenPool = IUpgradeableLockReleaseTokenPool_1_4( + MiscEthereum.GHO_CCIP_TOKEN_POOL + ); + address newTokenPoolImpl = address( + new UpgradeableLockReleaseTokenPool( + existingTokenPool.getToken(), + IGhoToken(existingTokenPool.getToken()).decimals(), + existingTokenPool.getArmProxy(), + existingTokenPool.getAllowListEnabled(), + existingTokenPool.canAcceptLiquidity() + ) + ); + + return + address( + new TransparentUpgradeableProxy( + newTokenPoolImpl, + MiscEthereum.PROXY_ADMIN, + abi.encodeCall( + IUpgradeableLockReleaseTokenPool_1_5_1.initialize, + ( + GovernanceV3Ethereum.EXECUTOR_LVL_1, // owner + existingTokenPool.getAllowList(), + existingTokenPool.getRouter(), + existingTokenPool.getBridgeLimit() + ) + ) + ) + ); + } + + function _validateConstants() private view { + assertEq(address(proposal.TOKEN_ADMIN_REGISTRY()), address(TOKEN_ADMIN_REGISTRY)); + assertEq(proposal.ARB_CHAIN_SELECTOR(), ARB_CHAIN_SELECTOR); + assertEq(address(proposal.EXISTING_TOKEN_POOL()), address(EXISTING_TOKEN_POOL)); + assertEq(address(proposal.EXISTING_REMOTE_POOL_ARB()), ARB_PROXY_POOL); + assertEq(address(proposal.NEW_TOKEN_POOL()), address(NEW_TOKEN_POOL)); + assertEq(address(proposal.NEW_REMOTE_POOL_ARB()), NEW_REMOTE_POOL_ARB); + + assertEq(TOKEN_ADMIN_REGISTRY.typeAndVersion(), 'TokenAdminRegistry 1.5.0'); + assertEq(NEW_TOKEN_POOL.typeAndVersion(), 'LockReleaseTokenPool 1.5.1'); + assertEq(EXISTING_TOKEN_POOL.typeAndVersion(), 'LockReleaseTokenPool 1.4.0'); + assertEq( + ITypeAndVersion(EXISTING_TOKEN_POOL.getProxyPool()).typeAndVersion(), + 'LockReleaseTokenPoolAndProxy 1.5.0' + ); + assertEq(ON_RAMP.typeAndVersion(), 'EVM2EVMOnRamp 1.5.0'); + assertEq(OFF_RAMP.typeAndVersion(), 'EVM2EVMOffRamp 1.5.0'); + + assertEq(ROUTER.typeAndVersion(), 'Router 1.2.0'); + assertEq(EXISTING_TOKEN_POOL.getRouter(), address(ROUTER)); + + assertEq(ROUTER.getOnRamp(ARB_CHAIN_SELECTOR), address(ON_RAMP)); + assertTrue(ROUTER.isOffRamp(ARB_CHAIN_SELECTOR, address(OFF_RAMP))); + } + + function _getTokenMessage( + CCIPSendParams memory params + ) internal returns (IClient.EVM2AnyMessage memory, IInternal.EVM2EVMMessage memory) { + IClient.EVM2AnyMessage memory message = CCIPUtils.generateMessage(params.sender, 1); + message.tokenAmounts[0] = IClient.EVMTokenAmount({ + token: MiscEthereum.GHO_TOKEN, + amount: params.amount + }); + + uint256 feeAmount = ROUTER.getFee(ARB_CHAIN_SELECTOR, message); + deal(params.sender, feeAmount); + + IInternal.EVM2EVMMessage memory eventArg = CCIPUtils.messageToEvent( + CCIPUtils.MessageToEventParams({ + message: message, + router: ROUTER, + sourceChainSelector: ETH_CHAIN_SELECTOR, + feeTokenAmount: feeAmount, + originalSender: params.sender, + sourceToken: MiscEthereum.GHO_TOKEN, + destinationToken: AaveV3ArbitrumAssets.GHO_UNDERLYING, + poolVersion: params.poolVersion + }) + ); + + return (message, eventArg); + } + + function _getStaticParams(address tokenPool) internal view returns (bytes memory) { + IUpgradeableLockReleaseTokenPool_1_4 ghoTokenPool = IUpgradeableLockReleaseTokenPool_1_4( + tokenPool + ); + return + abi.encode( + ghoTokenPool.getToken(), + ghoTokenPool.getAllowListEnabled(), + ghoTokenPool.getAllowList(), + ghoTokenPool.canAcceptLiquidity(), + ghoTokenPool.getRouter() + ); + } + + function _getDynamicParams(address tokenPool) internal view returns (bytes memory) { + IUpgradeableLockReleaseTokenPool_1_4 ghoTokenPool = IUpgradeableLockReleaseTokenPool_1_4( + tokenPool + ); + return + abi.encode( + ghoTokenPool.owner(), + ghoTokenPool.getSupportedChains(), + ghoTokenPool.getAllowListEnabled(), + ghoTokenPool.getRateLimitAdmin(), + ghoTokenPool.getBridgeLimitAdmin(), + ghoTokenPool.getRebalancer(), + ghoTokenPool.getBridgeLimit() + ); + } + + function _tokenBucketToConfig( + IRateLimiter.TokenBucket memory bucket + ) internal pure returns (IRateLimiter.Config memory) { + return + IRateLimiter.Config({ + isEnabled: bucket.isEnabled, + capacity: bucket.capacity, + rate: bucket.rate + }); + } + + function _getDisabledConfig() internal pure returns (IRateLimiter.Config memory) { + return IRateLimiter.Config({isEnabled: false, capacity: 0, rate: 0}); + } + + function assertEq( + IRateLimiter.TokenBucket memory bucket, + IRateLimiter.Config memory config + ) internal pure { + assertEq(abi.encode(_tokenBucketToConfig(bucket)), abi.encode(config)); + } + + function _getImplementation(address proxy) internal view returns (address) { + bytes32 slot = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); + return address(uint160(uint256(vm.load(proxy, slot)))); + } + + function _readInitialized(address proxy) internal view returns (uint8) { + return uint8(uint256(vm.load(proxy, bytes32(0)))); + } +} + +contract AaveV3Ethereum_GHOCCIP151Upgrade_20241209_SetupAndProposalActions is + AaveV3Ethereum_GHOCCIP151Upgrade_20241209_Base +{ + function setUp() public override { + super.setUp(); + } + + function test_tokenPoolOwnershipTransfer() public { + assertFalse( + TOKEN_ADMIN_REGISTRY.isAdministrator(address(GHO), GovernanceV3Ethereum.EXECUTOR_LVL_1) + ); + ITokenAdminRegistry.TokenConfig memory tokenConfig = TOKEN_ADMIN_REGISTRY.getTokenConfig( + address(GHO) + ); + assertNotEq(tokenConfig.administrator, GovernanceV3Ethereum.EXECUTOR_LVL_1); + assertEq(tokenConfig.pendingAdministrator, GovernanceV3Ethereum.EXECUTOR_LVL_1); + assertEq(tokenConfig.tokenPool, EXISTING_TOKEN_POOL.getProxyPool()); + + executePayload(vm, address(proposal)); + + tokenConfig = TOKEN_ADMIN_REGISTRY.getTokenConfig(address(GHO)); + assertEq(tokenConfig.administrator, GovernanceV3Ethereum.EXECUTOR_LVL_1); + assertEq(tokenConfig.pendingAdministrator, address(0)); + assertEq(tokenConfig.tokenPool, address(NEW_TOKEN_POOL)); + } + + function test_tokenPoolLiquidityMigration() public { + assertEq(EXISTING_TOKEN_POOL.getRebalancer(), address(0)); + uint256 balance = GHO.balanceOf(address(EXISTING_TOKEN_POOL)); + uint256 bridgedAmount = EXISTING_TOKEN_POOL.getCurrentBridgedAmount(); + + assertGt(balance, 0); + assertGt(bridgedAmount, 0); + + assertEq(GHO.balanceOf(address(NEW_TOKEN_POOL)), 0); + assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), 0); + + assertEq(bridgedAmount, balance); // bridgedAmountInvariant + + executePayload(vm, address(proposal)); + + assertEq(EXISTING_TOKEN_POOL.getRebalancer(), address(NEW_TOKEN_POOL)); + + assertEq(GHO.balanceOf(address(EXISTING_TOKEN_POOL)), 0); + // ! todo upgrade existing pool to reset bridgedAmount? not necessary since we reset bridgeLimit + // assertEq(EXISTING_TOKEN_POOL.getCurrentBridgedAmount(), 0); + + assertEq(balance, GHO.balanceOf(address(NEW_TOKEN_POOL))); + assertEq(bridgedAmount, NEW_TOKEN_POOL.getCurrentBridgedAmount()); + } + + function test_newTokenPoolSetupAndRegistration() public { + bytes memory staticParams = _getStaticParams(address(EXISTING_TOKEN_POOL)); + bytes memory dynamicParams = _getDynamicParams(address(EXISTING_TOKEN_POOL)); + assertEq(EXISTING_TOKEN_POOL.getRateLimitAdmin(), GHO_CCIP_STEWARD); + + assertEq(TOKEN_ADMIN_REGISTRY.getPool(address(GHO)), EXISTING_TOKEN_POOL.getProxyPool()); + + executePayload(vm, address(proposal)); + + assertEq(staticParams, _getStaticParams(address(NEW_TOKEN_POOL))); + assertEq(dynamicParams, _getDynamicParams(address(NEW_TOKEN_POOL))); + assertEq(NEW_TOKEN_POOL.getRateLimitAdmin(), GHO_CCIP_STEWARD); + assertEq(NEW_TOKEN_POOL.getRemotePools(ARB_CHAIN_SELECTOR).length, 2); + assertTrue(NEW_TOKEN_POOL.isRemotePool(ARB_CHAIN_SELECTOR, abi.encode(ARB_PROXY_POOL))); + assertTrue(NEW_TOKEN_POOL.isRemotePool(ARB_CHAIN_SELECTOR, abi.encode(NEW_REMOTE_POOL_ARB))); + assertEq( + NEW_TOKEN_POOL.getRemoteToken(ARB_CHAIN_SELECTOR), + abi.encode(AaveV3ArbitrumAssets.GHO_UNDERLYING) + ); + assertEq(NEW_TOKEN_POOL.getSupportedChains().length, 1); + assertTrue(NEW_TOKEN_POOL.isSupportedChain(ARB_CHAIN_SELECTOR)); + assertEq( + NEW_TOKEN_POOL.getCurrentInboundRateLimiterState(ARB_CHAIN_SELECTOR), + _getDisabledConfig() + ); + assertEq( + NEW_TOKEN_POOL.getCurrentOutboundRateLimiterState(ARB_CHAIN_SELECTOR), + _getDisabledConfig() + ); + assertEq(NEW_TOKEN_POOL.getRateLimitAdmin(), GHO_CCIP_STEWARD); // sanity check + assertEq(NEW_TOKEN_POOL.getBridgeLimitAdmin(), GHO_CCIP_STEWARD); // sanity check + + assertEq(TOKEN_ADMIN_REGISTRY.getPool(address(GHO)), address(NEW_TOKEN_POOL)); + } + + function test_newTokenPoolInitialization() public { + vm.expectRevert('Initializable: contract is already initialized'); + NEW_TOKEN_POOL.initialize(makeAddr('owner'), new address[](0), makeAddr('router'), 13e7); + assertEq(_readInitialized(address(NEW_TOKEN_POOL)), 1); + assertEq(_readInitialized(_getImplementation(address(NEW_TOKEN_POOL))), 255); + } +} + +contract AaveV3Ethereum_GHOCCIP151Upgrade_20241209_PostUpgrade is + AaveV3Ethereum_GHOCCIP151Upgrade_20241209_Base +{ + function setUp() public override { + super.setUp(); + + executePayload(vm, address(proposal)); + } + + function test_sendMessageSucceedsAndRoutesViaNewPool() public { + uint256 amount = 100_000e18; + + deal(address(GHO), alice, amount); + vm.prank(alice); + GHO.approve(address(ROUTER), amount); + + uint256 aliceBalance = GHO.balanceOf(alice); + uint256 tokenPoolBalance = GHO.balanceOf(address(NEW_TOKEN_POOL)); + uint256 bridgedAmount = NEW_TOKEN_POOL.getCurrentBridgedAmount(); + + ( + IClient.EVM2AnyMessage memory message, + IInternal.EVM2EVMMessage memory eventArg + ) = _getTokenMessage( + CCIPSendParams({amount: amount, sender: alice, poolVersion: CCIPUtils.PoolVersion.V1_5_1}) + ); + + vm.expectEmit(address(NEW_TOKEN_POOL)); // new token pool + emit Locked(address(ON_RAMP), amount); + + vm.expectEmit(address(ON_RAMP)); + emit CCIPSendRequested(eventArg); + + vm.prank(alice); + ROUTER.ccipSend{value: eventArg.feeTokenAmount}(ARB_CHAIN_SELECTOR, message); + + assertEq(GHO.balanceOf(alice), aliceBalance - amount); + assertEq(GHO.balanceOf(address(NEW_TOKEN_POOL)), tokenPoolBalance + amount); + assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), bridgedAmount + amount); + } + + // existing pool can no longer on ramp + function test_lockOrBurnRevertsOnExistingPool() public { + uint256 amount = 100_000e18; + + // prank router.gho.transferFrom(user, EXISTING_TOKEN_POOL, amount) + deal(address(GHO), address(EXISTING_TOKEN_POOL), amount); + + vm.prank(EXISTING_TOKEN_POOL.getProxyPool()); + vm.expectRevert(abi.encodeWithSelector(BridgeLimitExceeded.selector, 0)); + EXISTING_TOKEN_POOL.lockOrBurn( + alice, + abi.encode(alice), + amount, + ARB_CHAIN_SELECTOR, + new bytes(0) + ); + } + + // on-ramp via new pool + function test_lockOrBurnSucceedsOnNewPool() public { + uint256 amount = 100_000e18; + + // prank router.gho.transferFrom(user, NEW_TOKEN_POOL, amount) + // we don't override NEW_TOKEN_POOL balance here & instead transfer because we want + // to check the invariant GHO.balanceOf(tokenPool) == tokenPool.currentBridgedAmount() + deal(address(GHO), address(alice), amount); + vm.prank(alice); + GHO.transfer(address(NEW_TOKEN_POOL), amount); + + uint256 bridgedAmount = NEW_TOKEN_POOL.getCurrentBridgedAmount(); + + vm.expectEmit(address(NEW_TOKEN_POOL)); + emit Locked(address(ON_RAMP), amount); + + vm.prank(address(ON_RAMP)); + NEW_TOKEN_POOL.lockOrBurn( + IPool_CCIP.LockOrBurnInV1({ + receiver: abi.encode(alice), + remoteChainSelector: ARB_CHAIN_SELECTOR, + originalSender: alice, + amount: amount, + localToken: address(GHO) + }) + ); + + assertEq(GHO.balanceOf(address(NEW_TOKEN_POOL)), NEW_TOKEN_POOL.getCurrentBridgedAmount()); + assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), bridgedAmount + amount); + } + + // existing pool can no longer off ramp + function test_releaseOrMintRevertsOnExistingPool() public { + uint256 amount = 100_000e18; + + assertEq(GHO.balanceOf(address(EXISTING_TOKEN_POOL)), 0); + + vm.prank(EXISTING_TOKEN_POOL.getProxyPool()); + // underflow expected at tokenPool.GHO.transfer() since existing + // token pool does not hold any gho + vm.expectRevert(stdError.arithmeticError); + EXISTING_TOKEN_POOL.releaseOrMint( + abi.encode(alice), + alice, + amount, + ARB_CHAIN_SELECTOR, + new bytes(0) + ); + } + + // off-ramp messages sent from new eth token pool (v1.5.1) + function test_releaseOrMintSucceedsOnNewPoolOffRampedViaNewTokenPoolEth() public { + uint256 amount = 100_000e18; + + uint256 aliceBalance = GHO.balanceOf(alice); + uint256 tokenPoolBalance = GHO.balanceOf(address(NEW_TOKEN_POOL)); + uint256 bridgedAmount = NEW_TOKEN_POOL.getCurrentBridgedAmount(); + + vm.expectEmit(address(NEW_TOKEN_POOL)); + emit Released(address(OFF_RAMP), alice, amount); + + vm.prank(address(OFF_RAMP)); + NEW_TOKEN_POOL.releaseOrMint( + IPool_CCIP.ReleaseOrMintInV1({ + originalSender: abi.encode(alice), + remoteChainSelector: ARB_CHAIN_SELECTOR, + receiver: alice, + amount: amount, + localToken: address(GHO), + sourcePoolAddress: abi.encode(address(NEW_REMOTE_POOL_ARB)), + sourcePoolData: new bytes(0), + offchainTokenData: new bytes(0) + }) + ); + + assertEq(GHO.balanceOf(alice), aliceBalance + amount); + assertEq(GHO.balanceOf(address(NEW_TOKEN_POOL)), tokenPoolBalance - amount); + assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), bridgedAmount - amount); + } + + // off-ramp messages sent from existing eth token pool (v1.4) ie ProxyPool + function test_releaseOrMintSucceedsOnNewPoolOffRampedViaExistingTokenPoolEth() public { + uint256 amount = 100_000e18; + + uint256 aliceBalance = GHO.balanceOf(alice); + uint256 tokenPoolBalance = GHO.balanceOf(address(NEW_TOKEN_POOL)); + uint256 bridgedAmount = NEW_TOKEN_POOL.getCurrentBridgedAmount(); + + vm.expectEmit(address(NEW_TOKEN_POOL)); + emit Released(address(OFF_RAMP), alice, amount); + + vm.prank(address(OFF_RAMP)); + NEW_TOKEN_POOL.releaseOrMint( + IPool_CCIP.ReleaseOrMintInV1({ + originalSender: abi.encode(alice), + remoteChainSelector: ARB_CHAIN_SELECTOR, + receiver: alice, + amount: amount, + localToken: address(GHO), + sourcePoolAddress: abi.encode(address(ARB_PROXY_POOL)), + sourcePoolData: new bytes(0), + offchainTokenData: new bytes(0) + }) + ); + + assertEq(GHO.balanceOf(alice), aliceBalance + amount); + assertEq(GHO.balanceOf(address(NEW_TOKEN_POOL)), tokenPoolBalance - amount); + assertEq(NEW_TOKEN_POOL.getCurrentBridgedAmount(), bridgedAmount - amount); + } } diff --git a/src/20241209_Multi_GHOCCIP151Upgrade/utils/CCIPUtils.sol b/src/20241209_Multi_GHOCCIP151Upgrade/utils/CCIPUtils.sol index b7e6f2f1b..e853cd8cc 100644 --- a/src/20241209_Multi_GHOCCIP151Upgrade/utils/CCIPUtils.sol +++ b/src/20241209_Multi_GHOCCIP151Upgrade/utils/CCIPUtils.sol @@ -17,6 +17,11 @@ library CCIPUtils { bytes32 internal constant EVM_2_EVM_MESSAGE_HASH = keccak256('EVM2EVMMessageHashV2'); bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9; + enum PoolVersion { + V1_5_0, + V1_5_1 + } + struct SourceTokenData { bytes sourcePoolAddress; bytes destTokenAddress; @@ -30,7 +35,9 @@ library CCIPUtils { uint64 sourceChainSelector; uint256 feeTokenAmount; address originalSender; + address sourceToken; address destinationToken; + PoolVersion poolVersion; } function generateMessage( @@ -83,7 +90,9 @@ library CCIPUtils { onRamp.getPoolBySourceToken(destChainSelector, params.message.tokenAmounts[i].token) ), destTokenAddress: abi.encode(params.destinationToken), - extraData: '', + extraData: params.poolVersion == PoolVersion.V1_5_1 + ? abi.encode(getTokenDecimals(params.sourceToken)) + : new bytes(0), destGasAmount: getDestGasAmount(onRamp, params.message.tokenAmounts[i].token) }) ); @@ -154,4 +163,10 @@ library CCIPUtils { ? config.destGasOverhead : onRamp.getDynamicConfig().defaultTokenDestGasOverhead; } + + function getTokenDecimals(address token) internal view returns (uint8) { + (bool success, bytes memory data) = token.staticcall(abi.encodeWithSignature('decimals()')); + require(success, 'CCIPUtils: failed to get token decimals'); + return abi.decode(data, (uint8)); + } } diff --git a/src/interfaces/IGhoToken.sol b/src/interfaces/IGhoToken.sol index 2c78aeebd..96fc71c24 100644 --- a/src/interfaces/IGhoToken.sol +++ b/src/interfaces/IGhoToken.sol @@ -1,14 +1,51 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -interface IGhoToken { +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; + +interface IGhoToken is IERC20 { struct Facilitator { uint128 bucketCapacity; uint128 bucketLevel; string label; } - function balanceOf(address user) external returns (uint256); + /** + * @notice Mints the requested amount of tokens to the account address. + * @dev Only facilitators with enough bucket capacity available can mint. + * @dev The bucket level is increased upon minting. + * @param account The address receiving the GHO tokens + * @param amount The amount to mint + */ + function mint(address account, uint256 amount) external; + + /** + * @notice Burns the requested amount of tokens from the account address. + * @dev Only active facilitators (bucket level > 0) can burn. + * @dev The bucket level is decreased upon burning. + * @param amount The amount to burn + */ + function burn(uint256 amount) external; + + /** + * @notice Add the facilitator passed with the parameters to the facilitators list. + * @dev Only accounts with `FACILITATOR_MANAGER_ROLE` role can call this function + * @param facilitatorAddress The address of the facilitator to add + * @param facilitatorLabel A human readable identifier for the facilitator + * @param bucketCapacity The upward limit of GHO can be minted by the facilitator + */ + function addFacilitator( + address facilitatorAddress, + string calldata facilitatorLabel, + uint128 bucketCapacity + ) external; + + /** + * @notice Remove the facilitator from the facilitators list. + * @dev Only accounts with `FACILITATOR_MANAGER_ROLE` role can call this function + * @param facilitatorAddress The address of the facilitator to remove + */ + function removeFacilitator(address facilitatorAddress) external; /** * @notice Set the bucket capacity of the facilitator. diff --git a/src/interfaces/ccip/IEVM2EVMOffRamp.sol b/src/interfaces/ccip/IEVM2EVMOffRamp.sol index b900be64b..d9d132f5f 100644 --- a/src/interfaces/ccip/IEVM2EVMOffRamp.sol +++ b/src/interfaces/ccip/IEVM2EVMOffRamp.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.0; -import {IInternal} from 'src/interfaces/ccip/IInternal.sol'; +import {IInternal} from './IInternal.sol'; +import {ITypeAndVersion} from './ITypeAndVersion.sol'; -interface IEVM2EVMOffRamp { +interface IEVM2EVMOffRamp_1_2 is ITypeAndVersion { /// @notice Execute a single message. /// @param message The message that will be executed. /// @param offchainTokenData Token transfer data to be passed to TokenPool. @@ -17,3 +18,18 @@ interface IEVM2EVMOffRamp { bytes[] memory offchainTokenData ) external; } + +interface IEVM2EVMOffRamp_1_5 is ITypeAndVersion { + /// @notice Execute a single message. + /// @param message The message that will be executed. + /// @param offchainTokenData Token transfer data to be passed to TokenPool. + /// @dev We make this external and callable by the contract itself, in order to try/catch + /// its execution and enforce atomicity among successful message processing and token transfer. + /// @dev We use ERC-165 to check for the ccipReceive interface to permit sending tokens to contracts + /// (for example smart contract wallets) without an associated message. + function executeSingleMessage( + IInternal.EVM2EVMMessage calldata message, + bytes[] calldata offchainTokenData, + uint32[] memory tokenGasOverrides + ) external; +} diff --git a/src/interfaces/ccip/IEVM2EVMOnRamp.sol b/src/interfaces/ccip/IEVM2EVMOnRamp.sol new file mode 100644 index 000000000..13affbc6e --- /dev/null +++ b/src/interfaces/ccip/IEVM2EVMOnRamp.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {IInternal} from './IInternal.sol'; +import {ITypeAndVersion} from './ITypeAndVersion.sol'; + +interface IEVM2EVMOnRamp is ITypeAndVersion { + struct TokenTransferFeeConfig { + uint32 minFeeUSDCents; // ──────────╮ Minimum fee to charge per token transfer, multiples of 0.01 USD + uint32 maxFeeUSDCents; // │ Maximum fee to charge per token transfer, multiples of 0.01 USD + uint16 deciBps; // │ Basis points charged on token transfers, multiples of 0.1bps, or 1e-5 + uint32 destGasOverhead; // │ Gas charged to execute the token transfer on the destination chain + // │ Extra data availability bytes that are returned from the source pool and sent + uint32 destBytesOverhead; // │ to the destination pool. Must be >= Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES + bool aggregateRateLimitEnabled; // │ Whether this transfer token is to be included in Aggregate Rate Limiting + bool isEnabled; // ─────────────────╯ Whether this token has custom transfer fees + } + + struct DynamicConfig { + address router; // ──────────────────────────╮ Router address + uint16 maxNumberOfTokensPerMsg; // │ Maximum number of distinct ERC20 token transferred per message + uint32 destGasOverhead; // │ Gas charged on top of the gasLimit to cover destination chain costs + uint16 destGasPerPayloadByte; // │ Destination chain gas charged for passing each byte of `data` payload to receiver + uint32 destDataAvailabilityOverheadGas; // ──╯ Extra data availability gas charged on top of the message, e.g. for OCR + uint16 destGasPerDataAvailabilityByte; // ───╮ Amount of gas to charge per byte of message data that needs availability + uint16 destDataAvailabilityMultiplierBps; // │ Multiplier for data availability gas, multiples of bps, or 0.0001 + address priceRegistry; // │ Price registry address + uint32 maxDataBytes; // │ Maximum payload data size in bytes + uint32 maxPerMsgGasLimit; // ────────────────╯ Maximum gas limit for messages targeting EVMs + // │ + // The following three properties are defaults, they can be overridden by setting the TokenTransferFeeConfig for a token + uint16 defaultTokenFeeUSDCents; // ──────────╮ Default token fee charged per token transfer + uint32 defaultTokenDestGasOverhead; // │ Default gas charged to execute the token transfer on the destination chain + bool enforceOutOfOrder; // ──────────────────╯ Whether to enforce the allowOutOfOrderExecution extraArg value to be true. + } + + /// @notice Gets the next sequence number to be used in the onRamp + /// @return the next sequence number to be used + function getExpectedNextSequenceNumber() external view returns (uint64); + + /// @notice Get the next nonce for a given sender + /// @param sender The sender to get the nonce for + /// @return nonce The next nonce for the sender + function getSenderNonce(address sender) external view returns (uint64 nonce); + + /// @notice Adds and removed token pools. + /// @param removes The tokens and pools to be removed + /// @param adds The tokens and pools to be added. + function applyPoolUpdates( + IInternal.PoolUpdate[] memory removes, + IInternal.PoolUpdate[] memory adds + ) external; + + /// @notice Get the pool for a specific token + /// @param destChainSelector The destination chain selector + /// @param sourceToken The source chain token to get the pool for + /// @return pool Token pool + function getPoolBySourceToken( + uint64 destChainSelector, + address sourceToken + ) external view returns (address); + + /// @notice Gets the transfer fee config for a given token. + function getTokenTransferFeeConfig( + address token + ) external view returns (TokenTransferFeeConfig memory tokenTransferFeeConfig); + + /// @notice Returns the dynamic onRamp config. + /// @return dynamicConfig the configuration. + function getDynamicConfig() external view returns (DynamicConfig memory dynamicConfig); +} diff --git a/src/interfaces/ccip/IInternal.sol b/src/interfaces/ccip/IInternal.sol index fa8c78f6d..062ee93da 100644 --- a/src/interfaces/ccip/IInternal.sol +++ b/src/interfaces/ccip/IInternal.sol @@ -5,6 +5,11 @@ pragma solidity ^0.8.0; import {IClient} from 'src/interfaces/ccip/IClient.sol'; interface IInternal { + struct PoolUpdate { + address token; // The IERC20 token address + address pool; // The token pool address + } + /// @notice The cross chain message that gets committed to EVM chains. /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers. struct EVM2EVMMessage { diff --git a/src/interfaces/ccip/IRateLimiter.sol b/src/interfaces/ccip/IRateLimiter.sol new file mode 100644 index 000000000..d4cb9ffa4 --- /dev/null +++ b/src/interfaces/ccip/IRateLimiter.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IRateLimiter { + struct TokenBucket { + uint128 tokens; // ──────╮ Current number of tokens that are in the bucket. + uint32 lastUpdated; // │ Timestamp in seconds of the last token refill, good for 100+ years. + bool isEnabled; // ──────╯ Indication whether the rate limiting is enabled or not + uint128 capacity; // ────╮ Maximum number of tokens that can be in the bucket. + uint128 rate; // ────────╯ Number of tokens per second that the bucket is refilled. + } + + struct Config { + bool isEnabled; // Indication whether the rate limiting should be enabled + uint128 capacity; // ────╮ Specifies the capacity of the rate limiter + uint128 rate; // ───────╯ Specifies the rate of the rate limiter + } +} diff --git a/src/interfaces/ccip/IRouter.sol b/src/interfaces/ccip/IRouter.sol index 4e524b42f..7792d09cc 100644 --- a/src/interfaces/ccip/IRouter.sol +++ b/src/interfaces/ccip/IRouter.sol @@ -2,26 +2,42 @@ pragma solidity ^0.8.0; -import {IClient} from 'src/interfaces/ccip/IClient.sol'; +import {IClient} from './IClient.sol'; +import {ITypeAndVersion} from './ITypeAndVersion.sol'; -interface IRouter { - /// @notice Request a message to be sent to the destination chain - /// @param destinationChainSelector The destination chain ID - /// @param message The cross-chain CCIP message including data and/or tokens - /// @return messageId The message ID - /// @dev Note if msg.value is larger than the required fee (from getFee) we accept - /// the overpayment with no refund. - /// @dev Reverts with appropriate reason upon invalid message. +interface IRouter is ITypeAndVersion { + error UnsupportedDestinationChain(uint64 destChainSelector); + error InsufficientFeeTokenAmount(); + error InvalidMsgValue(); + + struct OnRamp { + uint64 destChainSelector; + address onRamp; + } + struct OffRamp { + uint64 sourceChainSelector; + address offRamp; + } + + function owner() external view returns (address); + function getWrappedNative() external view returns (address); + function getOffRamps() external view returns (OffRamp[] memory); + function getOnRamp(uint64 destChainSelector) external view returns (address onRampAddress); + function isOffRamp( + uint64 sourceChainSelector, + address offRamp + ) external view returns (bool isOffRamp); + function applyRampUpdates( + OnRamp[] calldata onRampUpdates, + OffRamp[] calldata offRampRemoves, + OffRamp[] calldata offRampAdds + ) external; + function isChainSupported(uint64 chainSelector) external view returns (bool supported); + function getSupportedTokens(uint64 chainSelector) external view returns (address[] memory tokens); function ccipSend( uint64 destinationChainSelector, IClient.EVM2AnyMessage memory message ) external payable returns (bytes32); - - /// @param destinationChainSelector The destination chainSelector - /// @param message The cross-chain CCIP message including data and/or tokens - /// @return fee returns execution fee for the message - /// delivery to destination chain, denominated in the feeToken specified in the message. - /// @dev Reverts with appropriate reason upon invalid message. function getFee( uint64 destinationChainSelector, IClient.EVM2AnyMessage memory message diff --git a/src/interfaces/ccip/ITokenAdminRegistry.sol b/src/interfaces/ccip/ITokenAdminRegistry.sol new file mode 100644 index 000000000..6cdaf7bf4 --- /dev/null +++ b/src/interfaces/ccip/ITokenAdminRegistry.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +interface ITokenAdminRegistry { + struct TokenConfig { + address administrator; + address pendingAdministrator; + address tokenPool; + } + + error AlreadyRegistered(address token); + error InvalidTokenPoolToken(address token); + error OnlyAdministrator(address sender, address token); + error OnlyPendingAdministrator(address sender, address token); + error OnlyRegistryModuleOrOwner(address sender); + error ZeroAddress(); + + event AdministratorTransferRequested( + address indexed token, + address indexed currentAdmin, + address indexed newAdmin + ); + event AdministratorTransferred(address indexed token, address indexed newAdmin); + event OwnershipTransferRequested(address indexed from, address indexed to); + event OwnershipTransferred(address indexed from, address indexed to); + event PoolSet(address indexed token, address indexed previousPool, address indexed newPool); + event RegistryModuleAdded(address module); + event RegistryModuleRemoved(address indexed module); + + function acceptAdminRole(address localToken) external; + function acceptOwnership() external; + function addRegistryModule(address module) external; + function getAllConfiguredTokens( + uint64 startIndex, + uint64 maxCount + ) external view returns (address[] memory tokens); + function getPool(address token) external view returns (address); + function getPools(address[] memory tokens) external view returns (address[] memory); + function getTokenConfig(address token) external view returns (TokenConfig memory); + function isAdministrator(address localToken, address administrator) external view returns (bool); + function isRegistryModule(address module) external view returns (bool); + function owner() external view returns (address); + function proposeAdministrator(address localToken, address administrator) external; + function removeRegistryModule(address module) external; + function setPool(address localToken, address pool) external; + function transferAdminRole(address localToken, address newAdmin) external; + function transferOwnership(address to) external; + function typeAndVersion() external view returns (string memory); +} diff --git a/src/interfaces/ccip/ITypeAndVersion.sol b/src/interfaces/ccip/ITypeAndVersion.sol new file mode 100644 index 000000000..135f6d0ae --- /dev/null +++ b/src/interfaces/ccip/ITypeAndVersion.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface ITypeAndVersion { + function typeAndVersion() external pure returns (string memory); +} diff --git a/src/interfaces/ccip/tokenPool/IPool.sol b/src/interfaces/ccip/tokenPool/IPool.sol new file mode 100644 index 000000000..f85af151e --- /dev/null +++ b/src/interfaces/ccip/tokenPool/IPool.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IPool { + struct LockOrBurnInV1 { + bytes receiver; + uint64 remoteChainSelector; + address originalSender; + uint256 amount; + address localToken; + } + + struct LockOrBurnOutV1 { + bytes destTokenAddress; + bytes destPoolData; + } + + struct ReleaseOrMintInV1 { + bytes originalSender; + uint64 remoteChainSelector; + address receiver; + uint256 amount; + address localToken; + bytes sourcePoolAddress; + bytes sourcePoolData; + bytes offchainTokenData; + } + + struct ReleaseOrMintOutV1 { + uint256 destinationAmount; + } +} diff --git a/src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol b/src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol new file mode 100644 index 000000000..b1d773a5e --- /dev/null +++ b/src/interfaces/ccip/tokenPool/IUpgradeableBurnMintTokenPool.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IRateLimiter} from '../IRateLimiter.sol'; +import {IPool} from './IPool.sol'; + +interface IUpgradeableBurnMintTokenPool_1_4 { + struct ChainUpdate { + uint64 remoteChainSelector; + bool allowed; + IRateLimiter.Config outboundIRateLimiterConfig; + IRateLimiter.Config inboundIRateLimiterConfig; + } + + error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested); + error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available); + error AllowListNotEnabled(); + error BadARMSignal(); + error BucketOverfilled(); + error CallerIsNotARampOnRouter(address caller); + error ChainAlreadyExists(uint64 chainSelector); + error ChainNotAllowed(uint64 remoteChainSelector); + error DisabledNonZeroRateLimit(IRateLimiter.Config config); + error InvalidRatelimitRate(IRateLimiter.Config IRateLimiterConfig); + error NonExistentChain(uint64 remoteChainSelector); + error RateLimitMustBeDisabled(); + error SenderNotAllowed(address sender); + error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress); + error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress); + error Unauthorized(address caller); + error ZeroAddressNotAllowed(); + + event AllowListAdd(address sender); + event AllowListRemove(address sender); + event Burned(address indexed sender, uint256 amount); + event ChainAdded( + uint64 remoteChainSelector, + IRateLimiter.Config outboundIRateLimiterConfig, + IRateLimiter.Config inboundIRateLimiterConfig + ); + event ChainConfigured( + uint64 remoteChainSelector, + IRateLimiter.Config outboundIRateLimiterConfig, + IRateLimiter.Config inboundIRateLimiterConfig + ); + event ChainRemoved(uint64 remoteChainSelector); + event ConfigChanged(IRateLimiter.Config config); + event Initialized(uint8 version); + event Locked(address indexed sender, uint256 amount); + event Minted(address indexed sender, address indexed recipient, uint256 amount); + event OwnershipTransferRequested(address indexed from, address indexed to); + event OwnershipTransferred(address indexed from, address indexed to); + event Released(address indexed sender, address indexed recipient, uint256 amount); + event RouterUpdated(address oldRouter, address newRouter); + event TokensConsumed(uint256 tokens); + + function acceptOwnership() external; + function applyAllowListUpdates(address[] memory removes, address[] memory adds) external; + function applyChainUpdates(ChainUpdate[] memory chains) external; + function getAllowList() external view returns (address[] memory); + function getAllowListEnabled() external view returns (bool); + function getArmProxy() external view returns (address armProxy); + function getCurrentInboundIRateLimiterState( + uint64 remoteChainSelector + ) external view returns (IRateLimiter.TokenBucket memory); + function getCurrentOutboundIRateLimiterState( + uint64 remoteChainSelector + ) external view returns (IRateLimiter.TokenBucket memory); + function getProxyPool() external view returns (address proxyPool); + function getRateLimitAdmin() external view returns (address); + function getRouter() external view returns (address router); + function getSupportedChains() external view returns (uint64[] memory); + function getToken() external view returns (address token); + function initialize(address owner, address[] memory allowlist, address router) external; + function isSupportedChain(uint64 remoteChainSelector) external view returns (bool); + function lockOrBurn( + address originalSender, + bytes memory, + uint256 amount, + uint64 remoteChainSelector, + bytes memory + ) external returns (bytes memory); + function owner() external view returns (address); + function releaseOrMint( + bytes memory, + address receiver, + uint256 amount, + uint64 remoteChainSelector, + bytes memory + ) external; + function setChainIRateLimiterConfig( + uint64 remoteChainSelector, + IRateLimiter.Config memory outboundConfig, + IRateLimiter.Config memory inboundConfig + ) external; + function setProxyPool(address proxyPool) external; + function setRateLimitAdmin(address rateLimitAdmin) external; + function setRouter(address newRouter) external; + function supportsInterface(bytes4 interfaceId) external pure returns (bool); + function transferOwnership(address to) external; + function typeAndVersion() external view returns (string memory); +} + +interface IUpgradeableBurnMintTokenPool_1_5_1 { + struct ChainUpdate { + uint64 remoteChainSelector; + bytes[] remotePoolAddresses; + bytes remoteTokenAddress; + IRateLimiter.Config outboundRateLimiterConfig; + IRateLimiter.Config inboundRateLimiterConfig; + } + + error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested); + error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available); + error AllowListNotEnabled(); + error BucketOverfilled(); + error CallerIsNotARampOnRouter(address caller); + error CannotTransferToSelf(); + error ChainAlreadyExists(uint64 chainSelector); + error ChainNotAllowed(uint64 remoteChainSelector); + error CursedByRMN(); + error DisabledNonZeroRateLimit(IRateLimiter.Config config); + error InvalidDecimalArgs(uint8 expected, uint8 actual); + error InvalidRateLimitRate(IRateLimiter.Config IRateLimiterConfig); + error InvalidRemoteChainDecimals(bytes sourcePoolData); + error InvalidRemotePoolForChain(uint64 remoteChainSelector, bytes remotePoolAddress); + error InvalidSourcePoolAddress(bytes sourcePoolAddress); + error InvalidToken(address token); + error MustBeProposedOwner(); + error NonExistentChain(uint64 remoteChainSelector); + error OnlyCallableByOwner(); + error OverflowDetected(uint8 remoteDecimals, uint8 localDecimals, uint256 remoteAmount); + error OwnerCannotBeZero(); + error PoolAlreadyAdded(uint64 remoteChainSelector, bytes remotePoolAddress); + error RateLimitMustBeDisabled(); + error SenderNotAllowed(address sender); + error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress); + error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress); + error Unauthorized(address caller); + error ZeroAddressNotAllowed(); + + event AllowListAdd(address sender); + event AllowListRemove(address sender); + event Burned(address indexed sender, uint256 amount); + event ChainAdded( + uint64 remoteChainSelector, + bytes remoteToken, + IRateLimiter.Config outboundIRateLimiterConfig, + IRateLimiter.Config inboundIRateLimiterConfig + ); + event ChainConfigured( + uint64 remoteChainSelector, + IRateLimiter.Config outboundIRateLimiterConfig, + IRateLimiter.Config inboundIRateLimiterConfig + ); + event ChainRemoved(uint64 remoteChainSelector); + event ConfigChanged(IRateLimiter.Config config); + event Initialized(uint8 version); + event Locked(address indexed sender, uint256 amount); + event Minted(address indexed sender, address indexed recipient, uint256 amount); + event OwnershipTransferRequested(address indexed from, address indexed to); + event OwnershipTransferred(address indexed from, address indexed to); + event RateLimitAdminSet(address rateLimitAdmin); + event Released(address indexed sender, address indexed recipient, uint256 amount); + event RemotePoolAdded(uint64 indexed remoteChainSelector, bytes remotePoolAddress); + event RemotePoolRemoved(uint64 indexed remoteChainSelector, bytes remotePoolAddress); + event RouterUpdated(address oldRouter, address newRouter); + event TokensConsumed(uint256 tokens); + + function transferLiquidity(address to, uint256 amount) external; + function acceptOwnership() external; + function addRemotePool(uint64 remoteChainSelector, bytes memory remotePoolAddress) external; + function applyAllowListUpdates(address[] memory removes, address[] memory adds) external; + function applyChainUpdates( + uint64[] memory remoteChainSelectorsToRemove, + ChainUpdate[] memory chainsToAdd + ) external; + function getAllowList() external view returns (address[] memory); + function getAllowListEnabled() external view returns (bool); + function getCurrentInboundRateLimiterState( + uint64 remoteChainSelector + ) external view returns (IRateLimiter.TokenBucket memory); + function getCurrentOutboundRateLimiterState( + uint64 remoteChainSelector + ) external view returns (IRateLimiter.TokenBucket memory); + function getRateLimitAdmin() external view returns (address); + function getRemotePools(uint64 remoteChainSelector) external view returns (bytes[] memory); + function getRemoteToken(uint64 remoteChainSelector) external view returns (bytes memory); + function getRmnProxy() external view returns (address rmnProxy); + function getRouter() external view returns (address router); + function getSupportedChains() external view returns (uint64[] memory); + function getToken() external view returns (address token); + function getTokenDecimals() external view returns (uint8 decimals); + function initialize(address owner_, address[] memory allowlist, address router) external; + function isRemotePool( + uint64 remoteChainSelector, + bytes memory remotePoolAddress + ) external view returns (bool); + function isSupportedChain(uint64 remoteChainSelector) external view returns (bool); + function isSupportedToken(address token) external view returns (bool); + function lockOrBurn( + IPool.LockOrBurnInV1 memory lockOrBurnIn + ) external returns (IPool.LockOrBurnOutV1 memory); + function owner() external view returns (address); + function releaseOrMint( + IPool.ReleaseOrMintInV1 memory releaseOrMintIn + ) external returns (IPool.ReleaseOrMintOutV1 memory); + function removeRemotePool(uint64 remoteChainSelector, bytes memory remotePoolAddress) external; + function setChainIRateLimiterConfig( + uint64 remoteChainSelector, + IRateLimiter.Config memory outboundConfig, + IRateLimiter.Config memory inboundConfig + ) external; + function setRateLimitAdmin(address rateLimitAdmin) external; + function setRouter(address newRouter) external; + function supportsInterface(bytes4 interfaceId) external pure returns (bool); + function transferOwnership(address to) external; + function typeAndVersion() external view returns (string memory); +} diff --git a/src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol b/src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol new file mode 100644 index 000000000..873611b80 --- /dev/null +++ b/src/interfaces/ccip/tokenPool/IUpgradeableLockReleaseTokenPool.sol @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IRateLimiter} from '../IRateLimiter.sol'; +import {IPool} from './IPool.sol'; + +interface IUpgradeableLockReleaseTokenPool_1_4 { + struct ChainUpdate { + uint64 remoteChainSelector; + bool allowed; + IRateLimiter.Config outboundIRateLimiterConfig; + IRateLimiter.Config inboundIRateLimiterConfig; + } + + error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested); + error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available); + error AllowListNotEnabled(); + error BadARMSignal(); + error BridgeLimitExceeded(uint256 bridgeLimit); + error BucketOverfilled(); + error CallerIsNotARampOnRouter(address caller); + error ChainAlreadyExists(uint64 chainSelector); + error ChainNotAllowed(uint64 remoteChainSelector); + error DisabledNonZeroRateLimit(IRateLimiter.Config config); + error InsufficientLiquidity(); + error InvalidRatelimitRate(IRateLimiter.Config IRateLimiterConfig); + error LiquidityNotAccepted(); + error NonExistentChain(uint64 remoteChainSelector); + error NotEnoughBridgedAmount(); + error RateLimitMustBeDisabled(); + error SenderNotAllowed(address sender); + error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress); + error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress); + error Unauthorized(address caller); + error ZeroAddressNotAllowed(); + + event AllowListAdd(address sender); + event AllowListRemove(address sender); + event BridgeLimitAdminUpdated(address indexed oldAdmin, address indexed newAdmin); + event BridgeLimitUpdated(uint256 oldBridgeLimit, uint256 newBridgeLimit); + event Burned(address indexed sender, uint256 amount); + event ChainAdded( + uint64 remoteChainSelector, + IRateLimiter.Config outboundRateLimiterConfig, + IRateLimiter.Config inboundRateLimiterConfig + ); + event ChainConfigured( + uint64 remoteChainSelector, + IRateLimiter.Config outboundRateLimiterConfig, + IRateLimiter.Config inboundRateLimiterConfig + ); + event ChainRemoved(uint64 remoteChainSelector); + event ConfigChanged(IRateLimiter.Config config); + event Initialized(uint8 version); + event LiquidityAdded(address indexed provider, uint256 indexed amount); + event LiquidityRemoved(address indexed provider, uint256 indexed amount); + event Locked(address indexed sender, uint256 amount); + event Minted(address indexed sender, address indexed recipient, uint256 amount); + event OwnershipTransferRequested(address indexed from, address indexed to); + event OwnershipTransferred(address indexed from, address indexed to); + event Released(address indexed sender, address indexed recipient, uint256 amount); + event RouterUpdated(address oldRouter, address newRouter); + event TokensConsumed(uint256 tokens); + + function acceptOwnership() external; + function applyAllowListUpdates(address[] memory removes, address[] memory adds) external; + function applyChainUpdates(ChainUpdate[] memory chains) external; + function canAcceptLiquidity() external view returns (bool); + function getAllowList() external view returns (address[] memory); + function getAllowListEnabled() external view returns (bool); + function getArmProxy() external view returns (address armProxy); + function getBridgeLimit() external view returns (uint256); + function getBridgeLimitAdmin() external view returns (address); + function getCurrentBridgedAmount() external view returns (uint256); + function getCurrentInboundRateLimiterState( + uint64 remoteChainSelector + ) external view returns (IRateLimiter.TokenBucket memory); + function getCurrentOutboundRateLimiterState( + uint64 remoteChainSelector + ) external view returns (IRateLimiter.TokenBucket memory); + function getLockReleaseInterfaceId() external pure returns (bytes4); + function getProxyPool() external view returns (address proxyPool); + function getRateLimitAdmin() external view returns (address); + function getRebalancer() external view returns (address); + function getRouter() external view returns (address router); + function getSupportedChains() external view returns (uint64[] memory); + function getToken() external view returns (address token); + function initialize( + address owner, + address[] memory allowlist, + address router, + uint256 bridgeLimit + ) external; + function isSupportedChain(uint64 remoteChainSelector) external view returns (bool); + function lockOrBurn( + address originalSender, + bytes memory, + uint256 amount, + uint64 remoteChainSelector, + bytes memory + ) external returns (bytes memory); + function owner() external view returns (address); + function provideLiquidity(uint256 amount) external; + function releaseOrMint( + bytes memory, + address receiver, + uint256 amount, + uint64 remoteChainSelector, + bytes memory + ) external; + function setBridgeLimit(uint256 newBridgeLimit) external; + function setBridgeLimitAdmin(address bridgeLimitAdmin) external; + function setChainRateLimiterConfig( + uint64 remoteChainSelector, + IRateLimiter.Config memory outboundConfig, + IRateLimiter.Config memory inboundConfig + ) external; + function setProxyPool(address proxyPool) external; + function setRateLimitAdmin(address rateLimitAdmin) external; + function setRebalancer(address rebalancer) external; + function setRouter(address newRouter) external; + function supportsInterface(bytes4 interfaceId) external pure returns (bool); + function transferOwnership(address to) external; + function typeAndVersion() external view returns (string memory); + function withdrawLiquidity(uint256 amount) external; +} + +interface IUpgradeableLockReleaseTokenPool_1_5_1 { + struct ChainUpdate { + uint64 remoteChainSelector; + bytes[] remotePoolAddresses; + bytes remoteTokenAddress; + IRateLimiter.Config outboundRateLimiterConfig; + IRateLimiter.Config inboundRateLimiterConfig; + } + + error AggregateValueMaxCapacityExceeded(uint256 capacity, uint256 requested); + error AggregateValueRateLimitReached(uint256 minWaitInSeconds, uint256 available); + error AllowListNotEnabled(); + error BridgeLimitExceeded(uint256 bridgeLimit); + error BucketOverfilled(); + error CallerIsNotARampOnRouter(address caller); + error CannotTransferToSelf(); + error ChainAlreadyExists(uint64 chainSelector); + error ChainNotAllowed(uint64 remoteChainSelector); + error CursedByRMN(); + error DisabledNonZeroRateLimit(IRateLimiter.Config config); + error InsufficientLiquidity(); + error InvalidDecimalArgs(uint8 expected, uint8 actual); + error InvalidRateLimitRate(IRateLimiter.Config IRateLimiterConfig); + error InvalidRemoteChainDecimals(bytes sourcePoolData); + error InvalidRemotePoolForChain(uint64 remoteChainSelector, bytes remotePoolAddress); + error InvalidSourcePoolAddress(bytes sourcePoolAddress); + error InvalidToken(address token); + error LiquidityNotAccepted(); + error MustBeProposedOwner(); + error NonExistentChain(uint64 remoteChainSelector); + error NotEnoughBridgedAmount(); + error OnlyCallableByOwner(); + error OverflowDetected(uint8 remoteDecimals, uint8 localDecimals, uint256 remoteAmount); + error OwnerCannotBeZero(); + error PoolAlreadyAdded(uint64 remoteChainSelector, bytes remotePoolAddress); + error RateLimitMustBeDisabled(); + error SenderNotAllowed(address sender); + error TokenMaxCapacityExceeded(uint256 capacity, uint256 requested, address tokenAddress); + error TokenRateLimitReached(uint256 minWaitInSeconds, uint256 available, address tokenAddress); + error Unauthorized(address caller); + error ZeroAddressNotAllowed(); + + event AllowListAdd(address sender); + event AllowListRemove(address sender); + event BridgeLimitAdminUpdated(address indexed oldAdmin, address indexed newAdmin); + event BridgeLimitUpdated(uint256 oldBridgeLimit, uint256 newBridgeLimit); + event Burned(address indexed sender, uint256 amount); + event ChainAdded( + uint64 remoteChainSelector, + bytes remoteToken, + IRateLimiter.Config outboundRateLimiterConfig, + IRateLimiter.Config inboundRateLimiterConfig + ); + event ChainConfigured( + uint64 remoteChainSelector, + IRateLimiter.Config outboundRateLimiterConfig, + IRateLimiter.Config inboundRateLimiterConfig + ); + event ChainRemoved(uint64 remoteChainSelector); + event ConfigChanged(IRateLimiter.Config config); + event Initialized(uint8 version); + event LiquidityAdded(address indexed provider, uint256 indexed amount); + event LiquidityRemoved(address indexed provider, uint256 indexed amount); + event LiquidityTransferred(address indexed from, uint256 amount); + event Locked(address indexed sender, uint256 amount); + event Minted(address indexed sender, address indexed recipient, uint256 amount); + event OwnershipTransferRequested(address indexed from, address indexed to); + event OwnershipTransferred(address indexed from, address indexed to); + event RateLimitAdminSet(address rateLimitAdmin); + event Released(address indexed sender, address indexed recipient, uint256 amount); + event RemotePoolAdded(uint64 indexed remoteChainSelector, bytes remotePoolAddress); + event RemotePoolRemoved(uint64 indexed remoteChainSelector, bytes remotePoolAddress); + event RouterUpdated(address oldRouter, address newRouter); + event TokensConsumed(uint256 tokens); + + function acceptOwnership() external; + function addRemotePool(uint64 remoteChainSelector, bytes memory remotePoolAddress) external; + function applyAllowListUpdates(address[] memory removes, address[] memory adds) external; + function applyChainUpdates( + uint64[] memory remoteChainSelectorsToRemove, + ChainUpdate[] memory chainsToAdd + ) external; + function canAcceptLiquidity() external view returns (bool); + function getAllowList() external view returns (address[] memory); + function getAllowListEnabled() external view returns (bool); + function getBridgeLimit() external view returns (uint256); + function getBridgeLimitAdmin() external view returns (address); + function getCurrentBridgedAmount() external view returns (uint256); + function getCurrentInboundRateLimiterState( + uint64 remoteChainSelector + ) external view returns (IRateLimiter.TokenBucket memory); + function getCurrentOutboundRateLimiterState( + uint64 remoteChainSelector + ) external view returns (IRateLimiter.TokenBucket memory); + function getRateLimitAdmin() external view returns (address); + function getRebalancer() external view returns (address); + function getRemotePools(uint64 remoteChainSelector) external view returns (bytes[] memory); + function getRemoteToken(uint64 remoteChainSelector) external view returns (bytes memory); + function getRmnProxy() external view returns (address rmnProxy); + function getRouter() external view returns (address router); + function getSupportedChains() external view returns (uint64[] memory); + function getToken() external view returns (address token); + function getTokenDecimals() external view returns (uint8 decimals); + function initialize( + address owner_, + address[] memory allowlist, + address router, + uint256 bridgeLimit + ) external; + function isRemotePool( + uint64 remoteChainSelector, + bytes memory remotePoolAddress + ) external view returns (bool); + function isSupportedChain(uint64 remoteChainSelector) external view returns (bool); + function isSupportedToken(address token) external view returns (bool); + function lockOrBurn( + IPool.LockOrBurnInV1 memory lockOrBurnIn + ) external returns (IPool.LockOrBurnOutV1 memory); + function owner() external view returns (address); + function provideLiquidity(uint256 amount) external; + function releaseOrMint( + IPool.ReleaseOrMintInV1 memory releaseOrMintIn + ) external returns (IPool.ReleaseOrMintOutV1 memory); + function removeRemotePool(uint64 remoteChainSelector, bytes memory remotePoolAddress) external; + function setBridgeLimit(uint256 newBridgeLimit) external; + function setBridgeLimitAdmin(address bridgeLimitAdmin) external; + function setChainRateLimiterConfig( + uint64 remoteChainSelector, + IRateLimiter.Config memory outboundConfig, + IRateLimiter.Config memory inboundConfig + ) external; + function setRateLimitAdmin(address rateLimitAdmin) external; + function setRebalancer(address rebalancer) external; + function setRouter(address newRouter) external; + function supportsInterface(bytes4 interfaceId) external pure returns (bool); + function transferLiquidity(address from, uint256 amount) external; + function transferOwnership(address to) external; + function typeAndVersion() external view returns (string memory); + function withdrawLiquidity(uint256 amount) external; +}