diff --git a/audits/internal10/README.md b/audits/internal10/README.md index e3f7a56..7f44b7b 100644 --- a/audits/internal10/README.md +++ b/audits/internal10/README.md @@ -30,6 +30,7 @@ File | % Stmts | % Branch | % Funcs | % Line --------------------------------------|----------|----------|----------|----------|----------------| ``` Please, pay attention. More tests are needed and magic offsets (like MIN_ARBITRUM_PAYLOAD_LENGTH) can only be checked during testing +[x] fixed ### Storage timelock Using sol2uml tools: https://github.com/naddison36/sol2uml
@@ -50,6 +51,7 @@ Dubious typecast in VerifyData._verifyData(address,bytes,uint256) (ProcessBridge bytes => bytes4 casting occurs in targetSelectorChainId |= uint256(uint32(bytes4(data))) << 160 (ProcessBridgedDataArbitrum-flatten.sol#30) bytes => bytes4 casting occurs in revert NotAuthorized(address,bytes4,uint256)(target,bytes4(data),chainId) (ProcessBridgedDataArbitrum-flatten.sol#36) ``` +[x] fixed - Checking for verifierL2s is contract in set function. ``` @@ -61,6 +63,7 @@ because if verifierL2s[i] is EOA bridgeParams.verifierL2.delegatecall - always success, ref: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L124 ``` +[x] fixed - Needing fix revert message ``` @@ -69,9 +72,4 @@ revert("Function call reverted"); replace to error-style message revert FailedInnerCall(); ``` - - - - - - +[x] fixed diff --git a/contracts/multisigs/GuardCM.sol b/contracts/multisigs/GuardCM.sol index b069204..22b3335 100644 --- a/contracts/multisigs/GuardCM.sol +++ b/contracts/multisigs/GuardCM.sol @@ -81,6 +81,13 @@ error NoSelfCall(); /// @param state Current proposal state. error NotDefeated(uint256 proposalId, ProposalState state); +/// @dev Delegatecall reverted. +error DelegateCallFailed(); + +/// @dev Only the contract address is allowed, but the EOA account was provided. +/// @param account Account address. +error ContractOnly(address account); + /// @dev Passed L2 chain Id is not supported. /// @param chainId L2 chain Id. error L2ChainIdNotSupported(uint256 chainId); @@ -209,18 +216,18 @@ contract GuardCM is VerifyData { // Check if the data goes across the bridge if (bridgeParams.verifierL2 != address(0)) { // Process the bridge logic - (bool success, bytes memory returndata) = bridgeParams.verifierL2.delegatecall(abi.encodeWithSelector( + (bool success, bytes memory returnData) = bridgeParams.verifierL2.delegatecall(abi.encodeWithSelector( IBridgeVerifier.processBridgeData.selector, callDatas[i], bridgeParams.bridgeMediatorL2, bridgeParams.chainId)); // Process unsuccessful delegatecall if (!success) { // Get the revert message bytes - if (returndata.length > 0) { + if (returnData.length > 0) { assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) + let returnDataSize := mload(returnData) + revert(add(32, returnData), returnDataSize) } } else { - revert("Function call reverted"); + revert DelegateCallFailed(); } } } else { @@ -370,6 +377,11 @@ contract GuardCM is VerifyData { revert ZeroAddress(); } + // Check that the verifier is a contract + if (verifierL2s[i].code.length == 0) { + revert ContractOnly(verifierL2s[i]); + } + // Check chain Id uint256 chainId = chainIds[i]; if (chainId == 0 || chainId > MAX_CHAIN_ID) { diff --git a/contracts/multisigs/VerifyData.sol b/contracts/multisigs/VerifyData.sol index 3b152fb..c34a77c 100644 --- a/contracts/multisigs/VerifyData.sol +++ b/contracts/multisigs/VerifyData.sol @@ -25,13 +25,14 @@ abstract contract VerifyData { // target occupies first 160 bits uint256 targetSelectorChainId = uint256(uint160(target)); // selector occupies next 32 bits - targetSelectorChainId |= uint256(uint32(bytes4(data))) << 160; + bytes4 targetSelector = bytes4(data); + targetSelectorChainId |= uint256(uint32(targetSelector)) << 160; // chainId occupies next 64 bits targetSelectorChainId |= chainId << 192; // Check the authorized combination of target and selector if (!mapAllowedTargetSelectorChainIds[targetSelectorChainId]) { - revert NotAuthorized(target, bytes4(data), chainId); + revert NotAuthorized(target, targetSelector, chainId); } } } \ No newline at end of file diff --git a/contracts/multisigs/bridge_verifier/ProcessBridgedDataArbitrum.sol b/contracts/multisigs/bridge_verifier/ProcessBridgedDataArbitrum.sol index 8032cd0..6bce2d3 100644 --- a/contracts/multisigs/bridge_verifier/ProcessBridgedDataArbitrum.sol +++ b/contracts/multisigs/bridge_verifier/ProcessBridgedDataArbitrum.sol @@ -13,22 +13,17 @@ error IncorrectDataLength(uint256 expected, uint256 provided); /// @param chainId Chain Id. error WrongSelector(bytes4 functionSig, uint256 chainId); -/// @dev Provided wrong L2 bridge mediator address. -/// @param provided Provided address. -/// @param expected Expected address. -error WrongL2BridgeMediator(address provided, address expected); - /// @title ProcessBridgedDataArbitrum - Smart contract for verifying the Guard CM bridged data on Arbitrum /// @author Aleksandr Kuperman - /// @author Andrey Lebedev - /// @author Mariapia Moscatiello - contract ProcessBridgedDataArbitrum is VerifyBridgedData { // unsafeCreateRetryableTicket selector in bridge mediator L1 - bytes4 public constant CREATE_TICKET_UNSAFE = bytes4(keccak256(bytes("unsafeCreateRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,uint256,bytes)"))); + bytes4 public constant CREATE_TICKET_UNSAFE = bytes4(keccak256(bytes("unsafeCreateRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)"))); // createRetryableTicket selector in bridge mediator L1 - bytes4 public constant CREATE_TICKET = bytes4(keccak256(bytes("createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,uint256,bytes)"))); + bytes4 public constant CREATE_TICKET = bytes4(keccak256(bytes("createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)"))); // Minimum payload length for message on Arbitrum accounting for all required encoding and at least one selector - uint256 public constant MIN_ARBITRUM_PAYLOAD_LENGTH = 584; + uint256 public constant MIN_ARBITRUM_PAYLOAD_LENGTH = 324; /// @dev Processes bridged data: checks the header and verifies the payload. /// @param data Full data bytes with the header. @@ -57,8 +52,8 @@ contract ProcessBridgedDataArbitrum is VerifyBridgedData { } // Decode the payload depending on the selector - (address targetAddress, , , , , , , , bytes memory targetPayload) = - abi.decode(payload, (address, uint256, uint256, address, address, uint256, uint256, uint256, bytes)); + (address targetAddress, , , , , , , bytes memory targetPayload) = + abi.decode(payload, (address, uint256, uint256, address, address, uint256, uint256, bytes)); // Verify the scope of the data _verifyData(targetAddress, targetPayload, chainId); diff --git a/contracts/multisigs/bridge_verifier/ProcessBridgedDataOptimism.sol b/contracts/multisigs/bridge_verifier/ProcessBridgedDataOptimism.sol index feda25d..f98951d 100644 --- a/contracts/multisigs/bridge_verifier/ProcessBridgedDataOptimism.sol +++ b/contracts/multisigs/bridge_verifier/ProcessBridgedDataOptimism.sol @@ -28,7 +28,7 @@ contract ProcessBridgedDataOptimism is VerifyBridgedData { // processMessageFromSource selector (Optimism and Base chains) bytes4 public constant PROCESS_MESSAGE_FROM_SOURCE = bytes4(keccak256(bytes("processMessageFromSource(bytes)"))); // Minimum payload length for message on Optimism accounting for all required encoding and at least one selector - uint256 public constant MIN_OPTIMISM_PAYLOAD_LENGTH = 264; + uint256 public constant MIN_OPTIMISM_PAYLOAD_LENGTH = 292; /// @dev Processes bridged data: checks the header and verifies the payload. /// @param data Full data bytes with the header. diff --git a/contracts/multisigs/bridge_verifier/ProcessBridgedDataWormhole.sol b/contracts/multisigs/bridge_verifier/ProcessBridgedDataWormhole.sol index b54ee25..8df2080 100644 --- a/contracts/multisigs/bridge_verifier/ProcessBridgedDataWormhole.sol +++ b/contracts/multisigs/bridge_verifier/ProcessBridgedDataWormhole.sol @@ -28,7 +28,7 @@ contract ProcessBridgedDataWormhole is VerifyBridgedData { // sendPayloadToEvm selector in bridge mediator L1 bytes4 public constant SEND_MESSAGE = bytes4(keccak256(bytes("sendPayloadToEvm(uint16,address,bytes,uint256,uint256)"))); // Minimum payload length for message sent via Wormhole accounting for all required encoding and at least one selector - uint256 public constant MIN_OPTIMISM_PAYLOAD_LENGTH = 520; + uint256 public constant MIN_OPTIMISM_PAYLOAD_LENGTH = 324; /// @dev Processes bridged data: checks the header and verifies the payload. /// @param data Full data bytes with the header. diff --git a/test/GuardCM.js b/test/GuardCM.js index 80d483e..29b7a09 100644 --- a/test/GuardCM.js +++ b/test/GuardCM.js @@ -18,29 +18,93 @@ describe("Community Multisig Guard", function () { let governor; let processBridgedDataGnosis; let processBridgedDataPolygon; + let processBridgedDataArbitrum; + let processBridgedDataOptimism; + let processBridgedDataWormhole; let signers; let deployer; - const l1BridgeMediators = ["0x4C36d2919e407f0Cc2Ee3c993ccF8ac26d9CE64e", "0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2"]; - const l2BridgeMediators = ["0x15bd56669F57192a97dF41A2aa8f4403e9491776", "0x9338b5153AE39BB89f50468E608eD9d764B755fD"]; - const verifiersL2 = new Array(2); - const l2ChainIds = [100, 137]; + const AddressZero = "0x" + "0".repeat(40); const Bytes32Zero = "0x" + "0".repeat(64); const Bytes4Zero = "0x" + "0".repeat(8); - const safeThreshold = 7; - const initialVotingDelay = 5; - const initialVotingPeriod = 10; - const initialProposalThreshold = ethers.utils.parseEther("5"); - const quorum = 1; - const localChainId = 31337; - // bytes32(keccak256("changeMultisigPermission")) = 0x82694b1d - const l2Selector = "0x82694b1d"; + + const l1BridgeMediators = [ + // gnosis (AMBProxyForeign) + "0x4C36d2919e407f0Cc2Ee3c993ccF8ac26d9CE64e", + // polygon (FxRoot) + "0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2", + // arbitrum (Inbox) + "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", + // arbitrum (Inbox) + "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", + // optimism (L1CrossDomainMessenger) + "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1", + // celo (L1WormholeRelayer) + "0x27428DD2d3DD32A4D7f7C497eAaa23130d894911", + // celo (L1WormholeRelayer) + "0x27428DD2d3DD32A4D7f7C497eAaa23130d894911" + ]; + const l2BridgeMediators = [ + // gnosis (HomeMediator) + "0x15bd56669F57192a97dF41A2aa8f4403e9491776", + // polygon (FxGovernorTunnel) + "0x9338b5153AE39BB89f50468E608eD9d764B755fD", + // arbitrum (None) + AddressZero, + // arbitrum (None) + AddressZero, + // optimism (OptimismMessenger) + "0x670Ac235EE13C0B2a5065282bBB0c61cfB354592", + // celo (WormholeMessenger) + "0x945550dECe7E40ae70C6ebf5699637927eAF13E9", + // celo (WormholeMessenger) + "0x945550dECe7E40ae70C6ebf5699637927eAF13E9" + ]; + + // gnosis, polygon, arbitrum, arbitrum, optimism, celo, celo + const l2ChainIds = [100, 137, 42161, 42161, 10, 42220, 42220]; + const verifiersL2 = new Array(l2ChainIds.length); + // bytes4(keccak256("changeMultisigPermission")) = 0x82694b1d + const l2SelectorPermission = "0x82694b1d"; + // bytes4(keccak256("mint")) = 0x40c10f19 + const l2SelectorMint = "0x40c10f19"; + const l2Selectors = [l2SelectorPermission, l2SelectorPermission, l2SelectorMint, l2SelectorMint, l2SelectorMint, + l2SelectorMint, l2SelectorMint]; + // ServiceRegistryL2 on Gnosis const gnosisContractAddress = "0x9338b5153AE39BB89f50468E608eD9d764B755fD"; const gnosisPayload = "0xdc8601b300000000000000000000000015bd56669f57192a97df41a2aa8f4403e9491776000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001e84800000000000000000000000000000000000000000000000000000000000000124cd9e30d9000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000d09338b5153ae39bb89f50468e608ed9d764b755fd0000000000000000000000000000004482694b1d0000000000000000000000003d77596beb0f130a4415df3d2d8232b3d3d31e4400000000000000000000000000000000000000000000000000000000000000009338b5153ae39bb89f50468e608ed9d764b755fd0000000000000000000000000000004482694b1d0000000000000000000000006e7f594f680f7abad18b7a63de50f0fee47dfd0600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; // ServiceRegistryL2 on Polygon const polygonContractAddress = "0xE3607b00E75f6405248323A9417ff6b39B244b50"; const polygonPayload = "0xb47204770000000000000000000000009338b5153ae39bb89f50468e608ed9d764b755fd000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000d0e3607b00e75f6405248323a9417ff6b39b244b500000000000000000000000000000004482694b1d00000000000000000000000034c895f302d0b5cf52ec0edd3945321eb0f83dd50000000000000000000000000000000000000000000000000000000000000000e3607b00e75f6405248323a9417ff6b39b244b500000000000000000000000000000004482694b1d000000000000000000000000d8bcc126ff31d2582018715d5291a508530587b0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"; + // ERC20 mock token on Arbitrum + const arbitrumContractAddress = "0xeDd71796B90eaCc56B074C39BAC90ED2Ca6D93Ee"; + const arbitrumPayload = "0x679b6ded000000000000000000000000edd71796b90eacc56b074c39bac90ed2ca6d93ee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f3207567800000000000000000000000052370ee170c0e2767b32687166791973a0de796600000000000000000000000052370ee170c0e2767b32687166791973a0de7966000000000000000000000000000000000000000000000000000000000000ac7e0000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004440c10f1900000000000000000000000052370ee170c0e2767b32687166791973a0de7966000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000"; + const arbitrumPayload2 = "0x6e6e8a6a000000000000000000000000edd71796b90eacc56b074c39bac90ed2ca6d93ee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005f3207567800000000000000000000000052370ee170c0e2767b32687166791973a0de796600000000000000000000000052370ee170c0e2767b32687166791973a0de7966000000000000000000000000000000000000000000000000000000000000ac7e0000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000004440c10f1900000000000000000000000052370ee170c0e2767b32687166791973a0de7966000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000"; + // ERC20 mock token on Optimism + const optimismContractAddress = "0x118173028162C1b7c6Bf8488bd5dA2abd7c30F9D"; + const optimismPayload = "0x3dbb202b000000000000000000000000670ac235ee13c0b2a5065282bbb0c61cfb354592000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000c4d3042d2b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000068118173028162c1b7c6bf8488bd5da2abd7c30f9d0000000000000000000000000000004440c10f1900000000000000000000000052370ee170c0e2767b32687166791973a0de7966000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + // ERC20 mock token on Celo + const celoContractAddress = "0x34235f9D447f9F54167e2Ac7A0F4283cB3fAD669"; + const celoPayload = "0x4b5ca6f4000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000945550dece7e40ae70c6ebf5699637927eaf13e900000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000945550dece7e40ae70c6ebf5699637927eaf13e9000000000000000000000000000000000000000000000000000000000000006834235f9d447f9f54167e2ac7a0f4283cb3fad6690000000000000000000000000000004440c10f1900000000000000000000000052370ee170c0e2767b32687166791973a0de79660000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000"; + const celoPayload2 = "0x8fecdd02000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000945550dece7e40ae70c6ebf5699637927eaf13e900000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000945550dece7e40ae70c6ebf5699637927eaf13e9000000000000000000000000000000000000000000000000000000000000006834235f9d447f9f54167e2ac7a0f4283cb3fad6690000000000000000000000000000004440c10f1900000000000000000000000052370ee170c0e2767b32687166791973a0de79660000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000"; + + const l2ContractAddresses = [ + gnosisContractAddress, + polygonContractAddress, + arbitrumContractAddress, + arbitrumContractAddress, + optimismContractAddress, + celoContractAddress, + celoContractAddress + ]; + + const safeThreshold = 7; + const initialVotingDelay = 5; + const initialVotingPeriod = 10; + const initialProposalThreshold = ethers.utils.parseEther("5"); + const quorum = 1; + const localChainId = 31337; beforeEach(async function () { const GnosisSafe = await ethers.getContractFactory("GnosisSafe"); @@ -114,6 +178,23 @@ describe("Community Multisig Guard", function () { await processBridgedDataPolygon.deployed(); verifiersL2[1] = processBridgedDataPolygon.address; + const ProcessBridgedDataArbitrum = await ethers.getContractFactory("ProcessBridgedDataArbitrum"); + processBridgedDataArbitrum = await ProcessBridgedDataArbitrum.deploy(); + await processBridgedDataArbitrum.deployed(); + verifiersL2[2] = processBridgedDataArbitrum.address; + verifiersL2[3] = processBridgedDataArbitrum.address; + + const ProcessBridgedDataOptimism = await ethers.getContractFactory("ProcessBridgedDataOptimism"); + processBridgedDataOptimism = await ProcessBridgedDataOptimism.deploy(); + await processBridgedDataOptimism.deployed(); + verifiersL2[4] = processBridgedDataOptimism.address; + + const ProcessBridgedDataWormhole = await ethers.getContractFactory("ProcessBridgedDataWormhole"); + processBridgedDataWormhole = await ProcessBridgedDataWormhole.deploy(); + await processBridgedDataWormhole.deployed(); + verifiersL2[5] = processBridgedDataWormhole.address; + verifiersL2[6] = processBridgedDataWormhole.address; + // Deploy Guard CM const GuardCM = await ethers.getContractFactory("GuardCM"); guard = await GuardCM.deploy(timelock.address, multisig.address, governor.address); @@ -224,20 +305,26 @@ describe("Community Multisig Guard", function () { // Incorrect L2 setup let setBridgeMediatorsPayload = guard.interface.encodeFunctionData("setBridgeMediatorL1BridgeParams", - [l1BridgeMediators, [], [], []]); + [[l1BridgeMediators[0]], [], [], []]); await expect( timelock.execute(guard.address, setBridgeMediatorsPayload) ).to.be.reverted; setBridgeMediatorsPayload = guard.interface.encodeFunctionData("setBridgeMediatorL1BridgeParams", - [l1BridgeMediators, l2BridgeMediators, [], []]); + [[l1BridgeMediators[0]], [verifiersL2[0]], [], []]); + await expect( + timelock.execute(guard.address, setBridgeMediatorsPayload) + ).to.be.reverted; + + setBridgeMediatorsPayload = guard.interface.encodeFunctionData("setBridgeMediatorL1BridgeParams", + [[l1BridgeMediators[0]], [verifiersL2[0]], [0], []]); await expect( timelock.execute(guard.address, setBridgeMediatorsPayload) ).to.be.reverted; // Zero addresses and chain Ids setBridgeMediatorsPayload = guard.interface.encodeFunctionData("setBridgeMediatorL1BridgeParams", - [[AddressZero], [AddressZero], [0], []]); + [[AddressZero], [AddressZero], [0], [AddressZero]]); await expect( timelock.execute(guard.address, setBridgeMediatorsPayload) ).to.be.reverted; @@ -248,11 +335,25 @@ describe("Community Multisig Guard", function () { timelock.execute(guard.address, setBridgeMediatorsPayload) ).to.be.reverted; + // L2 verifier is an EOA setBridgeMediatorsPayload = guard.interface.encodeFunctionData("setBridgeMediatorL1BridgeParams", [[signers[1].address], [signers[2].address], [0], [AddressZero]]); await expect( timelock.execute(guard.address, setBridgeMediatorsPayload) ).to.be.reverted; + + // Chain Id is out of bounds + setBridgeMediatorsPayload = guard.interface.encodeFunctionData("setBridgeMediatorL1BridgeParams", + [[l1BridgeMediators[0]], [verifiersL2[0]], [0], [AddressZero]]); + await expect( + timelock.execute(guard.address, setBridgeMediatorsPayload) + ).to.be.reverted; + + setBridgeMediatorsPayload = guard.interface.encodeFunctionData("setBridgeMediatorL1BridgeParams", + [[l1BridgeMediators[0]], [verifiersL2[0]], [ethers.constants.MaxUint256], [AddressZero]]); + await expect( + timelock.execute(guard.address, setBridgeMediatorsPayload) + ).to.be.reverted; }); it("Pause the guard", async function () { @@ -716,8 +817,9 @@ describe("Community Multisig Guard", function () { const snapshot = await helpers.takeSnapshot(); // Authorize pre-defined target, selector and chainId + const permissions = new Array(l2ChainIds.length).fill(true); const setTargetSelectorChainIdsPayload = guard.interface.encodeFunctionData("setTargetSelectorChainIds", - [[gnosisContractAddress, polygonContractAddress], [l2Selector, l2Selector], [100, 137], [true, true]]); + [l2ContractAddresses, l2Selectors, l2ChainIds, permissions]); await timelock.execute(guard.address, setTargetSelectorChainIdsPayload); // Set bridge mediator contract addresses and chain Ids @@ -743,6 +845,31 @@ describe("Community Multisig Guard", function () { Bytes32Zero, Bytes32Zero, 0]); await guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero); + // Check Arbitrum payload + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[2], 0, arbitrumPayload, + Bytes32Zero, Bytes32Zero, 0]); + await guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero); + + // Check Arbitrum payload2 + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[2], 0, arbitrumPayload2, + Bytes32Zero, Bytes32Zero, 0]); + await guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero); + + // Check Optimism payload + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[4], 0, optimismPayload, + Bytes32Zero, Bytes32Zero, 0]); + await guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero); + + // Check celo payload + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[5], 0, celoPayload, + Bytes32Zero, Bytes32Zero, 0]); + await guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero); + + // Check celo payload2 + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[6], 0, celoPayload2, + Bytes32Zero, Bytes32Zero, 0]); + await guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero); + // Restore to the state of the snapshot await snapshot.restore(); }); @@ -752,13 +879,14 @@ describe("Community Multisig Guard", function () { const snapshot = await helpers.takeSnapshot(); // Authorize pre-defined target, selector and chainId + const permissions = new Array(l2ChainIds.length).fill(true); const setTargetSelectorChainIdsPayload = guard.interface.encodeFunctionData("setTargetSelectorChainIds", - [[gnosisContractAddress, polygonContractAddress], [l2Selector, l2Selector], [10200, 80001], [true, true]]); + [l2ContractAddresses, l2Selectors, l2ChainIds, permissions]); await timelock.execute(guard.address, setTargetSelectorChainIdsPayload); // Set bridge mediator contract addresses and chain Ids const setBridgeMediatorL1BridgeParamsPayload = guard.interface.encodeFunctionData("setBridgeMediatorL1BridgeParams", - [l1BridgeMediators, verifiersL2, [10200, 80001], l2BridgeMediators]); + [l1BridgeMediators, verifiersL2, l2ChainIds, l2BridgeMediators]); await timelock.execute(guard.address, setBridgeMediatorL1BridgeParamsPayload); // Check Gnosis payload @@ -835,6 +963,81 @@ describe("Community Multisig Guard", function () { guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) ).to.be.revertedWithCustomError(processBridgedDataPolygon, "IncorrectDataLength"); + // Check Arbitrum payload + // createRetryableTicket or unsafeCreateRetryableTicket selector is incorrect + let errorArbitrumPayload = "0x679b6de1"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[2], 0, errorArbitrumPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataArbitrum, "WrongSelector"); + + // arbitrum payload length is incorrect + errorArbitrumPayload = "0x679b6ded"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[2], 0, errorArbitrumPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataArbitrum, "IncorrectDataLength"); + + // Check Optimism payload + // optimismMessenger address is incorrect + let errorOptimismPayload = "0x3dbb202b000000000000000000000000670ac235ee13c0b2a5065282bbb0c61cfb354590000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000c4d3042d2b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000068118173028162c1b7c6bf8488bd5da2abd7c30f9d0000000000000000000000000000004440c10f1900000000000000000000000052370ee170c0e2767b32687166791973a0de7966000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[4], 0, errorOptimismPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataOptimism, "WrongL2BridgeMediator"); + + // processMessageFromSource selector is incorrect + errorOptimismPayload = "0x3dbb202b000000000000000000000000670ac235ee13c0b2a5065282bbb0c61cfb354592000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000001e848000000000000000000000000000000000000000000000000000000000000000c4d3042d2a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000068118173028162c1b7c6bf8488bd5da2abd7c30f9d0000000000000000000000000000004440c10f1900000000000000000000000052370ee170c0e2767b32687166791973a0de7966000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[4], 0, errorOptimismPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataOptimism, "WrongSelector"); + + // optimism sendMessage selector is incorrect + errorOptimismPayload = "0x3dbb202a"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[4], 0, errorOptimismPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataOptimism, "WrongSelector"); + + // optimism payload length is incorrect + errorOptimismPayload = "0x3dbb202b00"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[4], 0, errorOptimismPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataOptimism, "IncorrectDataLength"); + + // Check Celo payload + // wormholeMessenger address is incorrect + let errorCeloPayload = "0x4b5ca6f4000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000945550dece7e40ae70c6ebf5699637927eaf13e800000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e8480000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000945550dece7e40ae70c6ebf5699637927eaf13e9000000000000000000000000000000000000000000000000000000000000006834235f9d447f9f54167e2ac7a0f4283cb3fad6690000000000000000000000000000004440c10f1900000000000000000000000052370ee170c0e2767b32687166791973a0de79660000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[5], 0, errorCeloPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataOptimism, "WrongL2BridgeMediator"); + + // wormhole sendPayloadToEvm selector is incorrect + errorCeloPayload = "0x4b5ca6f0"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[5], 0, errorCeloPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataOptimism, "WrongSelector"); + + // celo payload length is incorrect + errorCeloPayload = "0x4b5ca6f400"; + txData = await timelock.interface.encodeFunctionData("schedule", [l1BridgeMediators[5], 0, errorCeloPayload, + Bytes32Zero, Bytes32Zero, 0]); + await expect( + guard.checkTransaction(timelock.address, 0, txData, 0, 0, 0, 0, AddressZero, AddressZero, "0x", AddressZero) + ).to.be.revertedWithCustomError(processBridgedDataOptimism, "IncorrectDataLength"); + // Restore to the state of the snapshot await snapshot.restore(); });