diff --git a/src/testing/Domain.sol b/src/testing/Domain.sol index d3ac82e..37b50ba 100644 --- a/src/testing/Domain.sol +++ b/src/testing/Domain.sol @@ -11,7 +11,7 @@ struct Domain { 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({ diff --git a/src/testing/bridges/ArbitrumNativeBridge.sol b/src/testing/bridges/ArbitrumBridgeTesting.sol similarity index 57% rename from src/testing/bridges/ArbitrumNativeBridge.sol rename to src/testing/bridges/ArbitrumBridgeTesting.sol index ca24442..b0ffffb 100644 --- a/src/testing/bridges/ArbitrumNativeBridge.sol +++ b/src/testing/bridges/ArbitrumBridgeTesting.sol @@ -1,11 +1,10 @@ // 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, DomainHelpers } from "src/testing/Domain.sol"; -import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; +import { RecordedLogs } from "src/testing/utils/RecordedLogs.sol"; +import { BridgeData } from "./BridgeData.sol"; interface InboxLike { function createRetryableTicket( @@ -42,63 +41,85 @@ contract ArbSysOverride { } -contract ArbitrumNativeBridge is IBidirectionalBridge { +library ArbitrumBridgeTesting { using DomainHelpers 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 (BridgeData memory bridge) { + ( + address sourceCrossChainMessenger, + address destinationCrossChainMessenger + ) = getMessengerFromChainAlias(ethereum.chain.chainAlias, arbitrumInstance.chain.chainAlias) + + return init(BridgeData({ + source: ethereum, + destination: arbitrumInstance, + sourceCrossChainMessenger: sourceCrossChainMessenger, + destinationCrossChainMessenger: destinationCrossChainMessenger, + lastSourceLogIndex: 0, + lastDestinationLogIndex: 0, + extraData: "" + })); + } - address public l2ToL1Sender; - - BridgeData public data; - - constructor(BridgeData memory _data) { - data = _data; + 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; + } - data.source.selectFork(); - BridgeLike bridge = InboxLike(data.sourceCrossChainMessenger).bridge(); + function init(BridgeData memory bridge) internal returns (BridgeData memory bridge) { + bridge.source.selectFork(); + BridgeLike underlyingBridge = InboxLike(data.sourceCrossChainMessenger).bridge(); vm.recordLogs(); - vm.makePersistent(address(this)); // Make this contract a valid outbox - address _rollup = bridge.rollup(); + address _rollup = underlyingBridge.rollup(); vm.store( - address(bridge), + address(underlyingBridge), bytes32(uint256(8)), bytes32(uint256(uint160(address(this)))) ); - bridge.setOutbox(address(this), true); + underlyingBridge.setOutbox(address(this), true); vm.store( - address(bridge), + address(underlyingBridge), bytes32(uint256(8)), bytes32(uint256(uint160(_rollup))) ); // Need to replace ArbSys contract with custom code to make it compatible with revm - destination.selectFork(); - bytes memory bytecode = vm.getCode("ArbitrumDomain.sol:ArbSysOverride"); + 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); - source.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]; - } + bridge.source.selectFork(); } - function relayMessagesToSource(bool switchToDestinationFork) external override { - destination.selectFork(); + function relayMessagesToDestination(BridgeData 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(); @@ -108,7 +129,7 @@ contract ArbitrumNativeBridge is IBidirectionalBridge { // 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); + (address target, bytes memory message) = _parseData(logWithData.data); vm.startPrank(sender); (bool success, bytes memory response) = target.call(message); vm.stopPrank(); @@ -121,12 +142,12 @@ contract ArbitrumNativeBridge is IBidirectionalBridge { } if (!switchToDestinationFork) { - source.selectFork(); + bridge.source.selectFork(); } } - function relayMessagesToSource(bool switchToSourceFork) external override { - source.selectFork(); + function relayMessagesToSource(BridgeData 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(); @@ -134,7 +155,7 @@ contract ArbitrumNativeBridge is IBidirectionalBridge { 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; + //l2ToL1Sender = sender; (bool success, bytes memory response) = InboxLike(data.sourceCrossChainMessenger).bridge().executeCall(target, 0, message); if (!success) { assembly { @@ -145,7 +166,17 @@ contract ArbitrumNativeBridge is IBidirectionalBridge { } if (!switchToSourceFork) { - destination.selectFork(); + 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/data/BridgeData.sol b/src/testing/bridges/BridgeData.sol similarity index 94% rename from src/testing/bridges/data/BridgeData.sol rename to src/testing/bridges/BridgeData.sol index 56903c2..f9633bf 100644 --- a/src/testing/bridges/data/BridgeData.sol +++ b/src/testing/bridges/BridgeData.sol @@ -11,4 +11,5 @@ struct BridgeData { // These are used internally for log tracking uint256 lastSourceLogIndex; uint256 lastDestinationLogIndex; + bytes extraData; } diff --git a/src/testing/bridges/CCTPBridgeTesting.sol b/src/testing/bridges/CCTPBridgeTesting.sol new file mode 100644 index 0000000..a50d05d --- /dev/null +++ b/src/testing/bridges/CCTPBridgeTesting.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +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; + } + +} + +library ArbitrumBridgeTesting { + + using DomainHelpers for *; + + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + 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), + lastSourceLogIndex: 0, + lastDestinationLogIndex: 0, + extraData: "" + })); + } + + 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 init(BridgeData memory bridge) internal returns (BridgeData memory bridge) { + + } + + function relayMessagesToDestination(BridgeData memory bridge, bool switchToDestinationFork) internal { + + } + + function relayMessagesToSource(BridgeData memory bridge, bool switchToSourceFork) internal { + + } + +} diff --git a/src/testing/bridges/IBidirectionalBridge.sol b/src/testing/bridges/IBidirectionalBridge.sol deleted file mode 100644 index 62fdb92..0000000 --- a/src/testing/bridges/IBidirectionalBridge.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { IUnidirectionalBridge } from "./IUnidirectionalBridge.sol"; - -interface IUnidirectionalBridge is IUnidirectionalBridge { - function relayMessagesToSource(bool switchToSourceFork) external; -} diff --git a/src/testing/bridges/IUnidirectionalBridge.sol b/src/testing/bridges/IUnidirectionalBridge.sol deleted file mode 100644 index 756c154..0000000 --- a/src/testing/bridges/IUnidirectionalBridge.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -interface IUnidirectionalBridge { - function relayMessagesToDestination(bool switchToDestinationFork) external; -} diff --git a/src/testing/bridges/data/Arbitrum.sol b/src/testing/bridges/data/Arbitrum.sol deleted file mode 100644 index 15da267..0000000 --- a/src/testing/bridges/data/Arbitrum.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity >=0.8.0; - -import { BridgeData } from "./BridgeData.sol"; - -library Arbitrum { - - function createArbitrumNativeBridge(Domain memory ethereum, Domain memory arbitrumInstance) internal returns (ArbitrumNativeBridge memory bridge) { - require(keccak256(bytes(ethereum.chain.chainAlias)) == keccak256("mainnet"), "Source must be Ethereum."); - - bytes32 name = keccak256(bytes(arbitrumInstance.chain.chainAlias)); - address inbox; - if (name == keccak256("arbitrum_one")) { - inbox = 0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f; - } else if (name == keccak256("arbitrum_nova")) { - inbox = 0xc4448b71118c9071Bcb9734A0EAc55D18A153949; - } else { - revert("Unsupported destination chain"); - } - - return new ArbitrumNativeBridge(BridgeData({ - source: ethereum, - destination: arbitrumInstance, - sourceCrossChainMessenger: inbox, - destinationCrossChainMessenger: 0x0000000000000000000000000000000000000064, - lastSourceLogIndex: 0, - lastDestinationLogIndex: 0 - })); - } - -}