diff --git a/.env.example b/.env.example index b2f1de34c..bdc141521 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,7 @@ RPC_POLYGON=https://polygon-rpc.com RPC_ARBITRUM=https://arb1.arbitrum.io/rpc RPC_FANTOM=https://rpc.ftm.tools RPC_HARMONY=https://api.harmony.one +RPC_METIS=https://andromeda.metis.io/ ETHERSCAN_API_KEY_MAINNET= ETHERSCAN_API_KEY_POLYGON= diff --git a/Makefile b/Makefile index 77eca7782..2efc9e336 100644 --- a/Makefile +++ b/Makefile @@ -19,14 +19,17 @@ deploy-engine-opt :; forge script scripts/AaveV3ConfigEngine.s.sol:DeployEngine deploy-engine-arb :; forge script scripts/AaveV3ConfigEngine.s.sol:DeployEngineArb --rpc-url arbitrum --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-engine-pol :; forge script scripts/AaveV3ConfigEngine.s.sol:DeployEnginePol --rpc-url polygon --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-engine-ava :; forge script scripts/AaveV3ConfigEngine.s.sol:DeployEngineAva --rpc-url avalanche --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv +deploy-engine-met :; forge script scripts/AaveV3ConfigEngine.s.sol:DeployEngineMet --rpc-url metis --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-rates-factory-eth :; forge script scripts/V3RateStrategyFactory.s.sol:DeployRatesFactoryEth --rpc-url mainnet --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-rates-factory-pol :; forge script scripts/V3RateStrategyFactory.s.sol:DeployRatesFactoryPol --rpc-url polygon --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-rates-factory-opt :; forge script scripts/V3RateStrategyFactory.s.sol:DeployRatesFactoryOpt --rpc-url optimism --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-rates-factory-arb :; forge script scripts/V3RateStrategyFactory.s.sol:DeployRatesFactoryArb --rpc-url arbitrum --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-rates-factory-ava :; forge script scripts/V3RateStrategyFactory.s.sol:DeployRatesFactoryAva --rpc-url avalanche --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv +deploy-rates-factory-met :; forge script scripts/V3RateStrategyFactory.s.sol:DeployRatesFactoryMet --rpc-url metis --broadcast --legacy --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-forwarder-pol :; forge script scripts/CrosschainForwarders.s.sol:DeployPol --rpc-url mainnet --broadcast --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-forwarder-opt :; forge script scripts/CrosschainForwarders.s.sol:DeployOpt --rpc-url mainnet --broadcast --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv deploy-forwarder-arb :; forge script scripts/CrosschainForwarders.s.sol:DeployArb --rpc-url mainnet --broadcast --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv +deploy-forwarder-met :; forge script scripts/CrosschainForwarders.s.sol:DeployMet --rpc-url mainnet --broadcast --ledger --mnemonic-indexes ${MNEMONIC_INDEX} --sender ${LEDGER_SENDER} --verify -vvvv # Utilities download :; cast etherscan-source --chain ${chain} -d src/etherscan/${chain}_${address} ${address} diff --git a/foundry.toml b/foundry.toml index 2248e1fab..603831aaa 100644 --- a/foundry.toml +++ b/foundry.toml @@ -17,6 +17,7 @@ polygon = "${RPC_POLYGON}" arbitrum = "${RPC_ARBITRUM}" fantom = "${RPC_FANTOM}" harmony = "${RPC_HARMONY}" +metis = "${RPC_METIS}" [etherscan] mainnet={key="${ETHERSCAN_API_KEY_MAINNET}",chainId=1} diff --git a/lib/aave-address-book b/lib/aave-address-book index ce1bf7bea..996112885 160000 --- a/lib/aave-address-book +++ b/lib/aave-address-book @@ -1 +1 @@ -Subproject commit ce1bf7bea81d2670bb20c8f255eceeb8eae881fb +Subproject commit 9961128857066c1cd1b5d906e7a334bd907e2729 diff --git a/lib/forge-std b/lib/forge-std index fc560fa34..9fec3a496 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit fc560fa34fa12a335a50c35d92e55a6628ca467c +Subproject commit 9fec3a49630d8086cac96ea8ff3a2137a23f96ed diff --git a/scripts/AaveV3ConfigEngine.s.sol b/scripts/AaveV3ConfigEngine.s.sol index fcdf3047d..880f8cdc6 100644 --- a/scripts/AaveV3ConfigEngine.s.sol +++ b/scripts/AaveV3ConfigEngine.s.sol @@ -9,6 +9,8 @@ import {AaveV3Optimism} from 'aave-address-book/AaveV3Optimism.sol'; import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol'; import {AaveV3Polygon} from 'aave-address-book/AaveV3Polygon.sol'; import {AaveV3Avalanche} from 'aave-address-book/AaveV3Avalanche.sol'; +import {AaveV3Metis} from 'aave-address-book/AaveV3Metis.sol'; +import {IPool, IPoolConfigurator, IAaveOracle} from 'aave-address-book/AaveV3.sol'; library DeployEngineEthLib { function deploy() internal returns (address) { @@ -22,7 +24,7 @@ library DeployEngineEthLib { AaveV3Ethereum.DEFAULT_VARIABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Ethereum.DEFAULT_STABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Ethereum.DEFAULT_INCENTIVES_CONTROLLER, - AaveV3Ethereum.COLLECTOR, + address(AaveV3Ethereum.COLLECTOR), IV3RateStrategyFactory(AaveV3Ethereum.RATES_FACTORY) ) ); @@ -41,7 +43,7 @@ library DeployEngineOptLib { AaveV3Optimism.DEFAULT_VARIABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Optimism.DEFAULT_STABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Optimism.DEFAULT_INCENTIVES_CONTROLLER, - AaveV3Optimism.COLLECTOR, + address(AaveV3Optimism.COLLECTOR), IV3RateStrategyFactory(AaveV3Optimism.RATES_FACTORY) ) ); @@ -60,7 +62,7 @@ library DeployEngineArbLib { AaveV3Arbitrum.DEFAULT_VARIABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Arbitrum.DEFAULT_STABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Arbitrum.DEFAULT_INCENTIVES_CONTROLLER, - AaveV3Arbitrum.COLLECTOR, + address(AaveV3Arbitrum.COLLECTOR), IV3RateStrategyFactory(AaveV3Arbitrum.RATES_FACTORY) ) ); @@ -79,7 +81,7 @@ library DeployEnginePolLib { AaveV3Polygon.DEFAULT_VARIABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Polygon.DEFAULT_STABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Polygon.DEFAULT_INCENTIVES_CONTROLLER, - AaveV3Polygon.COLLECTOR, + address(AaveV3Polygon.COLLECTOR), IV3RateStrategyFactory(AaveV3Polygon.RATES_FACTORY) ) ); @@ -98,13 +100,32 @@ library DeployEngineAvaLib { AaveV3Avalanche.DEFAULT_VARIABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Avalanche.DEFAULT_STABLE_DEBT_TOKEN_IMPL_REV_1, AaveV3Avalanche.DEFAULT_INCENTIVES_CONTROLLER, - AaveV3Avalanche.COLLECTOR, + address(AaveV3Avalanche.COLLECTOR), IV3RateStrategyFactory(AaveV3Avalanche.RATES_FACTORY) ) ); } } +library DeployEngineMetLib { + function deploy() internal returns (address) { + return + address( + new AaveV3ConfigEngine( + AaveV3Metis.POOL, + AaveV3Metis.POOL_CONFIGURATOR, + AaveV3Metis.ORACLE, + AaveV3Metis.DEFAULT_A_TOKEN_IMPL_REV_1, + AaveV3Metis.DEFAULT_VARIABLE_DEBT_TOKEN_IMPL_REV_1, + AaveV3Metis.DEFAULT_STABLE_DEBT_TOKEN_IMPL_REV_1, + AaveV3Metis.DEFAULT_INCENTIVES_CONTROLLER, + address(AaveV3Metis.COLLECTOR), + IV3RateStrategyFactory(AaveV3Metis.RATES_FACTORY) + ) + ); + } +} + contract DeployEngineEth is EthereumScript { function run() external broadcast { DeployEngineEthLib.deploy(); @@ -134,3 +155,9 @@ contract DeployEngineAva is AvalancheScript { DeployEngineAvaLib.deploy(); } } + +contract DeployEngineMet is MetisScript { + function run() external broadcast { + DeployEngineMetLib.deploy(); + } +} diff --git a/scripts/CrosschainForwarders.s.sol b/scripts/CrosschainForwarders.s.sol index ca508302c..a4fd05b76 100644 --- a/scripts/CrosschainForwarders.s.sol +++ b/scripts/CrosschainForwarders.s.sol @@ -5,6 +5,7 @@ import '../src/ScriptUtils.sol'; import {CrosschainForwarderPolygon} from '../src/crosschainforwarders/CrosschainForwarderPolygon.sol'; import {CrosschainForwarderOptimism} from '../src/crosschainforwarders/CrosschainForwarderOptimism.sol'; import {CrosschainForwarderArbitrum} from '../src/crosschainforwarders/CrosschainForwarderArbitrum.sol'; +import {CrosschainForwarderMetis} from '../src/crosschainforwarders/CrosschainForwarderMetis.sol'; contract DeployPol is EthereumScript { function run() external broadcast { @@ -23,3 +24,9 @@ contract DeployArb is EthereumScript { new CrosschainForwarderArbitrum(); } } + +contract DeployMet is EthereumScript { + function run() external broadcast { + new CrosschainForwarderMetis(); + } +} diff --git a/scripts/V3RateStrategyFactory.s.sol b/scripts/V3RateStrategyFactory.s.sol index 4615e96f8..af9eebec4 100644 --- a/scripts/V3RateStrategyFactory.s.sol +++ b/scripts/V3RateStrategyFactory.s.sol @@ -10,6 +10,7 @@ import {AaveV3Optimism} from 'aave-address-book/AaveV3Optimism.sol'; import {AaveV3Arbitrum} from 'aave-address-book/AaveV3Arbitrum.sol'; import {AaveV3Polygon} from 'aave-address-book/AaveV3Polygon.sol'; import {AaveV3Avalanche} from 'aave-address-book/AaveV3Avalanche.sol'; +import {AaveV3Metis} from 'aave-address-book/AaveV3Metis.sol'; import {ITransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/interfaces/ITransparentProxyFactory.sol'; import {V3RateStrategyFactory} from '../src/v3-config-engine/V3RateStrategyFactory.sol'; @@ -128,6 +129,17 @@ library DeployRatesFactoryAvaLib { } } +library DeployRatesFactoryMetLib { + function deploy() internal returns (address, address[] memory) { + return + DeployRatesFactoryLib._createAndSetupRatesFactory( + AaveV3Metis.POOL_ADDRESSES_PROVIDER, + AaveMisc.TRANSPARENT_PROXY_FACTORY_METIS, + AaveMisc.PROXY_ADMIN_METIS + ); + } +} + contract DeployRatesFactoryEth is EthereumScript { function run() external broadcast { DeployRatesFactoryEthLib.deploy(); @@ -157,3 +169,9 @@ contract DeployRatesFactoryAva is AvalancheScript { DeployRatesFactoryAvaLib.deploy(); } } + +contract DeployRatesFactoryMet is MetisScript { + function run() external broadcast { + DeployRatesFactoryMetLib.deploy(); + } +} diff --git a/src/GovHelpers.sol b/src/GovHelpers.sol index b4cb1aa33..e2a159824 100644 --- a/src/GovHelpers.sol +++ b/src/GovHelpers.sol @@ -62,6 +62,14 @@ library GovHelpers { }); } + function buildMetis(address payloadAddress) internal pure returns (Payload memory) { + return + _buildL2({ + forwarder: AaveGovernanceV2.CROSSCHAIN_FORWARDER_METIS, + payloadAddress: payloadAddress + }); + } + function _buildL2(address forwarder, address payloadAddress) private pure @@ -338,7 +346,7 @@ contract MockExecutor { * @notice Non-standard functionality used to skip governance and just execute a payload. */ function execute(address payload) public { - (bool success, ) = address(payload).delegatecall(abi.encodeWithSignature('execute()')); + (bool success, ) = payload.delegatecall(abi.encodeWithSignature('execute()')); require(success, 'PROPOSAL_EXECUTION_FAILED'); } diff --git a/src/ScriptUtils.sol b/src/ScriptUtils.sol index 02c13c374..bfd4ea5b1 100644 --- a/src/ScriptUtils.sol +++ b/src/ScriptUtils.sol @@ -45,3 +45,7 @@ abstract contract FantomScript is WithChainIdValidation { abstract contract HarmonyScript is WithChainIdValidation { constructor() WithChainIdValidation(1666600000) {} } + +abstract contract MetisScript is WithChainIdValidation { + constructor() WithChainIdValidation(1088) {} +} diff --git a/src/crosschainforwarders/CrosschainForwarderMetis.sol b/src/crosschainforwarders/CrosschainForwarderMetis.sol new file mode 100644 index 000000000..965dcb139 --- /dev/null +++ b/src/crosschainforwarders/CrosschainForwarderMetis.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {ICrossDomainMessenger} from 'governance-crosschain-bridges/contracts/dependencies/optimism/interfaces/ICrossDomainMessenger.sol'; +import {IL2BridgeExecutor} from 'governance-crosschain-bridges/contracts/interfaces/IL2BridgeExecutor.sol'; + +/** + * @title A generic executor for proposals targeting the metis v3 pool + * @author BGD Labs + * @notice You can **only** use this executor when the metis payload has a `execute()` signature without parameters + * @notice You can **only** use this executor when the metis payload is expected to be executed via `DELEGATECALL` + * @notice The L2CrossDomainMessenger can **only** queue an action on metis with up to a max gas which is specified in `MAX_GAS_LIMIT`. + * It encodes and sends via the L2CrossDomainMessenger a message to queue for execution an action on L2, in the Aave METIS_BRIDGE_EXECUTOR. + */ +contract CrosschainForwarderMetis { + /** + * @dev The L1 Cross Domain Messenger contract sends messages from L1 to L2, and relays messages + * from L2 onto L1. In this contract it's used by the governance SHORT_EXECUTOR to send the encoded L2 queuing over the bridge. + */ + address public constant L1_CROSS_DOMAIN_MESSENGER_ADDRESS = + 0x081D1101855bD523bA69A9794e0217F0DB6323ff; + + /** + * @dev The metis bridge executor is a L2 governance execution contract. + * This contract allows queuing of proposals by allow listed addresses (in this case the L1 short executor). + * https://andromeda-explorer.metis.io/address/0x8EC77963068474a45016938Deb95E603Ca82a029 + */ + address public constant METIS_BRIDGE_EXECUTOR = AaveGovernanceV2.METIS_BRIDGE_EXECUTOR; + + /** + * @dev The gas limit of the queue transaction by the L2CrossDomainMessenger on L2. + * The limit seems reasonable considering the queue transaction, as all gas limits are prepaid. + */ + uint32 public constant MAX_GAS_LIMIT = 5_000_000; + + /** + * @dev this function will be executed once the proposal passes the mainnet vote. + * @param l2PayloadContract the metis contract containing the `execute()` signature. + */ + function execute(address l2PayloadContract) public { + address[] memory targets = new address[](1); + targets[0] = l2PayloadContract; + uint256[] memory values = new uint256[](1); + values[0] = 0; + string[] memory signatures = new string[](1); + signatures[0] = 'execute()'; + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = ''; + bool[] memory withDelegatecalls = new bool[](1); + withDelegatecalls[0] = true; + + bytes memory queue = abi.encodeWithSelector( + IL2BridgeExecutor.queue.selector, + targets, + values, + signatures, + calldatas, + withDelegatecalls + ); + ICrossDomainMessenger(L1_CROSS_DOMAIN_MESSENGER_ADDRESS).sendMessage( + METIS_BRIDGE_EXECUTOR, + queue, + MAX_GAS_LIMIT + ); + } +} diff --git a/src/crosschainforwarders/README.md b/src/crosschainforwarders/README.md index 4e6ed72b8..7d5d866b6 100644 --- a/src/crosschainforwarders/README.md +++ b/src/crosschainforwarders/README.md @@ -2,7 +2,7 @@ ## About -To simplify the process of creating a cross-chain proposal this repository contains opinionated `CrosschainForwarder` contracts for `Polygon`, `Optimism` and `Arbitrum` abstracting away the complexity of bridging & cross-chain gas calculations. +To simplify the process of creating a cross-chain proposal this repository contains opinionated `CrosschainForwarder` contracts for `Polygon`, `Optimism`, `Metis` and `Arbitrum` abstracting away the complexity of bridging & cross-chain gas calculations. All the forwarders follow the same pattern. They expect a payload to be deployed on L2 and to be executed with a parameterless `execute()` signature and via `DELEGATECALL`. ![visualization](./bridge-listing.png) @@ -17,6 +17,13 @@ Once the state is synced to `FX_CHILD` on Polygon network it will queue the payl For a proposal to be executed on Optimism it needs to pass a mainnet governance proposal that sends an encoded payload via `sendMessage(address,bytes,uint32)` on [L1_CROSS_DOMAIN_MESSENGER](https://etherscan.io/address/0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1)(mainnet) to [L2_CROSS_DOMAIN_MESSENGER](https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000007#code)(Optimism). Once the state is `L2_CROSS_DOMAIN_MESSENGER` on Optimism it will queue the payload on [OPTIMISM_BRIDGE_EXECUTOR](https://optimistic.etherscan.io/address/0x7d9103572bE58FfE99dc390E8246f02dcAe6f611). +### Metis + +Similar to Optimism, for a proposal to be executed on Metis it needs to pass a mainnet governance proposal that sends an encoded payload via `sendMessage(address,bytes,uint32)` on [L1_CROSS_DOMAIN_MESSENGER](https://etherscan.io/address/0x081D1101855bD523bA69A9794e0217F0DB6323ff)(mainnet) to [L2_CROSS_DOMAIN_MESSENGER](https://andromeda-explorer.metis.io/address/0x4200000000000000000000000000000000000007)(Metis). +Once the state is `L2_CROSS_DOMAIN_MESSENGER` on Metis it will queue the payload on [METIS_BRIDGE_EXECUTOR](https://andromeda-explorer.metis.io/address/0x8EC77963068474a45016938Deb95E603Ca82a029). + +Caveat: Opposed to the other messenger like on Optimism, using the Metis messenger requires a whitelist by the Metis team. The [SHORT_EXECUTOR](https://etherscan.io/address/0xEE56e2B3D491590B5b31738cC34d5232F378a8D5) has been whitelisted to use the messenger. Also, the gas to queue the payload by the messenger has been hardcoded to 5 million, which seems reasonable considering the queue transaction. Unlike Optimism, the Metis messenger has no limit on gas, and everything is prepaid. + ### Arbitrum For a proposal to be executed on Arbitrum it needs to pass a mainnet governance proposal that sends an encoded payload via `unsafeCreateRetryableTicket{value: uint256}(address,uint256,uint256,address,address,uint256,uint256,bytes)` on [INBOX](https://etherscan.io/address/0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f)(mainnet). The Arbitrum bridge will then call the bridged calldata via the L2_ALIAS of the mainnet `msg.sender` (in this case is the aliased mainnet governance executor) which will queue the payload on [ARBITRUM_BRIDGE_EXECUTOR](https://arbiscan.io/address/0x7d9103572bE58FfE99dc390E8246f02dcAe6f611). @@ -31,6 +38,7 @@ You can check if you need to top-up the SHORT_EXECUTOR by calling `getRequiredGa - [CrosschainForwarderPolygon](https://etherscan.io/address/0x158a6bc04f0828318821bae797f50b0a1299d45b#code) - [CrosschainForwarderOptimism](https://etherscan.io/address/0x5f5c02875a8e9b5a26fbd09040abcfdeb2aa6711#code) +- [CrosschainForwarderMetis](https://etherscan.io/address/0x2fE52eF191F0BE1D98459BdaD2F1d3160336C08f#code) - [CrosschainForwarderArbitrum](https://etherscan.io/address/0x2e2B1F112C4D79A9D22464F0D345dE9b792705f1#code) ## References @@ -41,6 +49,9 @@ You can check if you need to top-up the SHORT_EXECUTOR by calling `getRequiredGa - [OptimismBridge: L1CrossDomainMessenger](https://etherscan.io/address/0x25ace71c97b33cc4729cf772ae268934f7ab5fa1#readProxyContract) - [OptimismBridge: OptimismBridgeExecutor](https://optimistic.etherscan.io/address/0x7d9103572be58ffe99dc390e8246f02dcae6f611#code) +- [MetisBridge: L1CrossDomainMessenger](https://etherscan.io/address/0x081D1101855bD523bA69A9794e0217F0DB6323ff#code) +- [MetisBridge: MetisBridgeExecutor](https://andromeda-explorer.metis.io/address/0x8EC77963068474a45016938Deb95E603Ca82a029/contracts#address-tabs) + - [ArbitrumBridge: Inbox](https://etherscan.io/address/0x4dbd4fc535ac27206064b68ffcf827b0a60bab3f#code) - [ArtitrumBridge: ArbitrumBridgeExecutor](https://arbiscan.io/address/0x7d9103572be58ffe99dc390e8246f02dcae6f611#code) diff --git a/src/test/crosschainforwarders/MetisCrossChainForwarderTest.t.sol b/src/test/crosschainforwarders/MetisCrossChainForwarderTest.t.sol new file mode 100644 index 000000000..ff72bc8b3 --- /dev/null +++ b/src/test/crosschainforwarders/MetisCrossChainForwarderTest.t.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; +import {AaveMisc} from 'aave-address-book/AaveAddressBook.sol'; +import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {IL2CrossDomainMessenger} from 'governance-crosschain-bridges/contracts/dependencies/optimism/interfaces/IL2CrossDomainMessenger.sol'; +import {GovHelpers} from '../../GovHelpers.sol'; +import {ProtocolV3TestBase} from '../../ProtocolV3TestBase.sol'; +import {BridgeExecutorHelpers} from '../../BridgeExecutorHelpers.sol'; +import {CrosschainForwarderMetis} from '../../crosschainforwarders/CrosschainForwarderMetis.sol'; +import {PayloadWithEmit} from '../mocks/PayloadWithEmit.sol'; + +/** + * This test covers syncing between mainnet and metis. + */ +contract MetisCrossChainForwarderTest is ProtocolV3TestBase { + event TestEvent(); + // the identifiers of the forks + uint256 mainnetFork; + uint256 metisFork; + + address public constant METIS_BRIDGE_EXECUTOR = AaveGovernanceV2.METIS_BRIDGE_EXECUTOR; + + IL2CrossDomainMessenger public OVM_L2_CROSS_DOMAIN_MESSENGER = + IL2CrossDomainMessenger(0x4200000000000000000000000000000000000007); + + PayloadWithEmit public payloadWithEmit; + + CrosschainForwarderMetis public forwarder; + + function setUp() public { + mainnetFork = vm.createSelectFork(vm.rpcUrl('mainnet'), 17093477); + forwarder = new CrosschainForwarderMetis(); + metisFork = vm.createSelectFork(vm.rpcUrl('metis'), 5428548); + payloadWithEmit = new PayloadWithEmit(); + } + + function testProposalE2E() public { + // 1. create l1 proposal + vm.selectFork(mainnetFork); + vm.startPrank(AaveMisc.ECOSYSTEM_RESERVE); + GovHelpers.Payload[] memory payloads = new GovHelpers.Payload[](1); + payloads[0] = GovHelpers.Payload({ + target: address(forwarder), + signature: 'execute(address)', + callData: abi.encode(address(payloadWithEmit)) + }); + + uint256 proposalId = GovHelpers.createProposal( + payloads, + 'ipfs' + ); + vm.stopPrank(); + + // 2. execute proposal and record logs so we can extract the emitted StateSynced event + vm.recordLogs(); + GovHelpers.passVoteAndExecute(vm, proposalId); + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(keccak256('SentMessage(address,address,bytes,uint256,uint256,uint256)'), entries[3].topics[0]); + assertEq(address(uint160(uint256(entries[3].topics[1]))), METIS_BRIDGE_EXECUTOR); + (address sender, bytes memory message, uint256 nonce) = abi.decode( + entries[3].data, + (address, bytes, uint256) + ); + + // 3. mock the receive on l2 with the data emitted on StateSynced + vm.selectFork(metisFork); + vm.startPrank(0x192E1101855bD523Ba69a9794e0217f0Db633510); // AddressAliasHelper.applyL1ToL2Alias on L1_CROSS_DOMAIN_MESSENGER_ADDRESS + OVM_L2_CROSS_DOMAIN_MESSENGER.relayMessage(METIS_BRIDGE_EXECUTOR, sender, message, nonce); + vm.stopPrank(); + + // 4. execute proposal on l2 + vm.expectEmit(true, true, true, true); + emit TestEvent(); + BridgeExecutorHelpers.waitAndExecuteLatest(vm, METIS_BRIDGE_EXECUTOR); + } +} diff --git a/src/v3-config-engine/AaveV3PayloadMetis.sol b/src/v3-config-engine/AaveV3PayloadMetis.sol new file mode 100644 index 000000000..6e0e78397 --- /dev/null +++ b/src/v3-config-engine/AaveV3PayloadMetis.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveV3Metis, AaveV3MetisAssets} from 'aave-address-book/AaveV3Metis.sol'; +import './AaveV3PayloadBase.sol'; + +/** + * @dev Base smart contract for an Aave v3.0.2 (compatible with 3.0.0) listing on v3 Metis. + * @author BGD Labs + */ +abstract contract AaveV3PayloadMetis is + AaveV3PayloadBase(IEngine(AaveV3Metis.LISTING_ENGINE)) +{ + function getPoolContext() public pure override returns (IEngine.PoolContext memory) { + return IEngine.PoolContext({networkName: 'Metis', networkAbbreviation: 'Met'}); + } +}