diff --git a/src/testing/bridges/BridgeData.sol b/src/testing/Bridge.sol similarity index 83% rename from src/testing/bridges/BridgeData.sol rename to src/testing/Bridge.sol index f9633bf..f5740fb 100644 --- a/src/testing/bridges/BridgeData.sol +++ b/src/testing/Bridge.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity >=0.8.0; -import { Domain } from "src/testing/Domain.sol"; +import { Domain } from "./Domain.sol"; -struct BridgeData { +struct Bridge { Domain source; Domain destination; address sourceCrossChainMessenger; 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 37b50ba..d7492a2 100644 --- a/src/testing/Domain.sol +++ b/src/testing/Domain.sol @@ -16,7 +16,7 @@ library DomainHelpers { function createFork(StdChains.Chain memory chain, uint256 blockNumber) internal returns (Domain memory domain) { domain = Domain({ chain: chain, - forkId: vm.createFork(chain.rpcUrl, blockNum) + forkId: vm.createFork(chain.rpcUrl, blockNumber) }); } @@ -30,7 +30,7 @@ library DomainHelpers { function createSelectFork(StdChains.Chain memory chain, uint256 blockNumber) internal returns (Domain memory domain) { domain = Domain({ chain: chain, - forkId: vm.createSelectFork(chain.rpcUrl, blockNum) + forkId: vm.createSelectFork(chain.rpcUrl, blockNumber) }); _assertExpectedRpc(chain); } @@ -45,14 +45,14 @@ library DomainHelpers { function selectFork(Domain memory domain) internal { vm.selectFork(domain.forkId); - _assertExpectedRpc(domain); + _assertExpectedRpc(domain.chain); } function rollFork(Domain memory domain, uint256 blockNumber) internal { vm.rollFork(domain.forkId, blockNumber); } - function _assertExpectedRpc(StdChains.Chain memory chain) private { + 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/bridges/AMBBridgeTesting.sol b/src/testing/bridges/AMBBridgeTesting.sol index 1293bba..320d3fe 100644 --- a/src/testing/bridges/AMBBridgeTesting.sol +++ b/src/testing/bridges/AMBBridgeTesting.sol @@ -3,56 +3,112 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; -import { BridgeData } from "./BridgeData.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 createBridge(Domain memory source, Domain memory destination) internal returns (BridgeData memory bridge) { - return init(BridgeData({ - source: ethereum, - destination: arbitrumInstance, - sourceCrossChainMessenger: _getMessengerFromChainAlias(source.chain.chainAlias), - destinationCrossChainMessenger: _getMessengerFromChainAlias(destination.chain.chainAlias), + 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 getMessengerFromChainAlias(string memory chainAlias) internal pure returns (address) { + function getGnosisMessengerFromChainAlias(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; + return 0x4C36d2919e407f0Cc2Ee3c993ccF8ac26d9CE64e; + } else if (name == keccak256("gnosis_chain")) { + return 0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59; } else { revert("Unsupported chain"); } } - function init(BridgeData memory bridge) internal returns (BridgeData memory bridge) { - + 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 relayMessagesToDestination(BridgeData memory bridge, bool switchToDestinationFork) internal { - + 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 relayMessagesToSource(BridgeData memory bridge, bool switchToSourceFork) internal { - + 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) { + IValidatorContract validatorContract = IValidatorContract(IAMB(amb).validatorContract()); + address[] memory validators = validatorContract.validatorList(); + uint256 requiredSignatures = validatorContract.requiredSignatures(); + for (uint256 o = 0; o < requiredSignatures; o++) { + vm.prank(validators[o]); + 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 index 9935c53..7f66b5c 100644 --- a/src/testing/bridges/ArbitrumBridgeTesting.sol +++ b/src/testing/bridges/ArbitrumBridgeTesting.sol @@ -3,8 +3,9 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; -import { BridgeData } from "./BridgeData.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( @@ -51,13 +52,13 @@ library ArbitrumBridgeTesting { 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 (BridgeData memory bridge) { + function createNativeBridge(Domain memory ethereum, Domain memory arbitrumInstance) internal returns (Bridge memory bridge) { ( address sourceCrossChainMessenger, address destinationCrossChainMessenger - ) = getMessengerFromChainAlias(ethereum.chain.chainAlias, arbitrumInstance.chain.chainAlias) + ) = getMessengerFromChainAlias(ethereum.chain.chainAlias, arbitrumInstance.chain.chainAlias); - return init(BridgeData({ + return init(Bridge({ source: ethereum, destination: arbitrumInstance, sourceCrossChainMessenger: sourceCrossChainMessenger, @@ -88,11 +89,21 @@ library ArbitrumBridgeTesting { destinationCrossChainMessenger = 0x0000000000000000000000000000000000000064; } - function init(BridgeData memory bridge) internal returns (BridgeData memory bridge) { - bridge.source.selectFork(); - BridgeLike underlyingBridge = InboxLike(data.sourceCrossChainMessenger).bridge(); + 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(); + // Make this contract a valid outbox address _rollup = underlyingBridge.rollup(); vm.store( @@ -107,26 +118,16 @@ library ArbitrumBridgeTesting { bytes32(uint256(uint160(_rollup))) ); - // 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(ARB_SYS, deployed.code); - - bridge.source.selectFork(); + return bridge; } - function relayMessagesToDestination(BridgeData memory bridge, bool switchToDestinationFork) internal { + function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { bridge.destination.selectFork(); - // Read all L1 -> L2 messages and relay them under Arbitrum fork 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) { + if (log.topics[0] == MESSAGE_DELIVERED_TOPIC && log.emitter == bridge.sourceCrossChainMessenger) { // 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)); @@ -147,16 +148,15 @@ library ArbitrumBridgeTesting { } } - function relayMessagesToSource(BridgeData memory bridge, bool switchToSourceFork) internal { + function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { bridge.source.selectFork(); - // Read all L2 -> L1 messages and relay them under host fork - Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SEND_TO_L1_TOPIC, address(0)); + 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 sender, address target, bytes memory message) = abi.decode(log.data, (address, address, bytes)); + (, address target, bytes memory message) = abi.decode(log.data, (address, address, bytes)); //l2ToL1Sender = sender; - (bool success, bytes memory response) = InboxLike(data.sourceCrossChainMessenger).bridge().executeCall(target, 0, message); + (bool success, bytes memory response) = InboxLike(bridge.sourceCrossChainMessenger).bridge().executeCall(target, 0, message); if (!success) { assembly { revert(add(response, 32), mload(response)) diff --git a/src/testing/bridges/CCTPBridgeTesting.sol b/src/testing/bridges/CCTPBridgeTesting.sol index 65e531e..229a2b2 100644 --- a/src/testing/bridges/CCTPBridgeTesting.sol +++ b/src/testing/bridges/CCTPBridgeTesting.sol @@ -3,21 +3,27 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; -import { BridgeData } from "./BridgeData.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 (BridgeData memory bridge) { - return init(BridgeData({ - source: ethereum, - destination: arbitrumInstance, + 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, @@ -45,7 +51,7 @@ library CCTPBridgeTesting { } } - function init(BridgeData memory bridge) internal returns (BridgeData memory bridge) { + function init(Bridge memory bridge) internal returns (Bridge memory) { // Set minimum required signatures to zero for both domains bridge.destination.selectFork(); vm.store( @@ -61,14 +67,16 @@ library CCTPBridgeTesting { ); vm.recordLogs(); + + return bridge; } - function relayMessagesToDestination(BridgeData memory bridge, bool switchToDestinationFork) internal { + function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { bridge.destination.selectFork(); - Vm.Log[] memory logs = bridge.ingestAndFilterLogs(true, SENT_MESSAGE_TOPIC, address(0)); + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(true, SENT_MESSAGE_TOPIC, bridge.sourceCrossChainMessenger); for (uint256 i = 0; i < logs.length; i++) { - bridge.destinationCrossChainMessenger.receiveMessage(abi.decode(logs[i].data, (bytes)), ""); + IMessenger(bridge.destinationCrossChainMessenger).receiveMessage(abi.decode(logs[i].data, (bytes)), ""); } if (!switchToDestinationFork) { @@ -76,12 +84,12 @@ library CCTPBridgeTesting { } } - function relayMessagesToSource(BridgeData memory bridge, bool switchToSourceFork) internal { + function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { bridge.source.selectFork(); - Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SENT_MESSAGE_TOPIC, address(0)); + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, SENT_MESSAGE_TOPIC, bridge.destinationCrossChainMessenger); for (uint256 i = 0; i < logs.length; i++) { - bridge.sourceCrossChainMessenger.receiveMessage(abi.decode(logs[i].data, (bytes)), ""); + IMessenger(bridge.sourceCrossChainMessenger).receiveMessage(abi.decode(logs[i].data, (bytes)), ""); } if (!switchToSourceFork) { diff --git a/src/testing/bridges/OptimismBridgeTesting.sol b/src/testing/bridges/OptimismBridgeTesting.sol index a3547bb..54edde4 100644 --- a/src/testing/bridges/OptimismBridgeTesting.sol +++ b/src/testing/bridges/OptimismBridgeTesting.sol @@ -3,62 +3,44 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; -import { BridgeData } from "./BridgeData.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; - } - +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 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)"); + bytes32 private constant SENT_MESSAGE_TOPIC = keccak256("SentMessage(address,address,bytes,uint256,uint256)"); - function createNativeBridge(Domain memory ethereum, Domain memory arbitrumInstance) internal returns (BridgeData memory bridge) { + function createNativeBridge(Domain memory ethereum, Domain memory optimismInstance) internal returns (Bridge memory bridge) { ( address sourceCrossChainMessenger, address destinationCrossChainMessenger - ) = getMessengerFromChainAlias(ethereum.chain.chainAlias, arbitrumInstance.chain.chainAlias) + ) = getMessengerFromChainAlias(ethereum.chain.chainAlias, optimismInstance.chain.chainAlias); - return init(BridgeData({ + return init(Bridge({ source: ethereum, - destination: arbitrumInstance, + destination: optimismInstance, sourceCrossChainMessenger: sourceCrossChainMessenger, destinationCrossChainMessenger: destinationCrossChainMessenger, lastSourceLogIndex: 0, @@ -77,68 +59,40 @@ library OptimismBridgeTesting { 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; + if (name == keccak256("optimism")) { + sourceCrossChainMessenger = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1; + } else if (name == keccak256("base")) { + sourceCrossChainMessenger = 0x866E82a600A1414e583f7F13623F1aC5d58b0Afa; } else { revert("Unsupported destination chain"); } - destinationCrossChainMessenger = 0x0000000000000000000000000000000000000064; + destinationCrossChainMessenger = 0x4200000000000000000000000000000000000007; } - function init(BridgeData memory bridge) internal returns (BridgeData memory bridge) { - bridge.source.selectFork(); - BridgeLike underlyingBridge = InboxLike(data.sourceCrossChainMessenger).bridge(); + function init(Bridge memory bridge) internal returns (Bridge memory) { vm.recordLogs(); - // 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))) - ); - - // 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(ARB_SYS, deployed.code); - + // For consistency with other bridges bridge.source.selectFork(); + + return bridge; } - function relayMessagesToDestination(BridgeData memory bridge, bool switchToDestinationFork) internal { + function relayMessagesToDestination(Bridge memory bridge, bool switchToDestinationFork) internal { bridge.destination.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)) - } - } - } + 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) { @@ -146,23 +100,33 @@ library OptimismBridgeTesting { } } - function relayMessagesToSource(BridgeData memory bridge, bool switchToSourceFork) internal { + function relayMessagesToSource(Bridge memory bridge, bool switchToSourceFork) internal { bridge.source.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) = InboxLike(data.sourceCrossChainMessenger).bridge().executeCall(target, 0, message); + Vm.Log[] memory logs = bridge.ingestAndFilterLogs(false, 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,,) = 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) { @@ -170,14 +134,4 @@ library OptimismBridgeTesting { } } - 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/utils/RecordedLogs.sol b/src/testing/utils/RecordedLogs.sol index ca62d29..4912a24 100644 --- a/src/testing/utils/RecordedLogs.sol +++ b/src/testing/utils/RecordedLogs.sol @@ -3,6 +3,8 @@ pragma solidity >=0.8.0; import { Vm } from "forge-std/Vm.sol"; +import { Bridge } from "src/testing/Bridge.sol"; + library RecordedLogs { Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); @@ -31,7 +33,7 @@ library RecordedLogs { return logs; } - function ingestAndFilterLogs(BridgeData memory bridge, bool sourceToDestination, bytes32 topic, address emitter) internal pure returns (Vm.Log[] memory filteredLogs) { + 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; @@ -40,14 +42,19 @@ library RecordedLogs { for (; lastIndex < logs.length; lastIndex++) { Vm.Log memory log = logs[lastIndex]; - if (log.topics[0] == topic && log.emitter == emitter) { + if ((log.topics[0] == topic0 || log.topics[0] == topic1) && log.emitter == emitter) { filteredLogs[pushedIndex++] = log; } } if (sourceToDestination) bridge.lastSourceLogIndex = lastIndex; else bridge.lastDestinationLogIndex = lastIndex; - filteredLogs.length = pushedIndex; + // 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();