From 26f8b542e48697e6c5ac12df289cc000b529c6dd Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Fri, 7 Jun 2024 18:03:46 +0900 Subject: [PATCH 1/3] [SC-440] Refactor E2E (#16) * adding cctp support * complete circle cctp * remove get sender function as its pointless in this style of callback * add polygon and avalanche support * use more general language for cctp domain * rename some of the variables * more var renaming; use l2 authority * review fixes * convert domain into a struct + library; starting to split out bridges from the domains * more refactoring of bridge * part way through testing refactor * wip cctp * still wip for refactoring bridge testing * large refactor to split out domains and bridges and move into library structure * fix optimism * fix AMB * got arbitrum working * rm unused console --- src/testing/ArbitrumDomain.sol | 163 ---------------- src/testing/Bridge.sol | 15 ++ src/testing/BridgedDomain.sol | 16 -- src/testing/CircleCCTPDomain.sol | 106 ---------- src/testing/Domain.sol | 56 ++++-- src/testing/GnosisDomain.sol | 118 ----------- src/testing/OptimismDomain.sol | 116 ----------- src/testing/ZkEVMDomain.sol | 112 ----------- src/testing/bridges/AMBBridgeTesting.sol | 109 +++++++++++ src/testing/bridges/ArbitrumBridgeTesting.sol | 183 ++++++++++++++++++ src/testing/bridges/CCTPBridgeTesting.sol | 100 ++++++++++ src/testing/bridges/OptimismBridgeTesting.sol | 137 +++++++++++++ src/testing/{ => utils}/RecordedLogs.sol | 28 ++- test/ArbitrumIntegration.t.sol | 37 ++-- test/CCTPIntegration.t.sol | 51 +++-- test/GnosisIntegration.t.sol | 39 ++-- test/IntegrationBase.t.sol | 7 +- test/OptimismIntegration.t.sol | 35 ++-- test/ZkEVMIntegration.t.sol | 68 ------- 19 files changed, 704 insertions(+), 792 deletions(-) delete mode 100644 src/testing/ArbitrumDomain.sol create mode 100644 src/testing/Bridge.sol delete mode 100644 src/testing/BridgedDomain.sol delete mode 100644 src/testing/CircleCCTPDomain.sol delete mode 100644 src/testing/GnosisDomain.sol delete mode 100644 src/testing/OptimismDomain.sol delete mode 100644 src/testing/ZkEVMDomain.sol create mode 100644 src/testing/bridges/AMBBridgeTesting.sol create mode 100644 src/testing/bridges/ArbitrumBridgeTesting.sol create mode 100644 src/testing/bridges/CCTPBridgeTesting.sol create mode 100644 src/testing/bridges/OptimismBridgeTesting.sol rename src/testing/{ => utils}/RecordedLogs.sol (56%) delete mode 100644 test/ZkEVMIntegration.t.sol diff --git a/src/testing/ArbitrumDomain.sol b/src/testing/ArbitrumDomain.sol deleted file mode 100644 index a0546db..0000000 --- a/src/testing/ArbitrumDomain.sol +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { StdChains } from "forge-std/StdChains.sol"; -import { Vm } from "forge-std/Vm.sol"; - -import { Domain, BridgedDomain } from "./BridgedDomain.sol"; -import { RecordedLogs } from "./RecordedLogs.sol"; - -interface InboxLike { - function createRetryableTicket( - address destAddr, - uint256 arbTxCallValue, - uint256 maxSubmissionCost, - address submissionRefundAddress, - address valueRefundAddress, - uint256 maxGas, - uint256 gasPriceBid, - bytes calldata data - ) external payable returns (uint256); - function bridge() external view returns (address); -} - -interface BridgeLike { - function rollup() external view returns (address); - function executeCall( - address, - uint256, - bytes calldata - ) external returns (bool, bytes memory); - function setOutbox(address, bool) external; -} - -contract ArbSysOverride { - - event SendTxToL1(address sender, address target, bytes data); - - function sendTxToL1(address target, bytes calldata message) external payable returns (uint256) { - emit SendTxToL1(msg.sender, target, message); - return 0; - } - -} - -contract ArbitrumDomain is BridgedDomain { - - bytes32 private constant MESSAGE_DELIVERED_TOPIC = keccak256("MessageDelivered(uint256,bytes32,address,uint8,address,bytes32,uint256,uint64)"); - bytes32 private constant SEND_TO_L1_TOPIC = keccak256("SendTxToL1(address,address,bytes)"); - - address public constant ARB_SYS = 0x0000000000000000000000000000000000000064; - InboxLike public INBOX; - BridgeLike public immutable BRIDGE; - - address public l2ToL1Sender; - - uint256 internal lastFromHostLogIndex; - uint256 internal lastToHostLogIndex; - - constructor(StdChains.Chain memory _chain, Domain _hostDomain) Domain(_chain) BridgedDomain(_hostDomain) { - bytes32 name = keccak256(bytes(_chain.chainAlias)); - if (name == keccak256("arbitrum_one")) { - INBOX = InboxLike(0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f); - } else if (name == keccak256("arbitrum_one_goerli")) { - INBOX = InboxLike(0x6BEbC4925716945D46F0Ec336D5C2564F419682C); - } else if (name == keccak256("arbitrum_nova")) { - INBOX = InboxLike(0xc4448b71118c9071Bcb9734A0EAc55D18A153949); - } else { - revert("Unsupported chain"); - } - - _hostDomain.selectFork(); - BRIDGE = BridgeLike(INBOX.bridge()); - vm.recordLogs(); - - // Make this contract a valid outbox - address _rollup = BRIDGE.rollup(); - vm.store( - address(BRIDGE), - bytes32(uint256(8)), - bytes32(uint256(uint160(address(this)))) - ); - BRIDGE.setOutbox(address(this), true); - vm.store( - address(BRIDGE), - bytes32(uint256(8)), - bytes32(uint256(uint160(_rollup))) - ); - - // Need to replace ArbSys contract with custom code to make it compatible with revm - selectFork(); - bytes memory bytecode = vm.getCode("ArbitrumDomain.sol:ArbSysOverride"); - address deployed; - assembly { - deployed := create(0, add(bytecode, 0x20), mload(bytecode)) - } - vm.etch(ARB_SYS, deployed.code); - - _hostDomain.selectFork(); - } - - function parseData(bytes memory orig) private pure returns (address target, bytes memory message) { - // FIXME - this is not robust enough, only handling messages of a specific format - uint256 mlen; - (,,target ,,,,,,,, mlen) = abi.decode(orig, (uint256, uint256, address, uint256, uint256, uint256, address, address, uint256, uint256, uint256)); - message = new bytes(mlen); - for (uint256 i = 0; i < mlen; i++) { - message[i] = orig[i + 352]; - } - } - - function relayFromHost(bool switchToGuest) external override { - selectFork(); - - // Read all L1 -> L2 messages and relay them under Arbitrum fork - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastFromHostLogIndex < logs.length; lastFromHostLogIndex++) { - Vm.Log memory log = logs[lastFromHostLogIndex]; - if (log.topics[0] == MESSAGE_DELIVERED_TOPIC) { - // We need both the current event and the one that follows for all the relevant data - Vm.Log memory logWithData = logs[lastFromHostLogIndex + 1]; - (,, address sender,,,) = abi.decode(log.data, (address, uint8, address, bytes32, uint256, uint64)); - (address target, bytes memory message) = parseData(logWithData.data); - vm.startPrank(sender); - (bool success, bytes memory response) = target.call(message); - vm.stopPrank(); - if (!success) { - assembly { - revert(add(response, 32), mload(response)) - } - } - } - } - - if (!switchToGuest) { - hostDomain.selectFork(); - } - } - - function relayToHost(bool switchToHost) external override { - hostDomain.selectFork(); - - // Read all L2 -> L1 messages and relay them under host fork - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastToHostLogIndex < logs.length; lastToHostLogIndex++) { - Vm.Log memory log = logs[lastToHostLogIndex]; - if (log.topics[0] == SEND_TO_L1_TOPIC) { - (address sender, address target, bytes memory message) = abi.decode(log.data, (address, address, bytes)); - l2ToL1Sender = sender; - (bool success, bytes memory response) = BRIDGE.executeCall(target, 0, message); - if (!success) { - assembly { - revert(add(response, 32), mload(response)) - } - } - } - } - - if (!switchToHost) { - selectFork(); - } - } - -} diff --git a/src/testing/Bridge.sol b/src/testing/Bridge.sol new file mode 100644 index 0000000..f5740fb --- /dev/null +++ b/src/testing/Bridge.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { Domain } from "./Domain.sol"; + +struct Bridge { + Domain source; + Domain destination; + address sourceCrossChainMessenger; + address destinationCrossChainMessenger; + // These are used internally for log tracking + uint256 lastSourceLogIndex; + uint256 lastDestinationLogIndex; + bytes extraData; +} diff --git a/src/testing/BridgedDomain.sol b/src/testing/BridgedDomain.sol deleted file mode 100644 index 78a1a55..0000000 --- a/src/testing/BridgedDomain.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { Domain } from "./Domain.sol"; - -abstract contract BridgedDomain is Domain { - - Domain public immutable hostDomain; - - constructor(Domain _hostDomain) { - hostDomain = _hostDomain; - } - - function relayFromHost(bool switchToGuest) external virtual; - function relayToHost(bool switchToHost) external virtual; -} diff --git a/src/testing/CircleCCTPDomain.sol b/src/testing/CircleCCTPDomain.sol deleted file mode 100644 index 93591ab..0000000 --- a/src/testing/CircleCCTPDomain.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { StdChains } from "forge-std/StdChains.sol"; -import { Vm } from "forge-std/Vm.sol"; - -import { Domain, BridgedDomain } from "./BridgedDomain.sol"; -import { RecordedLogs } from "./RecordedLogs.sol"; - -interface MessengerLike { - function receiveMessage(bytes calldata message, bytes calldata attestation) external returns (bool success); -} - -contract CircleCCTPDomain is BridgedDomain { - - bytes32 private constant SENT_MESSAGE_TOPIC = keccak256("MessageSent(bytes)"); - - MessengerLike public SOURCE_MESSENGER; - MessengerLike public DESTINATION_MESSENGER; - - uint256 internal lastFromHostLogIndex; - uint256 internal lastToHostLogIndex; - - constructor(StdChains.Chain memory _chain, Domain _hostDomain) Domain(_chain) BridgedDomain(_hostDomain) { - SOURCE_MESSENGER = MessengerLike(_getMessengerFromChainAlias(_hostDomain.details().chainAlias)); - DESTINATION_MESSENGER = MessengerLike(_getMessengerFromChainAlias(_chain.chainAlias)); - - // Set minimum required signatures to zero for both domains - selectFork(); - vm.store( - address(DESTINATION_MESSENGER), - bytes32(uint256(4)), - 0 - ); - hostDomain.selectFork(); - vm.store( - address(SOURCE_MESSENGER), - bytes32(uint256(4)), - 0 - ); - - vm.recordLogs(); - } - - function _getMessengerFromChainAlias(string memory chainAlias) internal pure returns (address) { - bytes32 name = keccak256(bytes(chainAlias)); - if (name == keccak256("mainnet")) { - return 0x0a992d191DEeC32aFe36203Ad87D7d289a738F81; - } else if (name == keccak256("avalanche")) { - return 0x8186359aF5F57FbB40c6b14A588d2A59C0C29880; - } else if (name == keccak256("optimism")) { - return 0x4D41f22c5a0e5c74090899E5a8Fb597a8842b3e8; - } else if (name == keccak256("arbitrum_one")) { - return 0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca; - } else if (name == keccak256("base")) { - return 0xAD09780d193884d503182aD4588450C416D6F9D4; - } else if (name == keccak256("polygon")) { - return 0xF3be9355363857F3e001be68856A2f96b4C39Ba9; - } else { - revert("Unsupported chain"); - } - } - - function relayFromHost(bool switchToGuest) external override { - selectFork(); - - // Read all L1 -> L2 messages and relay them under CCTP fork - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastFromHostLogIndex < logs.length; lastFromHostLogIndex++) { - Vm.Log memory log = logs[lastFromHostLogIndex]; - if (log.topics[0] == SENT_MESSAGE_TOPIC && log.emitter == address(SOURCE_MESSENGER)) { - DESTINATION_MESSENGER.receiveMessage(removeFirst64Bytes(log.data), ""); - } - } - - if (!switchToGuest) { - hostDomain.selectFork(); - } - } - - function relayToHost(bool switchToHost) external override { - hostDomain.selectFork(); - - // Read all L2 -> L1 messages and relay them under host fork - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastToHostLogIndex < logs.length; lastToHostLogIndex++) { - Vm.Log memory log = logs[lastToHostLogIndex]; - if (log.topics[0] == SENT_MESSAGE_TOPIC && log.emitter == address(DESTINATION_MESSENGER)) { - SOURCE_MESSENGER.receiveMessage(removeFirst64Bytes(log.data), ""); - } - } - - if (!switchToHost) { - selectFork(); - } - } - - function removeFirst64Bytes(bytes memory inputData) public pure returns (bytes memory) { - bytes memory returnValue = new bytes(inputData.length - 64); - for (uint256 i = 0; i < inputData.length - 64; i++) { - returnValue[i] = inputData[i + 64]; - } - return returnValue; - } - -} diff --git a/src/testing/Domain.sol b/src/testing/Domain.sol index 6d48bf8..d7492a2 100644 --- a/src/testing/Domain.sol +++ b/src/testing/Domain.sol @@ -4,30 +4,56 @@ pragma solidity >=0.8.0; import { StdChains } from "forge-std/StdChains.sol"; import { Vm } from "forge-std/Vm.sol"; -contract Domain { +struct Domain { + StdChains.Chain chain; + uint256 forkId; +} + +library DomainHelpers { - Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function createFork(StdChains.Chain memory chain, uint256 blockNumber) internal returns (Domain memory domain) { + domain = Domain({ + chain: chain, + forkId: vm.createFork(chain.rpcUrl, blockNumber) + }); + } - StdChains.Chain private _details; - uint256 public forkId; + function createFork(StdChains.Chain memory chain) internal returns (Domain memory domain) { + domain = Domain({ + chain: chain, + forkId: vm.createFork(chain.rpcUrl) + }); + } + + function createSelectFork(StdChains.Chain memory chain, uint256 blockNumber) internal returns (Domain memory domain) { + domain = Domain({ + chain: chain, + forkId: vm.createSelectFork(chain.rpcUrl, blockNumber) + }); + _assertExpectedRpc(chain); + } - constructor(StdChains.Chain memory _chain) { - _details = _chain; - forkId = vm.createFork(_chain.rpcUrl); - vm.makePersistent(address(this)); + function createSelectFork(StdChains.Chain memory chain) internal returns (Domain memory domain) { + domain = Domain({ + chain: chain, + forkId: vm.createSelectFork(chain.rpcUrl) + }); + _assertExpectedRpc(chain); } - function details() public view returns (StdChains.Chain memory) { - return _details; + function selectFork(Domain memory domain) internal { + vm.selectFork(domain.forkId); + _assertExpectedRpc(domain.chain); } - function selectFork() public { - vm.selectFork(forkId); - require(block.chainid == _details.chainId, string(abi.encodePacked(_details.chainAlias, " is pointing to the wrong RPC endpoint '", _details.rpcUrl, "'"))); + function rollFork(Domain memory domain, uint256 blockNumber) internal { + vm.rollFork(domain.forkId, blockNumber); } - function rollFork(uint256 blocknum) public { - vm.rollFork(forkId, blocknum); + function _assertExpectedRpc(StdChains.Chain memory chain) private view { + require(block.chainid == chain.chainId, string(abi.encodePacked(chain.chainAlias, " is pointing to the wrong RPC endpoint '", chain.rpcUrl, "'"))); } } diff --git a/src/testing/GnosisDomain.sol b/src/testing/GnosisDomain.sol deleted file mode 100644 index 054cc03..0000000 --- a/src/testing/GnosisDomain.sol +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { StdChains } from "forge-std/StdChains.sol"; -import { Vm } from "forge-std/Vm.sol"; - -import { Domain, BridgedDomain } from "./BridgedDomain.sol"; -import { RecordedLogs } from "./RecordedLogs.sol"; - -interface IAMB { - function requireToPassMessage(address, bytes memory, uint256) external returns (bytes32); - function validatorContract() external view returns (address); -} - -interface IHomeAMB is IAMB { - function executeAffirmation(bytes memory) external; -} - -interface IForeignAMB is IAMB { - function executeSignatures(bytes memory, bytes memory) external; -} - -interface IValidatorContract { - function validatorList() external view returns (address[] memory); - function requiredSignatures() external view returns (uint256); -} - -contract GnosisDomain is BridgedDomain { - - bytes32 private constant USER_REQUEST_FOR_AFFIRMATION_TOPIC = keccak256("UserRequestForAffirmation(bytes32,bytes)"); - bytes32 private constant USER_REQUEST_FOR_SIGNATURE_TOPIC = keccak256("UserRequestForSignature(bytes32,bytes)"); - - IForeignAMB public L1_AMB_CROSS_DOMAIN_MESSENGER; - IHomeAMB public L2_AMB_CROSS_DOMAIN_MESSENGER; - - uint256 internal lastFromHostLogIndex; - uint256 internal lastToHostLogIndex; - - constructor(StdChains.Chain memory _chain, Domain _hostDomain) Domain(_chain) BridgedDomain(_hostDomain) { - bytes32 name = keccak256(bytes(_chain.chainAlias)); - if (name == keccak256("gnosis_chain")) { - L1_AMB_CROSS_DOMAIN_MESSENGER = IForeignAMB(0x4C36d2919e407f0Cc2Ee3c993ccF8ac26d9CE64e); - L2_AMB_CROSS_DOMAIN_MESSENGER = IHomeAMB(0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59); - } else if (name == keccak256("chiado")) { - L1_AMB_CROSS_DOMAIN_MESSENGER = IForeignAMB(0x87A19d769D875964E9Cd41dDBfc397B2543764E6); - L2_AMB_CROSS_DOMAIN_MESSENGER = IHomeAMB(0x99Ca51a3534785ED619f46A79C7Ad65Fa8d85e7a); - } else { - revert("Unsupported chain"); - } - - hostDomain.selectFork(); - - // Set minimum required signatures on L1 to 0 - IValidatorContract validatorContract = IValidatorContract(L1_AMB_CROSS_DOMAIN_MESSENGER.validatorContract()); - vm.store( - address(validatorContract), - 0x8a247e09a5673bd4d93a4e76d8fb9553523aa0d77f51f3d576e7421f5295b9bc, - 0 - ); - - vm.recordLogs(); - } - - function relayFromHost(bool switchToGuest) external override { - selectFork(); // switch to Gnosis domain - - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastFromHostLogIndex < logs.length; lastFromHostLogIndex++) { - Vm.Log memory log = logs[lastFromHostLogIndex]; - if ( - log.topics[0] == USER_REQUEST_FOR_AFFIRMATION_TOPIC - && log.emitter == address(L1_AMB_CROSS_DOMAIN_MESSENGER) - ) { - IValidatorContract validatorContract = IValidatorContract(L2_AMB_CROSS_DOMAIN_MESSENGER.validatorContract()); - address[] memory validators = validatorContract.validatorList(); - uint256 requiredSignatures = validatorContract.requiredSignatures(); - bytes memory messageToRelay = removeFirst64Bytes(log.data); - for (uint256 i = 0; i < requiredSignatures; i++) { - vm.prank(validators[i]); - L2_AMB_CROSS_DOMAIN_MESSENGER.executeAffirmation(messageToRelay); - } - } - } - - if (!switchToGuest) { - hostDomain.selectFork(); - } - } - - function relayToHost(bool switchToHost) external override { - hostDomain.selectFork(); - - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastToHostLogIndex < logs.length; lastToHostLogIndex++) { - Vm.Log memory log = logs[lastToHostLogIndex]; - if ( - log.topics[0] == USER_REQUEST_FOR_SIGNATURE_TOPIC - && log.emitter == address(L2_AMB_CROSS_DOMAIN_MESSENGER) - ) { - bytes memory messageToRelay = removeFirst64Bytes(log.data); - L1_AMB_CROSS_DOMAIN_MESSENGER.executeSignatures(messageToRelay, abi.encodePacked(uint256(0))); - } - } - - if (!switchToHost) { - selectFork(); - } - } - - function removeFirst64Bytes(bytes memory inputData) public pure returns (bytes memory) { - bytes memory returnValue = new bytes(inputData.length - 64); - for (uint256 i = 0; i < inputData.length - 64; i++) { - returnValue[i] = inputData[i + 64]; - } - return returnValue; - } - -} diff --git a/src/testing/OptimismDomain.sol b/src/testing/OptimismDomain.sol deleted file mode 100644 index 90b6297..0000000 --- a/src/testing/OptimismDomain.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { StdChains } from "forge-std/StdChains.sol"; -import { Vm } from "forge-std/Vm.sol"; - -import { Domain, BridgedDomain } from "./BridgedDomain.sol"; -import { RecordedLogs } from "./RecordedLogs.sol"; - -interface MessengerLike { - function sendMessage( - address target, - bytes memory message, - uint32 gasLimit - ) external; - function relayMessage( - uint256 _nonce, - address _sender, - address _target, - uint256 _value, - uint256 _minGasLimit, - bytes calldata _message - ) external payable; -} - -contract OptimismDomain is BridgedDomain { - - bytes32 private constant SENT_MESSAGE_TOPIC = keccak256("SentMessage(address,address,bytes,uint256,uint256)"); - uint160 private constant OFFSET = uint160(0x1111000000000000000000000000000000001111); - - MessengerLike public L1_MESSENGER; - MessengerLike public constant L2_MESSENGER = MessengerLike(0x4200000000000000000000000000000000000007); - - uint256 internal lastFromHostLogIndex; - uint256 internal lastToHostLogIndex; - - constructor(StdChains.Chain memory _chain, Domain _hostDomain) Domain(_chain) BridgedDomain(_hostDomain) { - bytes32 name = keccak256(bytes(_chain.chainAlias)); - if (name == keccak256("optimism")) { - L1_MESSENGER = MessengerLike(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1); - } else if (name == keccak256("optimism_goerli")) { - L1_MESSENGER = MessengerLike(0x5086d1eEF304eb5284A0f6720f79403b4e9bE294); - } else if (name == keccak256("base")) { - L1_MESSENGER = MessengerLike(0x866E82a600A1414e583f7F13623F1aC5d58b0Afa); - } else if (name == keccak256("base_goerli")) { - L1_MESSENGER = MessengerLike(0x8e5693140eA606bcEB98761d9beB1BC87383706D); - } else { - revert("Unsupported chain"); - } - - vm.recordLogs(); - } - - function relayFromHost(bool switchToGuest) external override { - selectFork(); - address malias; - unchecked { - malias = address(uint160(address(L1_MESSENGER)) + OFFSET); - } - - // Read all L1 -> L2 messages and relay them under Optimism fork - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastFromHostLogIndex < logs.length; lastFromHostLogIndex++) { - Vm.Log memory log = logs[lastFromHostLogIndex]; - if (log.topics[0] == SENT_MESSAGE_TOPIC && log.emitter == address(L1_MESSENGER)) { - address target = address(uint160(uint256(log.topics[1]))); - (address sender, bytes memory message, uint256 nonce, uint256 gasLimit) = abi.decode(log.data, (address, bytes, uint256, uint256)); - vm.prank(malias); - L2_MESSENGER.relayMessage(nonce, sender, target, 0, gasLimit, message); - } - } - - if (!switchToGuest) { - hostDomain.selectFork(); - } - } - - function relayToHost(bool switchToHost) external override { - hostDomain.selectFork(); - - // Read all L2 -> L1 messages and relay them under Primary fork - // Note: We bypass the L1 messenger relay here because it's easier to not have to generate valid state roots / merkle proofs - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastToHostLogIndex < logs.length; lastToHostLogIndex++) { - Vm.Log memory log = logs[lastToHostLogIndex]; - if (log.topics[0] == SENT_MESSAGE_TOPIC && log.emitter == address(L2_MESSENGER)) { - address target = address(uint160(uint256(log.topics[1]))); - (address sender, bytes memory message,,) = abi.decode(log.data, (address, bytes, uint256, uint256)); - // Set xDomainMessageSender - vm.store( - address(L1_MESSENGER), - bytes32(uint256(204)), - bytes32(uint256(uint160(sender))) - ); - vm.startPrank(address(L1_MESSENGER)); - (bool success, bytes memory response) = target.call(message); - vm.stopPrank(); - vm.store( - address(L1_MESSENGER), - bytes32(uint256(204)), - bytes32(uint256(0)) - ); - if (!success) { - assembly { - revert(add(response, 32), mload(response)) - } - } - } - } - - if (!switchToHost) { - selectFork(); - } - } - -} diff --git a/src/testing/ZkEVMDomain.sol b/src/testing/ZkEVMDomain.sol deleted file mode 100644 index dc851cf..0000000 --- a/src/testing/ZkEVMDomain.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { StdChains } from "forge-std/StdChains.sol"; -import { Vm } from "forge-std/Vm.sol"; - -import { Domain, BridgedDomain } from "./BridgedDomain.sol"; -import { RecordedLogs } from "./RecordedLogs.sol"; - -interface IBridgeMessageReceiver { - function onMessageReceived(address originAddress, uint32 originNetwork, bytes memory data) external payable; -} - -interface IZkEVMBridgeLike { - function bridgeMessage( - uint32 destinationNetwork, - address destinationAddress, - bool forceUpdateGlobalExitRoot, - bytes calldata metadata - ) external payable; -} - -contract ZkEVMDomain is BridgedDomain { - IZkEVMBridgeLike public L1_MESSENGER; - - bytes32 constant BRIDGE_EVENT_TOPIC = - keccak256("BridgeEvent(uint8,uint32,address,uint32,address,uint256,bytes,uint32)"); - - uint256 internal lastFromHostLogIndex; - uint256 internal lastToHostLogIndex; - - constructor(StdChains.Chain memory _chain, Domain _hostDomain) Domain(_chain) BridgedDomain(_hostDomain) { - bytes32 name = keccak256(bytes(_chain.chainAlias)); - if (name == keccak256("zkevm")) { - L1_MESSENGER = IZkEVMBridgeLike(0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe); - } else { - revert("Unsupported chain"); - } - vm.recordLogs(); - } - - function relayFromHost(bool switchToGuest) external override { - selectFork(); - - // Read all L1 -> L2 messages and relay them under zkevm fork - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastFromHostLogIndex < logs.length; lastFromHostLogIndex++) { - Vm.Log memory log = logs[lastFromHostLogIndex]; - if (_isBridgeMessageEvent(log, true)) _claimMessage(log); - } - - if (!switchToGuest) { - hostDomain.selectFork(); - } - } - - function relayToHost(bool switchToHost) external override { - hostDomain.selectFork(); - - // Read all L2 -> L1 messages and relay them under Primary fork - Vm.Log[] memory logs = RecordedLogs.getLogs(); - for (; lastToHostLogIndex < logs.length; lastToHostLogIndex++) { - Vm.Log memory log = logs[lastToHostLogIndex]; - if (_isBridgeMessageEvent(log, false)) _claimMessage(log); - } - - if (!switchToHost) { - selectFork(); - } - } - - function _isBridgeMessageEvent(Vm.Log memory log, bool host) internal view returns (bool) { - // early return to prevent abi decode errors - if (log.topics[0] != BRIDGE_EVENT_TOPIC) return false; - - (uint8 messageType, uint32 originNetwork,,,,,,) = - abi.decode(log.data, (uint8, uint32, address, uint32, address, uint256, bytes, uint32)); - return - log.emitter == address(L1_MESSENGER) && messageType == 1 && (host ? originNetwork == 0 : originNetwork == 1); - } - - function _claimMessage(Vm.Log memory log) internal { - ( - /* uint8 messageType */ - , - uint32 originNetwork, - address originAddress, - /* uint32 destinationNetwork */ - , - address destinationAddress, - uint256 msgValue, - bytes memory metadata, - /* uint32 depositCount */ - ) = abi.decode(log.data, (uint8, uint32, address, uint32, address, uint256, bytes, uint32)); - - // mock bridged eth balance increase - uint256 prevBalance = address(L1_MESSENGER).balance; - vm.deal(address(L1_MESSENGER), prevBalance + msgValue); - - // mock bridge callback - // ref: https://github.com/0xPolygonHermez/zkevm-contracts/blob/main/contracts/PolygonZkEVMBridge.sol#L455-L465 - vm.prank(address(L1_MESSENGER)); - (bool success, bytes memory response) = destinationAddress.call{value: msgValue}( - abi.encodeCall(IBridgeMessageReceiver.onMessageReceived, (originAddress, originNetwork, metadata)) - ); - if (!success) { - assembly { - revert(add(response, 32), mload(response)) - } - } - } -} diff --git a/src/testing/bridges/AMBBridgeTesting.sol b/src/testing/bridges/AMBBridgeTesting.sol new file mode 100644 index 0000000..e1b28c6 --- /dev/null +++ b/src/testing/bridges/AMBBridgeTesting.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { Vm } from "forge-std/Vm.sol"; + +import { Bridge } from "src/testing/Bridge.sol"; +import { Domain, DomainHelpers } from "src/testing/Domain.sol"; +import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; + +interface IAMB { + function validatorContract() external view returns (address); + function executeSignatures(bytes memory, bytes memory) external; + function executeAffirmation(bytes memory) external; +} + +interface IValidatorContract { + function validatorList() external view returns (address[] memory); + function requiredSignatures() external view returns (uint256); +} + +library AMBBridgeTesting { + + using DomainHelpers for *; + using RecordedLogs for *; + + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 private constant USER_REQUEST_FOR_AFFIRMATION_TOPIC = keccak256("UserRequestForAffirmation(bytes32,bytes)"); + bytes32 private constant USER_REQUEST_FOR_SIGNATURE_TOPIC = keccak256("UserRequestForSignature(bytes32,bytes)"); + + function createGnosisBridge(Domain memory source, Domain memory destination) internal returns (Bridge memory bridge) { + return init(Bridge({ + source: source, + destination: destination, + sourceCrossChainMessenger: getGnosisMessengerFromChainAlias(source.chain.chainAlias), + destinationCrossChainMessenger: getGnosisMessengerFromChainAlias(destination.chain.chainAlias), + lastSourceLogIndex: 0, + lastDestinationLogIndex: 0, + extraData: "" + })); + } + + function getGnosisMessengerFromChainAlias(string memory chainAlias) internal pure returns (address) { + bytes32 name = keccak256(bytes(chainAlias)); + if (name == keccak256("mainnet")) { + return 0x4C36d2919e407f0Cc2Ee3c993ccF8ac26d9CE64e; + } else if (name == keccak256("gnosis_chain")) { + return 0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59; + } else { + revert("Unsupported chain"); + } + } + + function init(Bridge memory bridge) internal returns (Bridge memory) { + vm.recordLogs(); + + // Set minimum required signatures to zero for both domains + bridge.destination.selectFork(); + vm.store( + IAMB(bridge.destinationCrossChainMessenger).validatorContract(), + 0x8a247e09a5673bd4d93a4e76d8fb9553523aa0d77f51f3d576e7421f5295b9bc, + 0 + ); + bridge.source.selectFork(); + vm.store( + IAMB(bridge.sourceCrossChainMessenger).validatorContract(), + 0x8a247e09a5673bd4d93a4e76d8fb9553523aa0d77f51f3d576e7421f5295b9bc, + 0 + ); + + return bridge; + } + + function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { + bridge.destination.selectFork(); + + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(true, USER_REQUEST_FOR_AFFIRMATION_TOPIC, USER_REQUEST_FOR_SIGNATURE_TOPIC, bridge.sourceCrossChainMessenger); + _relayAllMessages(logs, bridge.destinationCrossChainMessenger); + + if (!switchToDestinationFork) { + bridge.source.selectFork(); + } + } + + function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { + bridge.source.selectFork(); + + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, USER_REQUEST_FOR_AFFIRMATION_TOPIC, USER_REQUEST_FOR_SIGNATURE_TOPIC, bridge.destinationCrossChainMessenger); + _relayAllMessages(logs, bridge.sourceCrossChainMessenger); + + if (!switchToSourceFork) { + bridge.destination.selectFork(); + } + } + + function _relayAllMessages(Vm.Log[] memory logs, address amb) private { + for (uint256 i = 0; i < logs.length; i++) { + Vm.Log memory log = logs[i]; + bytes memory messageToRelay = abi.decode(log.data, (bytes)); + if (log.topics[0] == USER_REQUEST_FOR_AFFIRMATION_TOPIC) { + vm.prank(IValidatorContract(IAMB(amb).validatorContract()).validatorList()[0]); + IAMB(amb).executeAffirmation(messageToRelay); + } else if (log.topics[0] == USER_REQUEST_FOR_SIGNATURE_TOPIC) { + IAMB(amb).executeSignatures(messageToRelay, abi.encodePacked(uint256(0))); + } + } + } + +} diff --git a/src/testing/bridges/ArbitrumBridgeTesting.sol b/src/testing/bridges/ArbitrumBridgeTesting.sol new file mode 100644 index 0000000..073d40e --- /dev/null +++ b/src/testing/bridges/ArbitrumBridgeTesting.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { Vm } from "forge-std/Vm.sol"; + +import { Bridge } from "src/testing/Bridge.sol"; +import { Domain, DomainHelpers } from "src/testing/Domain.sol"; +import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; + +interface InboxLike { + function createRetryableTicket( + address destAddr, + uint256 arbTxCallValue, + uint256 maxSubmissionCost, + address submissionRefundAddress, + address valueRefundAddress, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable returns (uint256); + function bridge() external view returns (BridgeLike); +} + +interface BridgeLike { + function rollup() external view returns (address); + function executeCall( + address, + uint256, + bytes calldata + ) external returns (bool, bytes memory); + function setOutbox(address, bool) external; +} + +contract ArbSysOverride { + + event SendTxToL1(address sender, address target, bytes data); + + function sendTxToL1(address target, bytes calldata message) external payable returns (uint256) { + emit SendTxToL1(msg.sender, target, message); + return 0; + } + +} + +library ArbitrumBridgeTesting { + + using DomainHelpers for *; + using RecordedLogs for *; + + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 private constant MESSAGE_DELIVERED_TOPIC = keccak256("MessageDelivered(uint256,bytes32,address,uint8,address,bytes32,uint256,uint64)"); + bytes32 private constant SEND_TO_L1_TOPIC = keccak256("SendTxToL1(address,address,bytes)"); + + function createNativeBridge(Domain memory ethereum, Domain memory arbitrumInstance) internal returns (Bridge memory bridge) { + ( + address sourceCrossChainMessenger, + address destinationCrossChainMessenger + ) = getMessengerFromChainAlias(ethereum.chain.chainAlias, arbitrumInstance.chain.chainAlias); + + return init(Bridge({ + source: ethereum, + destination: arbitrumInstance, + sourceCrossChainMessenger: sourceCrossChainMessenger, + destinationCrossChainMessenger: destinationCrossChainMessenger, + lastSourceLogIndex: 0, + lastDestinationLogIndex: 0, + extraData: "" + })); + } + + function getMessengerFromChainAlias( + string memory sourceChainAlias, + string memory destinationChainAlias + ) internal pure returns ( + address sourceCrossChainMessenger, + address destinationCrossChainMessenger + ) { + require(keccak256(bytes(sourceChainAlias)) == keccak256("mainnet"), "Source must be Ethereum."); + + bytes32 name = keccak256(bytes(destinationChainAlias)); + if (name == keccak256("arbitrum_one")) { + sourceCrossChainMessenger = 0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f; + } else if (name == keccak256("arbitrum_nova")) { + sourceCrossChainMessenger = 0xc4448b71118c9071Bcb9734A0EAc55D18A153949; + } else { + revert("Unsupported destination chain"); + } + destinationCrossChainMessenger = 0x0000000000000000000000000000000000000064; + } + + function init(Bridge memory bridge) internal returns (Bridge memory) { + vm.recordLogs(); + + // Need to replace ArbSys contract with custom code to make it compatible with revm + bridge.destination.selectFork(); + bytes memory bytecode = vm.getCode("ArbitrumBridgeTesting.sol:ArbSysOverride"); + address deployed; + assembly { + deployed := create(0, add(bytecode, 0x20), mload(bytecode)) + } + vm.etch(bridge.destinationCrossChainMessenger, deployed.code); + + bridge.source.selectFork(); + BridgeLike underlyingBridge = InboxLike(bridge.sourceCrossChainMessenger).bridge(); + bridge.extraData = abi.encode(address(underlyingBridge)); + + // Make this contract a valid outbox + address _rollup = underlyingBridge.rollup(); + vm.store( + address(underlyingBridge), + bytes32(uint256(8)), + bytes32(uint256(uint160(address(this)))) + ); + underlyingBridge.setOutbox(address(this), true); + vm.store( + address(underlyingBridge), + bytes32(uint256(8)), + bytes32(uint256(uint160(_rollup))) + ); + + return bridge; + } + + function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { + bridge.destination.selectFork(); + + Vm.Log[] memory logs = RecordedLogs.getLogs(); + for (; bridge.lastSourceLogIndex < logs.length; bridge.lastSourceLogIndex++) { + Vm.Log memory log = logs[bridge.lastSourceLogIndex]; + if (log.topics[0] == MESSAGE_DELIVERED_TOPIC && log.emitter == abi.decode(bridge.extraData, (address))) { + // We need both the current event and the one that follows for all the relevant data + Vm.Log memory logWithData = logs[bridge.lastSourceLogIndex + 1]; + (,, address sender,,,) = abi.decode(log.data, (address, uint8, address, bytes32, uint256, uint64)); + (address target, bytes memory message) = _parseData(logWithData.data); + vm.startPrank(sender); + (bool success, bytes memory response) = target.call(message); + vm.stopPrank(); + if (!success) { + assembly { + revert(add(response, 32), mload(response)) + } + } + } + } + + if (!switchToDestinationFork) { + bridge.source.selectFork(); + } + } + + function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { + bridge.source.selectFork(); + + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SEND_TO_L1_TOPIC, bridge.destinationCrossChainMessenger); + for (uint256 i = 0; i < logs.length; i++) { + Vm.Log memory log = logs[i]; + (, address target, bytes memory message) = abi.decode(log.data, (address, address, bytes)); + //l2ToL1Sender = sender; + (bool success, bytes memory response) = InboxLike(bridge.sourceCrossChainMessenger).bridge().executeCall(target, 0, message); + if (!success) { + assembly { + revert(add(response, 32), mload(response)) + } + } + } + + if (!switchToSourceFork) { + bridge.destination.selectFork(); + } + } + + function _parseData(bytes memory orig) private pure returns (address target, bytes memory message) { + // FIXME - this is not robust enough, only handling messages of a specific format + uint256 mlen; + (,,target ,,,,,,,, mlen) = abi.decode(orig, (uint256, uint256, address, uint256, uint256, uint256, address, address, uint256, uint256, uint256)); + message = new bytes(mlen); + for (uint256 i = 0; i < mlen; i++) { + message[i] = orig[i + 352]; + } + } + +} diff --git a/src/testing/bridges/CCTPBridgeTesting.sol b/src/testing/bridges/CCTPBridgeTesting.sol new file mode 100644 index 0000000..229a2b2 --- /dev/null +++ b/src/testing/bridges/CCTPBridgeTesting.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { Vm } from "forge-std/Vm.sol"; + +import { Bridge } from "src/testing/Bridge.sol"; +import { Domain, DomainHelpers } from "src/testing/Domain.sol"; +import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; + +interface IMessenger { + function receiveMessage(bytes calldata message, bytes calldata attestation) external returns (bool success); +} + +library CCTPBridgeTesting { + + bytes32 private constant SENT_MESSAGE_TOPIC = keccak256("MessageSent(bytes)"); + + using DomainHelpers for *; + using RecordedLogs for *; + + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + function createCircleBridge(Domain memory source, Domain memory destination) internal returns (Bridge memory bridge) { + return init(Bridge({ + source: source, + destination: destination, + sourceCrossChainMessenger: getCircleMessengerFromChainAlias(source.chain.chainAlias), + destinationCrossChainMessenger: getCircleMessengerFromChainAlias(destination.chain.chainAlias), + lastSourceLogIndex: 0, + lastDestinationLogIndex: 0, + extraData: "" + })); + } + + function getCircleMessengerFromChainAlias(string memory chainAlias) internal pure returns (address) { + bytes32 name = keccak256(bytes(chainAlias)); + if (name == keccak256("mainnet")) { + return 0x0a992d191DEeC32aFe36203Ad87D7d289a738F81; + } else if (name == keccak256("avalanche")) { + return 0x8186359aF5F57FbB40c6b14A588d2A59C0C29880; + } else if (name == keccak256("optimism")) { + return 0x4D41f22c5a0e5c74090899E5a8Fb597a8842b3e8; + } else if (name == keccak256("arbitrum_one")) { + return 0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca; + } else if (name == keccak256("base")) { + return 0xAD09780d193884d503182aD4588450C416D6F9D4; + } else if (name == keccak256("polygon")) { + return 0xF3be9355363857F3e001be68856A2f96b4C39Ba9; + } else { + revert("Unsupported chain"); + } + } + + function init(Bridge memory bridge) internal returns (Bridge memory) { + // Set minimum required signatures to zero for both domains + bridge.destination.selectFork(); + vm.store( + bridge.destinationCrossChainMessenger, + bytes32(uint256(4)), + 0 + ); + bridge.source.selectFork(); + vm.store( + bridge.sourceCrossChainMessenger, + bytes32(uint256(4)), + 0 + ); + + vm.recordLogs(); + + return bridge; + } + + function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { + bridge.destination.selectFork(); + + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(true, SENT_MESSAGE_TOPIC, bridge.sourceCrossChainMessenger); + for (uint256 i = 0; i < logs.length; i++) { + IMessenger(bridge.destinationCrossChainMessenger).receiveMessage(abi.decode(logs[i].data, (bytes)), ""); + } + + if (!switchToDestinationFork) { + bridge.source.selectFork(); + } + } + + function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { + bridge.source.selectFork(); + + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SENT_MESSAGE_TOPIC, bridge.destinationCrossChainMessenger); + for (uint256 i = 0; i < logs.length; i++) { + IMessenger(bridge.sourceCrossChainMessenger).receiveMessage(abi.decode(logs[i].data, (bytes)), ""); + } + + if (!switchToSourceFork) { + bridge.destination.selectFork(); + } + } + +} diff --git a/src/testing/bridges/OptimismBridgeTesting.sol b/src/testing/bridges/OptimismBridgeTesting.sol new file mode 100644 index 0000000..97626a5 --- /dev/null +++ b/src/testing/bridges/OptimismBridgeTesting.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity >=0.8.0; + +import { Vm } from "forge-std/Vm.sol"; + +import { Bridge } from "src/testing/Bridge.sol"; +import { Domain, DomainHelpers } from "src/testing/Domain.sol"; +import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; + +interface IMessenger { + function sendMessage( + address target, + bytes memory message, + uint32 gasLimit + ) external; + function relayMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _minGasLimit, + bytes calldata _message + ) external payable; +} + +library OptimismBridgeTesting { + + using DomainHelpers for *; + using RecordedLogs for *; + + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 private constant SENT_MESSAGE_TOPIC = keccak256("SentMessage(address,address,bytes,uint256,uint256)"); + + function createNativeBridge(Domain memory ethereum, Domain memory optimismInstance) internal returns (Bridge memory bridge) { + ( + address sourceCrossChainMessenger, + address destinationCrossChainMessenger + ) = getMessengerFromChainAlias(ethereum.chain.chainAlias, optimismInstance.chain.chainAlias); + + return init(Bridge({ + source: ethereum, + destination: optimismInstance, + sourceCrossChainMessenger: sourceCrossChainMessenger, + destinationCrossChainMessenger: destinationCrossChainMessenger, + lastSourceLogIndex: 0, + lastDestinationLogIndex: 0, + extraData: "" + })); + } + + function getMessengerFromChainAlias( + string memory sourceChainAlias, + string memory destinationChainAlias + ) internal pure returns ( + address sourceCrossChainMessenger, + address destinationCrossChainMessenger + ) { + require(keccak256(bytes(sourceChainAlias)) == keccak256("mainnet"), "Source must be Ethereum."); + + bytes32 name = keccak256(bytes(destinationChainAlias)); + if (name == keccak256("optimism")) { + sourceCrossChainMessenger = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1; + } else if (name == keccak256("base")) { + sourceCrossChainMessenger = 0x866E82a600A1414e583f7F13623F1aC5d58b0Afa; + } else { + revert("Unsupported destination chain"); + } + destinationCrossChainMessenger = 0x4200000000000000000000000000000000000007; + } + + function init(Bridge memory bridge) internal returns (Bridge memory) { + vm.recordLogs(); + + // For consistency with other bridges + bridge.source.selectFork(); + + return bridge; + } + + function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { + bridge.destination.selectFork(); + + address malias; + unchecked { + malias = address(uint160(bridge.sourceCrossChainMessenger) + uint160(0x1111000000000000000000000000000000001111)); + } + + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(true, SENT_MESSAGE_TOPIC, bridge.sourceCrossChainMessenger); + for (uint256 i = 0; i < logs.length; i++) { + Vm.Log memory log = logs[i]; + address target = address(uint160(uint256(log.topics[1]))); + (address sender, bytes memory message, uint256 nonce, uint256 gasLimit) = abi.decode(log.data, (address, bytes, uint256, uint256)); + vm.prank(malias); + IMessenger(bridge.destinationCrossChainMessenger).relayMessage(nonce, sender, target, 0, gasLimit, message); + } + + if (!switchToDestinationFork) { + bridge.source.selectFork(); + } + } + + function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { + bridge.source.selectFork(); + + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SENT_MESSAGE_TOPIC, bridge.destinationCrossChainMessenger); + for (uint256 i = 0; i < logs.length; i++) { + Vm.Log memory log = logs[i]; + address target = address(uint160(uint256(log.topics[1]))); + (address sender, bytes memory message,,) = abi.decode(log.data, (address, bytes, uint256, uint256)); + // Set xDomainMessageSender + vm.store( + bridge.sourceCrossChainMessenger, + bytes32(uint256(204)), + bytes32(uint256(uint160(sender))) + ); + vm.startPrank(bridge.sourceCrossChainMessenger); + (bool success, bytes memory response) = target.call(message); + vm.stopPrank(); + vm.store( + bridge.sourceCrossChainMessenger, + bytes32(uint256(204)), + bytes32(uint256(0)) + ); + if (!success) { + assembly { + revert(add(response, 32), mload(response)) + } + } + } + + if (!switchToSourceFork) { + bridge.destination.selectFork(); + } + } + +} diff --git a/src/testing/RecordedLogs.sol b/src/testing/utils/RecordedLogs.sol similarity index 56% rename from src/testing/RecordedLogs.sol rename to src/testing/utils/RecordedLogs.sol index 5ca5826..4912a24 100644 --- a/src/testing/RecordedLogs.sol +++ b/src/testing/utils/RecordedLogs.sol @@ -3,9 +3,11 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; +import { Bridge } from "src/testing/Bridge.sol"; + library RecordedLogs { - Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); function getLogs() internal returns (Vm.Log[] memory) { string memory _logs = vm.serializeUint("RECORDED_LOGS", "a", 0); // this is the only way to get the logs from the memory object @@ -31,4 +33,28 @@ library RecordedLogs { return logs; } + function ingestAndFilterLogs(Bridge memory bridge, bool sourceToDestination, bytes32 topic0, bytes32 topic1, address emitter) internal returns (Vm.Log[] memory filteredLogs) { + Vm.Log[] memory logs = RecordedLogs.getLogs(); + uint256 lastIndex = sourceToDestination ? bridge.lastSourceLogIndex : bridge.lastDestinationLogIndex; + uint256 pushedIndex = 0; + + filteredLogs = new Vm.Log[](logs.length - lastIndex); + + for (; lastIndex < logs.length; lastIndex++) { + Vm.Log memory log = logs[lastIndex]; + if ((log.topics[0] == topic0 || log.topics[0] == topic1) && log.emitter == emitter) { + filteredLogs[pushedIndex++] = log; + } + } + + if (sourceToDestination) bridge.lastSourceLogIndex = lastIndex; + else bridge.lastDestinationLogIndex = lastIndex; + // Reduce the array length + assembly { mstore(filteredLogs, pushedIndex) } + } + + function ingestAndFilterLogs(Bridge memory bridge, bool sourceToDestination, bytes32 topic, address emitter) internal returns (Vm.Log[] memory filteredLogs) { + return ingestAndFilterLogs(bridge, sourceToDestination, topic, bytes32(0), emitter); + } + } diff --git a/test/ArbitrumIntegration.t.sol b/test/ArbitrumIntegration.t.sol index 441e879..6f63ac9 100644 --- a/test/ArbitrumIntegration.t.sol +++ b/test/ArbitrumIntegration.t.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import "./IntegrationBase.t.sol"; -import { ArbitrumDomain, ArbSysOverride } from "../src/testing/ArbitrumDomain.sol"; +import { ArbitrumBridgeTesting, ArbSysOverride } from "src/testing/bridges/ArbitrumBridgeTesting.sol"; -import { ArbitrumReceiver } from "../src/ArbitrumReceiver.sol"; +import { ArbitrumReceiver } from "src/ArbitrumReceiver.sol"; contract MessageOrderingArbitrum is MessageOrdering, ArbitrumReceiver { @@ -19,21 +19,24 @@ contract MessageOrderingArbitrum is MessageOrdering, ArbitrumReceiver { contract ArbitrumIntegrationTest is IntegrationBaseTest { + using ArbitrumBridgeTesting for *; + using DomainHelpers for *; + function test_arbitrumOne() public { - checkArbitrumStyle(new ArbitrumDomain(getChain("arbitrum_one"), mainnet)); + checkArbitrumStyle(getChain("arbitrum_one").createFork()); } function test_arbitrumNova() public { - checkArbitrumStyle(new ArbitrumDomain(getChain("arbitrum_nova"), mainnet)); + checkArbitrumStyle(getChain("arbitrum_nova").createFork()); } - function checkArbitrumStyle(ArbitrumDomain arbitrum) public { + function checkArbitrumStyle(Domain memory arbitrum) public { + Bridge memory bridge = ArbitrumBridgeTesting.createNativeBridge(mainnet, arbitrum); + deal(l1Authority, 100 ether); deal(notL1Authority, 100 ether); - Domain host = arbitrum.hostDomain(); - - host.selectFork(); + mainnet.selectFork(); MessageOrdering moHost = new MessageOrdering(); @@ -42,11 +45,11 @@ contract ArbitrumIntegrationTest is IntegrationBaseTest { MessageOrdering moArbitrum = new MessageOrderingArbitrum(l1Authority); // Queue up some L2 -> L1 messages - ArbSysOverride(arbitrum.ARB_SYS()).sendTxToL1( + ArbSysOverride(bridge.destinationCrossChainMessenger).sendTxToL1( address(moHost), abi.encodeWithSelector(MessageOrdering.push.selector, 3) ); - ArbSysOverride(arbitrum.ARB_SYS()).sendTxToL1( + ArbSysOverride(bridge.destinationCrossChainMessenger).sendTxToL1( address(moHost), abi.encodeWithSelector(MessageOrdering.push.selector, 4) ); @@ -54,12 +57,12 @@ contract ArbitrumIntegrationTest is IntegrationBaseTest { assertEq(moArbitrum.length(), 0); // Do not relay right away - host.selectFork(); + mainnet.selectFork(); // Queue up two more L1 -> L2 messages vm.startPrank(l1Authority); XChainForwarders.sendMessageArbitrum( - address(arbitrum.INBOX()), + bridge.sourceCrossChainMessenger, address(moArbitrum), abi.encodeWithSelector(MessageOrdering.push.selector, 1), 100000, @@ -67,7 +70,7 @@ contract ArbitrumIntegrationTest is IntegrationBaseTest { block.basefee + 10 gwei ); XChainForwarders.sendMessageArbitrum( - address(arbitrum.INBOX()), + bridge.sourceCrossChainMessenger, address(moArbitrum), abi.encodeWithSelector(MessageOrdering.push.selector, 2), 100000, @@ -78,13 +81,13 @@ contract ArbitrumIntegrationTest is IntegrationBaseTest { assertEq(moHost.length(), 0); - arbitrum.relayFromHost(true); + bridge.relayMessagesToDestination(true); assertEq(moArbitrum.length(), 2); assertEq(moArbitrum.messages(0), 1); assertEq(moArbitrum.messages(1), 2); - arbitrum.relayToHost(true); + bridge.relayMessagesToSource(true); assertEq(moHost.length(), 2); assertEq(moHost.messages(0), 3); @@ -93,7 +96,7 @@ contract ArbitrumIntegrationTest is IntegrationBaseTest { // Validate the message receiver failure mode vm.startPrank(notL1Authority); XChainForwarders.sendMessageArbitrum( - address(arbitrum.INBOX()), + bridge.sourceCrossChainMessenger, address(moArbitrum), abi.encodeWithSelector(MessageOrdering.push.selector, 999), 100000, @@ -103,7 +106,7 @@ contract ArbitrumIntegrationTest is IntegrationBaseTest { vm.stopPrank(); vm.expectRevert("Receiver/invalid-l1Authority"); - arbitrum.relayFromHost(true); + bridge.relayMessagesToDestination(true); } } diff --git a/test/CCTPIntegration.t.sol b/test/CCTPIntegration.t.sol index a3ed28d..9e04a54 100644 --- a/test/CCTPIntegration.t.sol +++ b/test/CCTPIntegration.t.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import "./IntegrationBase.t.sol"; -import { CircleCCTPDomain } from "../src/testing/CircleCCTPDomain.sol"; +import { CCTPBridgeTesting } from "src/testing/bridges/CCTPBridgeTesting.sol"; -import { CCTPReceiver } from "../src/CCTPReceiver.sol"; +import { CCTPReceiver } from "src/CCTPReceiver.sol"; contract MessageOrderingCCTP is MessageOrdering, CCTPReceiver { @@ -27,49 +27,48 @@ contract MessageOrderingCCTP is MessageOrdering, CCTPReceiver { contract CircleCCTPIntegrationTest is IntegrationBaseTest { + using CCTPBridgeTesting for *; + using DomainHelpers for *; + address l2Authority = makeAddr("l2Authority"); function test_avalanche() public { - CircleCCTPDomain cctp = new CircleCCTPDomain(getChain("avalanche"), mainnet); - checkCircleCCTPStyle(cctp, 1); + checkCircleCCTPStyle(getChain("avalanche").createFork(), 1); } function test_optimism() public { - CircleCCTPDomain cctp = new CircleCCTPDomain(getChain("optimism"), mainnet); - checkCircleCCTPStyle(cctp, 2); + checkCircleCCTPStyle(getChain("optimism").createFork(), 2); } function test_arbitrum_one() public { - CircleCCTPDomain cctp = new CircleCCTPDomain(getChain("arbitrum_one"), mainnet); - checkCircleCCTPStyle(cctp, 3); + checkCircleCCTPStyle(getChain("arbitrum_one").createFork(), 3); } function test_base() public { - CircleCCTPDomain cctp = new CircleCCTPDomain(getChain("base"), mainnet); - checkCircleCCTPStyle(cctp, 6); + checkCircleCCTPStyle(getChain("base").createFork(), 6); } function test_polygon() public { - CircleCCTPDomain cctp = new CircleCCTPDomain(getChain("polygon"), mainnet); - checkCircleCCTPStyle(cctp, 7); + checkCircleCCTPStyle(getChain("polygon").createFork(), 7); } - function checkCircleCCTPStyle(CircleCCTPDomain cctp, uint32 destinationDomainId) public { - Domain host = cctp.hostDomain(); + function checkCircleCCTPStyle(Domain memory destination, uint32 destinationDomainId) public { + Bridge memory bridge = CCTPBridgeTesting.createCircleBridge(mainnet, destination); + uint32 sourceDomainId = 0; // Ethereum - host.selectFork(); + mainnet.selectFork(); MessageOrderingCCTP moHost = new MessageOrderingCCTP( - address(cctp.SOURCE_MESSENGER()), + bridge.sourceCrossChainMessenger, destinationDomainId, l2Authority ); - cctp.selectFork(); + destination.selectFork(); MessageOrderingCCTP moCCTP = new MessageOrderingCCTP( - address(cctp.DESTINATION_MESSENGER()), + bridge.destinationCrossChainMessenger, sourceDomainId, l1Authority ); @@ -77,13 +76,13 @@ contract CircleCCTPIntegrationTest is IntegrationBaseTest { // Queue up some L2 -> L1 messages vm.startPrank(l2Authority); XChainForwarders.sendMessageCCTP( - address(cctp.DESTINATION_MESSENGER()), + bridge.destinationCrossChainMessenger, sourceDomainId, address(moHost), abi.encodeWithSelector(MessageOrdering.push.selector, 3) ); XChainForwarders.sendMessageCCTP( - address(cctp.DESTINATION_MESSENGER()), + bridge.destinationCrossChainMessenger, sourceDomainId, address(moHost), abi.encodeWithSelector(MessageOrdering.push.selector, 4) @@ -93,7 +92,7 @@ contract CircleCCTPIntegrationTest is IntegrationBaseTest { assertEq(moCCTP.length(), 0); // Do not relay right away - host.selectFork(); + mainnet.selectFork(); // Queue up two more L1 -> L2 messages vm.startPrank(l1Authority); @@ -111,13 +110,13 @@ contract CircleCCTPIntegrationTest is IntegrationBaseTest { assertEq(moHost.length(), 0); - cctp.relayFromHost(true); + bridge.relayMessagesToDestination(true); assertEq(moCCTP.length(), 2); assertEq(moCCTP.messages(0), 1); assertEq(moCCTP.messages(1), 2); - cctp.relayToHost(true); + bridge.relayMessagesToSource(true); assertEq(moHost.length(), 2); assertEq(moHost.messages(0), 3); @@ -133,9 +132,9 @@ contract CircleCCTPIntegrationTest is IntegrationBaseTest { vm.stopPrank(); vm.expectRevert("Receiver/invalid-sourceAuthority"); - cctp.relayFromHost(true); + bridge.relayMessagesToDestination(true); - cctp.selectFork(); + destination.selectFork(); vm.expectRevert("Receiver/invalid-sender"); moCCTP.push(999); @@ -143,7 +142,7 @@ contract CircleCCTPIntegrationTest is IntegrationBaseTest { moCCTP.handleReceiveMessage(0, bytes32(uint256(uint160(l1Authority))), abi.encodeWithSelector(MessageOrdering.push.selector, 999)); assertEq(moCCTP.sourceDomainId(), 0); - vm.prank(address(cctp.DESTINATION_MESSENGER())); + vm.prank(bridge.destinationCrossChainMessenger); vm.expectRevert("Receiver/invalid-sourceDomain"); moCCTP.handleReceiveMessage(1, bytes32(uint256(uint160(l1Authority))), abi.encodeWithSelector(MessageOrdering.push.selector, 999)); } diff --git a/test/GnosisIntegration.t.sol b/test/GnosisIntegration.t.sol index 0828082..c6b94f5 100644 --- a/test/GnosisIntegration.t.sol +++ b/test/GnosisIntegration.t.sol @@ -3,9 +3,13 @@ pragma solidity >=0.8.0; import "./IntegrationBase.t.sol"; -import { GnosisDomain } from "../src/testing/GnosisDomain.sol"; +import { AMBBridgeTesting } from "src/testing/bridges/AMBBridgeTesting.sol"; -import { GnosisReceiver } from "../src/GnosisReceiver.sol"; +import { GnosisReceiver } from "src/GnosisReceiver.sol"; + +interface IAMB { + function requireToPassMessage(address, bytes memory, uint256) external returns (bytes32); +} contract MessageOrderingGnosis is MessageOrdering, GnosisReceiver { @@ -18,30 +22,33 @@ contract MessageOrderingGnosis is MessageOrdering, GnosisReceiver { } contract GnosisIntegrationTest is IntegrationBaseTest { + + using AMBBridgeTesting for *; + using DomainHelpers for *; function test_gnosisChain() public { - checkGnosisStyle(new GnosisDomain(getChain('gnosis_chain'), mainnet), 0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59); + checkGnosisStyle(getChain('gnosis_chain').createFork()); } - function checkGnosisStyle(GnosisDomain gnosis, address _l2CrossDomain) public { - Domain host = gnosis.hostDomain(); + function checkGnosisStyle(Domain memory gnosis) public { + Bridge memory bridge = AMBBridgeTesting.createGnosisBridge(mainnet, gnosis); - host.selectFork(); + mainnet.selectFork(); MessageOrdering moHost = new MessageOrdering(); uint256 _chainId = block.chainid; gnosis.selectFork(); - MessageOrderingGnosis moGnosis = new MessageOrderingGnosis(_l2CrossDomain, _chainId, l1Authority); + MessageOrderingGnosis moGnosis = new MessageOrderingGnosis(bridge.destinationCrossChainMessenger, _chainId, l1Authority); // Queue up some L2 -> L1 messages - gnosis.L2_AMB_CROSS_DOMAIN_MESSENGER().requireToPassMessage( + IAMB(bridge.destinationCrossChainMessenger).requireToPassMessage( address(moHost), abi.encodeWithSelector(MessageOrdering.push.selector, 3), 100000 ); - gnosis.L2_AMB_CROSS_DOMAIN_MESSENGER().requireToPassMessage( + IAMB(bridge.destinationCrossChainMessenger).requireToPassMessage( address(moHost), abi.encodeWithSelector(MessageOrdering.push.selector, 4), 100000 @@ -50,18 +57,18 @@ contract GnosisIntegrationTest is IntegrationBaseTest { assertEq(moGnosis.length(), 0); // Do not relay right away - host.selectFork(); + mainnet.selectFork(); // Queue up two more L1 -> L2 messages vm.startPrank(l1Authority); XChainForwarders.sendMessageGnosis( - address(gnosis.L1_AMB_CROSS_DOMAIN_MESSENGER()), + bridge.sourceCrossChainMessenger, address(moGnosis), abi.encodeWithSelector(MessageOrdering.push.selector, 1), 100000 ); XChainForwarders.sendMessageGnosis( - address(gnosis.L1_AMB_CROSS_DOMAIN_MESSENGER()), + bridge.sourceCrossChainMessenger, address(moGnosis), abi.encodeWithSelector(MessageOrdering.push.selector, 2), 100000 @@ -70,13 +77,13 @@ contract GnosisIntegrationTest is IntegrationBaseTest { assertEq(moHost.length(), 0); - gnosis.relayFromHost(true); + bridge.relayMessagesToDestination(true); assertEq(moGnosis.length(), 2); assertEq(moGnosis.messages(0), 1); assertEq(moGnosis.messages(1), 2); - gnosis.relayToHost(true); + bridge.relayMessagesToSource(true); assertEq(moHost.length(), 2); assertEq(moHost.messages(0), 3); @@ -85,7 +92,7 @@ contract GnosisIntegrationTest is IntegrationBaseTest { // Validate the message receiver failure modes vm.startPrank(notL1Authority); XChainForwarders.sendMessageGnosis( - address(gnosis.L1_AMB_CROSS_DOMAIN_MESSENGER()), + bridge.sourceCrossChainMessenger, address(moGnosis), abi.encodeWithSelector(MessageOrdering.push.selector, 999), 100000 @@ -94,7 +101,7 @@ contract GnosisIntegrationTest is IntegrationBaseTest { // The revert is caught so it doesn't propagate // Just look at the no change to verify it didn't go through - gnosis.relayFromHost(true); + bridge.relayMessagesToDestination(true); assertEq(moGnosis.length(), 2); // No change gnosis.selectFork(); diff --git a/test/IntegrationBase.t.sol b/test/IntegrationBase.t.sol index b9df3e7..49405a4 100644 --- a/test/IntegrationBase.t.sol +++ b/test/IntegrationBase.t.sol @@ -3,7 +3,8 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import { Domain } from "../src/testing/Domain.sol"; +import { Bridge } from "src/testing/Bridge.sol"; +import { Domain, DomainHelpers } from "src/testing/Domain.sol"; import { XChainForwarders } from "../src/XChainForwarders.sol"; @@ -23,13 +24,15 @@ contract MessageOrdering { abstract contract IntegrationBaseTest is Test { + using DomainHelpers for *; + Domain mainnet; address l1Authority = makeAddr("l1Authority"); address notL1Authority = makeAddr("notL1Authority"); function setUp() public { - mainnet = new Domain(getChain("mainnet")); + mainnet = getChain("mainnet").createFork(); } } diff --git a/test/OptimismIntegration.t.sol b/test/OptimismIntegration.t.sol index 69e6793..292c685 100644 --- a/test/OptimismIntegration.t.sol +++ b/test/OptimismIntegration.t.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import "./IntegrationBase.t.sol"; -import { OptimismDomain } from "../src/testing/OptimismDomain.sol"; +import { OptimismBridgeTesting, IMessenger } from "src/testing/bridges/OptimismBridgeTesting.sol"; -import { OptimismReceiver } from "../src/OptimismReceiver.sol"; +import { OptimismReceiver } from "src/OptimismReceiver.sol"; contract MessageOrderingOptimism is MessageOrdering, OptimismReceiver { @@ -19,20 +19,23 @@ contract MessageOrderingOptimism is MessageOrdering, OptimismReceiver { contract OptimismIntegrationTest is IntegrationBaseTest { + using OptimismBridgeTesting for *; + using DomainHelpers for *; + event FailedRelayedMessage(bytes32); function test_optimism() public { - checkOptimismStyle(new OptimismDomain(getChain("optimism"), mainnet)); + checkOptimismStyle(getChain("optimism").createFork()); } function test_base() public { - checkOptimismStyle(new OptimismDomain(getChain("base"), mainnet)); + checkOptimismStyle(getChain("base").createFork()); } - function checkOptimismStyle(OptimismDomain optimism) public { - Domain host = optimism.hostDomain(); + function checkOptimismStyle(Domain memory optimism) public { + Bridge memory bridge = OptimismBridgeTesting.createNativeBridge(mainnet, optimism); - host.selectFork(); + mainnet.selectFork(); MessageOrdering moHost = new MessageOrdering(); @@ -41,12 +44,12 @@ contract OptimismIntegrationTest is IntegrationBaseTest { MessageOrdering moOptimism = new MessageOrderingOptimism(l1Authority); // Queue up some L2 -> L1 messages - optimism.L2_MESSENGER().sendMessage( + IMessenger(bridge.destinationCrossChainMessenger).sendMessage( address(moHost), abi.encodeWithSelector(MessageOrdering.push.selector, 3), 100000 ); - optimism.L2_MESSENGER().sendMessage( + IMessenger(bridge.destinationCrossChainMessenger).sendMessage( address(moHost), abi.encodeWithSelector(MessageOrdering.push.selector, 4), 100000 @@ -55,18 +58,18 @@ contract OptimismIntegrationTest is IntegrationBaseTest { assertEq(moOptimism.length(), 0); // Do not relay right away - host.selectFork(); + mainnet.selectFork(); // Queue up two more L1 -> L2 messages vm.startPrank(l1Authority); XChainForwarders.sendMessageOptimism( - address(optimism.L1_MESSENGER()), + bridge.sourceCrossChainMessenger, address(moOptimism), abi.encodeWithSelector(MessageOrdering.push.selector, 1), 100000 ); XChainForwarders.sendMessageOptimism( - address(optimism.L1_MESSENGER()), + bridge.sourceCrossChainMessenger, address(moOptimism), abi.encodeWithSelector(MessageOrdering.push.selector, 2), 100000 @@ -75,13 +78,13 @@ contract OptimismIntegrationTest is IntegrationBaseTest { assertEq(moHost.length(), 0); - optimism.relayFromHost(true); + bridge.relayMessagesToDestination(true); assertEq(moOptimism.length(), 2); assertEq(moOptimism.messages(0), 1); assertEq(moOptimism.messages(1), 2); - optimism.relayToHost(true); + bridge.relayMessagesToSource(true); assertEq(moHost.length(), 2); assertEq(moHost.messages(0), 3); @@ -90,7 +93,7 @@ contract OptimismIntegrationTest is IntegrationBaseTest { // Validate the message receiver failure modes vm.startPrank(notL1Authority); XChainForwarders.sendMessageOptimism( - address(optimism.L1_MESSENGER()), + bridge.sourceCrossChainMessenger, address(moOptimism), abi.encodeWithSelector(MessageOrdering.push.selector, 999), 100000 @@ -99,7 +102,7 @@ contract OptimismIntegrationTest is IntegrationBaseTest { // The revert is caught so it doesn't propagate // Just look at the no change to verify it didn't go through - optimism.relayFromHost(true); + bridge.relayMessagesToDestination(true); assertEq(moOptimism.length(), 2); // No change optimism.selectFork(); diff --git a/test/ZkEVMIntegration.t.sol b/test/ZkEVMIntegration.t.sol deleted file mode 100644 index 85f968b..0000000 --- a/test/ZkEVMIntegration.t.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import "./IntegrationBase.t.sol"; - -import { ZkEVMDomain, IBridgeMessageReceiver } from "../src/testing/ZkEVMDomain.sol"; - -contract ZkevmMessageOrdering is MessageOrdering, IBridgeMessageReceiver { - function onMessageReceived(address /*originAddress*/, uint32 /*originNetwork*/, bytes memory data) external payable { - // call the specific method - (bool success, bytes memory ret) = address(this).call(data); - if (!success) { - assembly { - revert(add(ret, 0x20), mload(ret)) - } - } - } - -} - -// FIXME: zkEVM bridging is broken, marking as abstract to temporarily disable until it's fixed -abstract contract ZkEVMIntegrationTest is IntegrationBaseTest { - - function test_zkevm() public { - setChain("zkevm", ChainData("ZkEVM", 1101, "https://zkevm-rpc.com")); - - checkZkEVMStyle(new ZkEVMDomain(getChain("zkevm"), mainnet)); - } - - function checkZkEVMStyle(ZkEVMDomain zkevm) public { - Domain host = zkevm.hostDomain(); - - host.selectFork(); - - ZkevmMessageOrdering moHost = new ZkevmMessageOrdering(); - - zkevm.selectFork(); - - ZkevmMessageOrdering moZkevm = new ZkevmMessageOrdering(); - - // Queue up two more L2 -> L1 messages - zkevm.L1_MESSENGER().bridgeMessage(0, address(moHost), true, abi.encodeCall(MessageOrdering.push, (3))); - zkevm.L1_MESSENGER().bridgeMessage(0, address(moHost), true, abi.encodeCall(MessageOrdering.push, (4))); - - assertEq(moZkevm.length(), 0); - - host.selectFork(); - - // Queue up two more L1 -> L2 messages - XChainForwarders.sendMessageZkEVM(address(moZkevm), abi.encodeCall(MessageOrdering.push, (1))); - XChainForwarders.sendMessageZkEVM(address(moZkevm), abi.encodeCall(MessageOrdering.push, (2))); - - assertEq(moHost.length(), 0); - - zkevm.relayFromHost(true); - - assertEq(moZkevm.length(), 2); - assertEq(moZkevm.messages(0), 1); - assertEq(moZkevm.messages(1), 2); - - zkevm.relayToHost(true); - - assertEq(moHost.length(), 2); - assertEq(moHost.messages(0), 3); - assertEq(moHost.messages(1), 4); - } - -} From 743b951032cad01dacf149cb40bb057a3272aa13 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Sat, 8 Jun 2024 20:45:32 +0900 Subject: [PATCH 2/3] forge install: openzeppelin-contracts v5.0.2 --- .gitmodules | 3 +++ lib/openzeppelin-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 888d42d..e80ffd8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/openzeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..dbb6104 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From 7eb7d7a6af52a70060e1b6c89c7dbacd25c052d5 Mon Sep 17 00:00:00 2001 From: Sam MacPherson Date: Sat, 8 Jun 2024 20:55:01 +0900 Subject: [PATCH 3/3] use fallback() instead of forward() --- src/receivers/AMBReceiver.sol | 13 ++++++------- src/receivers/ArbitrumReceiver.sol | 13 ++++++------- src/receivers/CCTPReceiver.sol | 11 +++++------ src/receivers/OptimismReceiver.sol | 13 ++++++------- test/ArbitrumIntegration.t.sol | 2 +- test/GnosisIntegration.t.sol | 6 +++--- test/OptimismIntegration.t.sol | 4 ++-- 7 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/receivers/AMBReceiver.sol b/src/receivers/AMBReceiver.sol index df285b4..f9f3cb1 100644 --- a/src/receivers/AMBReceiver.sol +++ b/src/receivers/AMBReceiver.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.0; +import { Address } from "lib/openzeppelin-contracts/contracts/utils/Address.sol"; + interface IArbitraryMessagingBridge { function messageSender() external view returns (address); function messageSourceChainId() external view returns (bytes32); @@ -12,6 +14,8 @@ interface IArbitraryMessagingBridge { */ contract AMBReceiver { + using Address for address; + address public immutable amb; bytes32 public immutable sourceChainId; address public immutable sourceAuthority; @@ -29,17 +33,12 @@ contract AMBReceiver { target = _target; } - function forward(bytes memory message) external { + fallback(bytes calldata message) external returns (bytes memory) { require(msg.sender == amb, "AMBReceiver/invalid-sender"); require(IArbitraryMessagingBridge(amb).messageSourceChainId() == sourceChainId, "AMBReceiver/invalid-sourceChainId"); require(IArbitraryMessagingBridge(amb).messageSender() == sourceAuthority, "AMBReceiver/invalid-sourceAuthority"); - (bool success, bytes memory ret) = target.call(message); - if (!success) { - assembly { - revert(add(ret, 0x20), mload(ret)) - } - } + return target.functionCall(message); } } diff --git a/src/receivers/ArbitrumReceiver.sol b/src/receivers/ArbitrumReceiver.sol index 4f48127..1807551 100644 --- a/src/receivers/ArbitrumReceiver.sol +++ b/src/receivers/ArbitrumReceiver.sol @@ -1,12 +1,16 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.0; +import { Address } from "lib/openzeppelin-contracts/contracts/utils/Address.sol"; + /** * @title ArbitrumReceiver * @notice Receive messages to an Arbitrum-style chain. */ contract ArbitrumReceiver { + using Address for address; + address public immutable l1Authority; address public immutable target; @@ -24,15 +28,10 @@ contract ArbitrumReceiver { } } - function forward(bytes memory message) external { + fallback(bytes calldata message) external returns (bytes memory) { require(_getL1MessageSender() == l1Authority, "ArbitrumReceiver/invalid-l1Authority"); - (bool success, bytes memory ret) = target.call(message); - if (!success) { - assembly { - revert(add(ret, 0x20), mload(ret)) - } - } + return target.functionCall(message); } } diff --git a/src/receivers/CCTPReceiver.sol b/src/receivers/CCTPReceiver.sol index 04bc204..0ca5b7c 100644 --- a/src/receivers/CCTPReceiver.sol +++ b/src/receivers/CCTPReceiver.sol @@ -1,12 +1,16 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.0; +import { Address } from "lib/openzeppelin-contracts/contracts/utils/Address.sol"; + /** * @title CCTPReceiver * @notice Receive messages from CCTP-style bridge. */ contract CCTPReceiver { + using Address for address; + address public immutable destinationMessenger; uint32 public immutable sourceDomainId; bytes32 public immutable sourceAuthority; @@ -33,12 +37,7 @@ contract CCTPReceiver { require(sourceDomainId == sourceDomain, "CCTPReceiver/invalid-sourceDomain"); require(sender == sourceAuthority, "CCTPReceiver/invalid-sourceAuthority"); - (bool success, bytes memory ret) = target.call(messageBody); - if (!success) { - assembly { - revert(add(ret, 0x20), mload(ret)) - } - } + target.functionCall(messageBody); return true; } diff --git a/src/receivers/OptimismReceiver.sol b/src/receivers/OptimismReceiver.sol index 65c524d..ec39e95 100644 --- a/src/receivers/OptimismReceiver.sol +++ b/src/receivers/OptimismReceiver.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.0; +import { Address } from "lib/openzeppelin-contracts/contracts/utils/Address.sol"; + interface ICrossDomainOptimism { function xDomainMessageSender() external view returns (address); } @@ -11,6 +13,8 @@ interface ICrossDomainOptimism { */ contract OptimismReceiver { + using Address for address; + ICrossDomainOptimism public constant l2CrossDomain = ICrossDomainOptimism(0x4200000000000000000000000000000000000007); address public immutable l1Authority; @@ -24,16 +28,11 @@ contract OptimismReceiver { target = _target; } - function forward(bytes memory message) external { + fallback(bytes calldata message) external returns (bytes memory) { require(msg.sender == address(l2CrossDomain), "OptimismReceiver/invalid-sender"); require(l2CrossDomain.xDomainMessageSender() == l1Authority, "OptimismReceiver/invalid-l1Authority"); - (bool success, bytes memory ret) = target.call(message); - if (!success) { - assembly { - revert(add(ret, 0x20), mload(ret)) - } - } + return target.functionCall(message); } } diff --git a/test/ArbitrumIntegration.t.sol b/test/ArbitrumIntegration.t.sol index f7a2037..6ef0e77 100644 --- a/test/ArbitrumIntegration.t.sol +++ b/test/ArbitrumIntegration.t.sol @@ -57,7 +57,7 @@ contract ArbitrumIntegrationTest is IntegrationBaseTest { ArbitrumForwarder.sendMessageL1toL2( bridge.sourceCrossChainMessenger, destinationReceiver, - abi.encodeCall(ArbitrumReceiver.forward, (message)), + message, 100000, 1 gwei, block.basefee + 10 gwei diff --git a/test/GnosisIntegration.t.sol b/test/GnosisIntegration.t.sol index bf08229..2703b7d 100644 --- a/test/GnosisIntegration.t.sol +++ b/test/GnosisIntegration.t.sol @@ -32,7 +32,7 @@ contract GnosisIntegrationTest is IntegrationBaseTest { vm.prank(randomAddress); vm.expectRevert("AMBReceiver/invalid-sender"); - AMBReceiver(destinationReceiver).forward(abi.encodeCall(MessageOrdering.push, (1))); + MessageOrdering(destinationReceiver).push(1); } function test_invalidSourceChainId() public { @@ -76,7 +76,7 @@ contract GnosisIntegrationTest is IntegrationBaseTest { function queueSourceToDestination(bytes memory message) internal override { AMBForwarder.sendMessageEthereumToGnosisChain( destinationReceiver, - abi.encodeCall(AMBReceiver.forward, (message)), + message, 100000 ); } @@ -84,7 +84,7 @@ contract GnosisIntegrationTest is IntegrationBaseTest { function queueDestinationToSource(bytes memory message) internal override { AMBForwarder.sendMessageGnosisChainToEthereum( sourceReceiver, - abi.encodeCall(AMBReceiver.forward, (message)), + message, 100000 ); } diff --git a/test/OptimismIntegration.t.sol b/test/OptimismIntegration.t.sol index a2c2f8c..8043136 100644 --- a/test/OptimismIntegration.t.sol +++ b/test/OptimismIntegration.t.sol @@ -36,7 +36,7 @@ contract OptimismIntegrationTest is IntegrationBaseTest { vm.prank(randomAddress); vm.expectRevert("OptimismReceiver/invalid-sender"); - OptimismReceiver(destinationReceiver).forward(abi.encodeCall(MessageOrdering.push, (1))); + MessageOrdering(destinationReceiver).push(1); } function test_optimism() public { @@ -63,7 +63,7 @@ contract OptimismIntegrationTest is IntegrationBaseTest { OptimismForwarder.sendMessageL1toL2( bridge.sourceCrossChainMessenger, destinationReceiver, - abi.encodeCall(OptimismReceiver.forward, (message)), + message, 100000 ); }