diff --git a/src/controllers/GovernanceTimelock.sol b/src/controllers/GovernanceTimelock.sol index bbe36e3..18c6bc8 100644 --- a/src/controllers/GovernanceTimelock.sol +++ b/src/controllers/GovernanceTimelock.sol @@ -54,13 +54,17 @@ contract GovernanceTimelock is IGovernanceTimelock { ////////////////////////////////////////////////////////////////*/ /// @param _admin is the address of admin contract that schedule txs - constructor(address _admin) { + /// @param _delay is the initial delay + constructor(address _admin, uint256 _delay) { if (_admin == address(0)) { revert Error.ZERO_ADDRESS_INPUT(); } + _checkDelay(_delay); admin = _admin; emit AdminUpdated(address(0), _admin); + + delay = _delay; } /*///////////////////////////////////////////////////////////////// @@ -130,13 +134,7 @@ contract GovernanceTimelock is IGovernanceTimelock { /// @inheritdoc IGovernanceTimelock function setDelay(uint256 _delay) external override onlySelf { - if (delay < MINIMUM_DELAY) { - revert Error.INVALID_DELAY_MIN(); - } - - if (delay > MAXIMUM_DELAY) { - revert Error.INVALID_DELAY_MAX(); - } + _checkDelay(_delay); uint256 oldDelay = delay; delay = _delay; @@ -155,4 +153,18 @@ contract GovernanceTimelock is IGovernanceTimelock { emit AdminUpdated(oldAdmin, _newAdmin); } + + /*///////////////////////////////////////////////////////////////// + PRIVATE/INTERNAL FUNCTIONS + ////////////////////////////////////////////////////////////////*/ + + function _checkDelay(uint256 _delay) internal pure { + if (_delay < MINIMUM_DELAY) { + revert Error.INVALID_DELAY_MIN(); + } + + if (_delay > MAXIMUM_DELAY) { + revert Error.INVALID_DELAY_MAX(); + } + } } diff --git a/test/Setup.t.sol b/test/Setup.t.sol index a264d6a..81f0f4b 100644 --- a/test/Setup.t.sol +++ b/test/Setup.t.sol @@ -29,6 +29,15 @@ abstract contract Setup is Test { /*/////////////////////////////////////////////////////////////// CONSTANTS //////////////////////////////////////////////////////////////*/ + /// @dev chain IDs + uint256 constant ETHEREUM_CHAIN_ID = 1; + uint256 constant BSC_CHAIN_ID = 56; + uint256 constant POLYGON_CHAIN_ID = 137; + + /// @dev common src and dst chain IDs + uint256 constant SRC_CHAIN_ID = ETHEREUM_CHAIN_ID; + uint256 constant DST_CHAIN_ID = POLYGON_CHAIN_ID; + /// @dev simulated caller address constant caller = address(10); address constant owner = address(420); @@ -54,10 +63,10 @@ abstract contract Setup is Test { //////////////////////////////////////////////////////////////*/ /// @notice configure all the chain ids we use for the tests (including src chain) /// id 0 represents src chain - uint256[] public ALL_CHAINS = [1, 56, 137]; + uint256[] public ALL_CHAINS = [ETHEREUM_CHAIN_ID, BSC_CHAIN_ID, POLYGON_CHAIN_ID]; /// @notice configure any new dst chains here - uint256[] public DST_CHAINS = [56, 137]; + uint256[] public DST_CHAINS = [BSC_CHAIN_ID, POLYGON_CHAIN_ID]; /// @notice configure all wormhole parameters in order of DST_CHAINS address[] public WORMHOLE_RELAYERS = [BSC_RELAYER, POLYGON_RELAYER]; @@ -255,7 +264,7 @@ abstract contract Setup is Test { address mmaReceiver = address(new MultiMessageReceiver{salt: _salt}()); contractAddress[chainId][bytes("MMA_RECEIVER")] = mmaReceiver; contractAddress[chainId][bytes("TIMELOCK")] = - address(address(new GovernanceTimelock{salt: _salt}(mmaReceiver))); + address(address(new GovernanceTimelock{salt: _salt}(mmaReceiver, 3 days))); } } diff --git a/test/integration-tests/GracePeriodExpiry.t.sol b/test/integration-tests/GracePeriodExpiry.t.sol index 906ece6..8bd13ed 100644 --- a/test/integration-tests/GracePeriodExpiry.t.sol +++ b/test/integration-tests/GracePeriodExpiry.t.sol @@ -17,22 +17,22 @@ import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol"; contract GracePeriodExpiryTest is Setup { MockUniswapReceiver target; - /// @dev intializes the setup + /// @dev initializes the setup function setUp() public override { super.setUp(); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); target = new MockUniswapReceiver(); } function test_timelockCheck() public { - vm.selectFork(fork[1]); + vm.selectFork(fork[SRC_CHAIN_ID]); vm.startPrank(caller); /// send cross-chain message using MMA infra vm.recordLogs(); - MultiMessageSender(contractAddress[1][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( - 137, + MultiMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( + DST_CHAIN_ID, address(target), abi.encode(MockUniswapReceiver.setValue.selector, ""), 0, @@ -44,21 +44,21 @@ contract GracePeriodExpiryTest is Setup { vm.recordLogs(); /// simulate off-chain actors - _simulatePayloadDelivery(1, 137, logs); + _simulatePayloadDelivery(SRC_CHAIN_ID, DST_CHAIN_ID, logs); bytes32 msgId = _getMsgId(vm.getRecordedLogs()); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); vm.recordLogs(); /// execute the message and move it to governance timelock contract - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).executeMessage(msgId); + MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).executeMessage(msgId); (uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) = _getExecParams(vm.getRecordedLogs()); - /// increment the time by 20 day (beyond expiry, delay) + /// increment the time by 21 day (beyond expiry, delay) /// @notice should revert here with TX_EXPIRED error - vm.warp(block.timestamp + 20 days); + vm.warp(block.timestamp + 21 days); vm.expectRevert(Error.TX_EXPIRED.selector); - GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).executeTransaction( + GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction( txId, finalTarget, value, data, eta ); } diff --git a/test/integration-tests/MultiMessageAggregation.t.sol b/test/integration-tests/MultiMessageAggregation.t.sol index c4ac948..8085839 100644 --- a/test/integration-tests/MultiMessageAggregation.t.sol +++ b/test/integration-tests/MultiMessageAggregation.t.sol @@ -16,23 +16,23 @@ import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol"; contract MultiMessageAggregationTest is Setup { MockUniswapReceiver target; - /// @dev intializes the setup + /// @dev initializes the setup function setUp() public override { super.setUp(); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); target = new MockUniswapReceiver(); } /// @dev just sends a message function test_mmaSendMessage() public { - vm.selectFork(fork[1]); + vm.selectFork(fork[SRC_CHAIN_ID]); vm.startPrank(caller); /// send cross-chain message using MMA infra vm.recordLogs(); - MultiMessageSender(contractAddress[1][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( - 137, + MultiMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( + DST_CHAIN_ID, address(target), abi.encode(MockUniswapReceiver.setValue.selector, ""), 0, @@ -44,19 +44,19 @@ contract MultiMessageAggregationTest is Setup { vm.recordLogs(); /// simulate off-chain actors - _simulatePayloadDelivery(1, 137, logs); + _simulatePayloadDelivery(SRC_CHAIN_ID, DST_CHAIN_ID, logs); bytes32 msgId = _getMsgId(vm.getRecordedLogs()); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); vm.recordLogs(); /// execute the message and move it to governance timelock contract - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).executeMessage(msgId); + MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).executeMessage(msgId); (uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) = _getExecParams(vm.getRecordedLogs()); - /// increment the time by 2 day (delay time) - vm.warp(block.timestamp + 2 days); - GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).executeTransaction( + /// increment the time by 7 days (delay time) + vm.warp(block.timestamp + 7 days); + GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction( txId, finalTarget, value, data, eta ); assertEq(target.i(), type(uint256).max); diff --git a/test/integration-tests/RemoteAdapterAdd.t.sol b/test/integration-tests/RemoteAdapterAdd.t.sol index 246d85c..7df4759 100644 --- a/test/integration-tests/RemoteAdapterAdd.t.sol +++ b/test/integration-tests/RemoteAdapterAdd.t.sol @@ -15,7 +15,7 @@ import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol"; /// @dev scenario: admin updates sender adapters on dst chain using message from source chain /// @notice handles both single add and multiple add contract RemoteAdapterAdd is Setup { - /// @dev intializes the setup + /// @dev initializes the setup function setUp() public override { super.setUp(); } @@ -56,9 +56,9 @@ contract RemoteAdapterAdd is Setup { /// send cross-chain message using MMA infra vm.recordLogs(); - MultiMessageSender(contractAddress[1][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( - 137, - address(contractAddress[137][bytes("MMA_RECEIVER")]), + MultiMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( + DST_CHAIN_ID, + address(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]), abi.encodeWithSelector(MultiMessageReceiver.updateReceiverAdapters.selector, adaptersToAdd, operation), 0, block.timestamp + EXPIRATION_CONSTANT @@ -69,25 +69,25 @@ contract RemoteAdapterAdd is Setup { vm.recordLogs(); /// simulate off-chain actors - _simulatePayloadDelivery(1, 137, logs); + _simulatePayloadDelivery(SRC_CHAIN_ID, DST_CHAIN_ID, logs); bytes32 msgId = _getMsgId(vm.getRecordedLogs()); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); vm.recordLogs(); /// execute the message and move it to governance timelock contract - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).executeMessage(msgId); + MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).executeMessage(msgId); (uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) = _getExecParams(vm.getRecordedLogs()); - /// increment the time by 2 day (delay time) - vm.warp(block.timestamp + 2 days); - GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).executeTransaction( + /// increment the time by 3 days (delay time) + vm.warp(block.timestamp + 3 days); + GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction( txId, finalTarget, value, data, eta ); for (uint256 j; j < adaptersToAdd.length; ++j) { - bool isTrusted = - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).isTrustedExecutor(adaptersToAdd[j]); + bool isTrusted = MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]) + .isTrustedExecutor(adaptersToAdd[j]); assert(isTrusted); } } diff --git a/test/integration-tests/RemoteAdapterRemove.t.sol b/test/integration-tests/RemoteAdapterRemove.t.sol index 818d7e4..5c033b5 100644 --- a/test/integration-tests/RemoteAdapterRemove.t.sol +++ b/test/integration-tests/RemoteAdapterRemove.t.sol @@ -15,7 +15,7 @@ import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol"; /// @dev scenario: admin updates sender adapters on dst chain using message from source chain /// @notice handles both single add and multiple remove contract RemoteAdapterRemove is Setup { - /// @dev intializes the setup + /// @dev initializes the setup function setUp() public override { super.setUp(); } @@ -23,7 +23,7 @@ contract RemoteAdapterRemove is Setup { /// @dev just remove one adapter and assert function test_remoteRemoveReceiverAdapterSingle() public { address[] memory adaptersToRemove = new address[](1); - adaptersToRemove[0] = contractAddress[137]["AXELAR_RECEIVER_ADAPTER"]; + adaptersToRemove[0] = contractAddress[DST_CHAIN_ID]["AXELAR_RECEIVER_ADAPTER"]; /// true = add /// false = remove @@ -41,8 +41,8 @@ contract RemoteAdapterRemove is Setup { _updateDummy(); address[] memory adaptersToRemove = new address[](2); - adaptersToRemove[0] = contractAddress[137]["AXELAR_RECEIVER_ADAPTER"]; - adaptersToRemove[1] = contractAddress[137]["WORMHOLE_RECEIVER_ADAPTER"]; + adaptersToRemove[0] = contractAddress[DST_CHAIN_ID]["AXELAR_RECEIVER_ADAPTER"]; + adaptersToRemove[1] = contractAddress[DST_CHAIN_ID]["WORMHOLE_RECEIVER_ADAPTER"]; /// true = add /// false = remove @@ -61,9 +61,9 @@ contract RemoteAdapterRemove is Setup { /// send cross-chain message using MMA infra vm.recordLogs(); - MultiMessageSender(contractAddress[1][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( - 137, - address(contractAddress[137][bytes("MMA_RECEIVER")]), + MultiMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( + DST_CHAIN_ID, + address(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]), abi.encodeWithSelector( MultiMessageReceiver.updateQuorumAndReceiverAdapter.selector, newQuorum, adaptersToRemove, operation ), @@ -76,30 +76,30 @@ contract RemoteAdapterRemove is Setup { vm.recordLogs(); /// simulate off-chain actors - _simulatePayloadDelivery(1, 137, logs); + _simulatePayloadDelivery(SRC_CHAIN_ID, DST_CHAIN_ID, logs); bytes32 msgId = _getMsgId(vm.getRecordedLogs()); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); vm.recordLogs(); /// execute the message and move it to governance timelock contract - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).executeMessage(msgId); + MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).executeMessage(msgId); (uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) = _getExecParams(vm.getRecordedLogs()); - /// increment the time by 2 day (delay time) - vm.warp(block.timestamp + 2 days); - GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).executeTransaction( + /// increment the time by 7 days (delay time) + vm.warp(block.timestamp + 7 days); + GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction( txId, finalTarget, value, data, eta ); /// @dev validates quorum post update - uint256 currQuorum = MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).quorum(); + uint256 currQuorum = MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).quorum(); assertEq(currQuorum, newQuorum); /// @dev validates adapters post update for (uint256 j; j < adaptersToRemove.length; ++j) { - bool isTrusted = - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).isTrustedExecutor(adaptersToRemove[j]); + bool isTrusted = MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]) + .isTrustedExecutor(adaptersToRemove[j]); assert(!isTrusted); } } @@ -113,8 +113,10 @@ contract RemoteAdapterRemove is Setup { bool[] memory operation = new bool[](1); operation[0] = true; - vm.startPrank(contractAddress[137]["TIMELOCK"]); - MultiMessageReceiver(contractAddress[137]["MMA_RECEIVER"]).updateReceiverAdapters(newDummyAdapter, operation); + vm.startPrank(contractAddress[DST_CHAIN_ID]["TIMELOCK"]); + MultiMessageReceiver(contractAddress[DST_CHAIN_ID]["MMA_RECEIVER"]).updateReceiverAdapters( + newDummyAdapter, operation + ); vm.stopPrank(); } } diff --git a/test/integration-tests/RemoteSetQuorum.t.sol b/test/integration-tests/RemoteSetQuorum.t.sol index 18d089e..b119363 100644 --- a/test/integration-tests/RemoteSetQuorum.t.sol +++ b/test/integration-tests/RemoteSetQuorum.t.sol @@ -14,7 +14,7 @@ import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol"; /// @dev scenario: admin updates quorum on dst chain using message from source chain contract RemoteQuorumUpdate is Setup { - /// @dev intializes the setup + /// @dev initializes the setup function setUp() public override { super.setUp(); } @@ -23,14 +23,14 @@ contract RemoteQuorumUpdate is Setup { function test_remoteQuorumUpdate() public { uint256 newQuorum = 1; - vm.selectFork(fork[1]); + vm.selectFork(fork[SRC_CHAIN_ID]); vm.startPrank(caller); /// send cross-chain message using MMA infra vm.recordLogs(); - MultiMessageSender(contractAddress[1][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( - 137, - address(contractAddress[137][bytes("MMA_RECEIVER")]), + MultiMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( + DST_CHAIN_ID, + address(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]), abi.encodeWithSelector(MultiMessageReceiver.updateQuorum.selector, newQuorum), 0, block.timestamp + EXPIRATION_CONSTANT @@ -42,26 +42,26 @@ contract RemoteQuorumUpdate is Setup { vm.recordLogs(); /// simulate off-chain actors - _simulatePayloadDelivery(1, 137, logs); + _simulatePayloadDelivery(SRC_CHAIN_ID, DST_CHAIN_ID, logs); bytes32 msgId = _getMsgId(vm.getRecordedLogs()); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); vm.recordLogs(); /// execute the message and move it to governance timelock contract - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).executeMessage(msgId); + MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).executeMessage(msgId); (uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) = _getExecParams(vm.getRecordedLogs()); - uint256 oldQuorum = MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).quorum(); + uint256 oldQuorum = MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).quorum(); assertEq(oldQuorum, 2); - /// increment the time by 2 day (delay time) - vm.warp(block.timestamp + 2 days); - GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).executeTransaction( + /// increment the time by 3 days (delay time) + vm.warp(block.timestamp + 3 days); + GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction( txId, finalTarget, value, data, eta ); - uint256 currQuorum = MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).quorum(); + uint256 currQuorum = MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).quorum(); assertEq(currQuorum, newQuorum); } } diff --git a/test/integration-tests/RemoteTimelockUpdate.t.sol b/test/integration-tests/RemoteTimelockUpdate.t.sol index 60eb9da..62bdd5d 100644 --- a/test/integration-tests/RemoteTimelockUpdate.t.sol +++ b/test/integration-tests/RemoteTimelockUpdate.t.sol @@ -14,7 +14,7 @@ import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol"; /// @dev scenario: admin updates timelock delay on dst chain using message from source chain contract RemoteTimelockUpdate is Setup { - /// @dev intializes the setup + /// @dev initializes the setup function setUp() public override { super.setUp(); } @@ -23,14 +23,14 @@ contract RemoteTimelockUpdate is Setup { function test_remoteTimelockUpdate() public { uint256 newDelay = 19 days; - vm.selectFork(fork[1]); + vm.selectFork(fork[SRC_CHAIN_ID]); vm.startPrank(caller); /// send cross-chain message using MMA infra vm.recordLogs(); - MultiMessageSender(contractAddress[1][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( - 137, - address(contractAddress[137][bytes("TIMELOCK")]), + MultiMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( + POLYGON_CHAIN_ID, + address(contractAddress[POLYGON_CHAIN_ID][bytes("TIMELOCK")]), abi.encodeWithSelector(GovernanceTimelock.setDelay.selector, newDelay), 0, block.timestamp + EXPIRATION_CONSTANT @@ -42,26 +42,26 @@ contract RemoteTimelockUpdate is Setup { vm.recordLogs(); /// simulate off-chain actors - _simulatePayloadDelivery(1, 137, logs); + _simulatePayloadDelivery(ETHEREUM_CHAIN_ID, POLYGON_CHAIN_ID, logs); bytes32 msgId = _getMsgId(vm.getRecordedLogs()); - vm.selectFork(fork[137]); + vm.selectFork(fork[POLYGON_CHAIN_ID]); vm.recordLogs(); /// execute the message and move it to governance timelock contract - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).executeMessage(msgId); + MultiMessageReceiver(contractAddress[POLYGON_CHAIN_ID][bytes("MMA_RECEIVER")]).executeMessage(msgId); (uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) = _getExecParams(vm.getRecordedLogs()); - uint256 oldDelay = GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).delay(); - assertEq(oldDelay, GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).MINIMUM_DELAY()); + uint256 oldDelay = GovernanceTimelock(contractAddress[POLYGON_CHAIN_ID][bytes("TIMELOCK")]).delay(); + assertEq(oldDelay, 3 days); - /// increment the time by 2 day (delay time) - vm.warp(block.timestamp + 2 days); - GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).executeTransaction( + /// increment the time by 3 days (delay time) + vm.warp(block.timestamp + 3 days); + GovernanceTimelock(contractAddress[POLYGON_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction( txId, finalTarget, value, data, eta ); - uint256 currDelay = GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).delay(); + uint256 currDelay = GovernanceTimelock(contractAddress[POLYGON_CHAIN_ID][bytes("TIMELOCK")]).delay(); assertEq(currDelay, newDelay); } } diff --git a/test/integration-tests/TimelockCheck.t.sol b/test/integration-tests/TimelockCheck.t.sol index 5227504..2b729e1 100644 --- a/test/integration-tests/TimelockCheck.t.sol +++ b/test/integration-tests/TimelockCheck.t.sol @@ -18,23 +18,23 @@ import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol"; contract TimelockCheckTest is Setup { MockUniswapReceiver target; - /// @dev intializes the setup + /// @dev initializes the setup function setUp() public override { super.setUp(); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); target = new MockUniswapReceiver(); } /// @dev just sends a message function test_timelockCheck() public { - vm.selectFork(fork[1]); + vm.selectFork(fork[SRC_CHAIN_ID]); vm.startPrank(caller); /// send cross-chain message using MMA infra vm.recordLogs(); - MultiMessageSender(contractAddress[1][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( - 137, + MultiMessageSender(contractAddress[SRC_CHAIN_ID][bytes("MMA_SENDER")]).remoteCall{value: 2 ether}( + DST_CHAIN_ID, address(target), abi.encode(MockUniswapReceiver.setValue.selector, ""), 0, @@ -46,13 +46,13 @@ contract TimelockCheckTest is Setup { vm.recordLogs(); /// simulate off-chain actors - _simulatePayloadDelivery(1, 137, logs); + _simulatePayloadDelivery(SRC_CHAIN_ID, DST_CHAIN_ID, logs); bytes32 msgId = _getMsgId(vm.getRecordedLogs()); - vm.selectFork(fork[137]); + vm.selectFork(fork[DST_CHAIN_ID]); vm.recordLogs(); /// execute the message and move it to governance timelock contract - MultiMessageReceiver(contractAddress[137][bytes("MMA_RECEIVER")]).executeMessage(msgId); + MultiMessageReceiver(contractAddress[DST_CHAIN_ID][bytes("MMA_RECEIVER")]).executeMessage(msgId); (uint256 txId, address finalTarget, uint256 value, bytes memory data, uint256 eta) = _getExecParams(vm.getRecordedLogs()); @@ -60,13 +60,13 @@ contract TimelockCheckTest is Setup { /// @notice should revert here with TX_TIMELOCKED error vm.warp(block.timestamp + 1 days); vm.expectRevert(Error.TX_TIMELOCKED.selector); - GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).executeTransaction( + GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction( txId, finalTarget, value, data, eta ); /// increment the time by 2 day (delay time) vm.warp(block.timestamp + 2 days); - GovernanceTimelock(contractAddress[137][bytes("TIMELOCK")]).executeTransaction( + GovernanceTimelock(contractAddress[DST_CHAIN_ID][bytes("TIMELOCK")]).executeTransaction( txId, finalTarget, value, data, eta ); assertEq(target.i(), type(uint256).max); diff --git a/test/unit-tests/GovernanceTimelock.t.sol b/test/unit-tests/GovernanceTimelock.t.sol new file mode 100644 index 0000000..1f3ebe5 --- /dev/null +++ b/test/unit-tests/GovernanceTimelock.t.sol @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.9; + +/// library imports +import {Vm} from "forge-std/Test.sol"; + +/// local imports +import "../Setup.t.sol"; +import "src/libraries/Error.sol"; +import {GovernanceTimelock} from "src/controllers/GovernanceTimelock.sol"; + +contract GovernanceTimelockTest is Setup { + event TransactionScheduled(uint256 indexed txId, address target, uint256 value, bytes data, uint256 eta); + event TransactionExecuted(uint256 indexed txId, address target, uint256 value, bytes data, uint256 eta); + + event ExecutionPeriodUpdated(uint256 oldPeriod, uint256 newPeriod); + event DelayUpdated(uint256 oldDelay, uint256 newDelay); + event AdminUpdated(address oldAdmin, address newAdmin); + + GovernanceTimelock timelock; + address admin; + + /// @dev initializes the setup + function setUp() public override { + super.setUp(); + + vm.selectFork(fork[DST_CHAIN_ID]); + // admin is set to the receiver in setup + admin = contractAddress[DST_CHAIN_ID]["MMA_RECEIVER"]; + timelock = GovernanceTimelock(contractAddress[DST_CHAIN_ID]["TIMELOCK"]); + } + + /// @dev constructor + function test_constructor() public { + // checks existing setup + assertEq(address(timelock.admin()), admin); + assertEq(timelock.delay(), 3 days); + assertEq(timelock.txCounter(), 0); + } + + /// @dev cannot be called with zero address admin + function test_constructor_zero_address_input() public { + vm.expectRevert(Error.ZERO_ADDRESS_INPUT.selector); + new GovernanceTimelock(address(0), 3 days); + } + + /// @dev schedule transaction + function test_schedule_transaction() public { + vm.startPrank(admin); + + uint256 eta = block.timestamp + timelock.delay(); + + vm.expectEmit(true, true, true, true, address(timelock)); + emit TransactionScheduled(1, address(42), 1, bytes("42"), eta); + + timelock.scheduleTransaction(address(42), 1, bytes("42")); + + assertEq(timelock.txCounter(), 1); + assertEq( + timelock.scheduledTransaction(1), keccak256(abi.encodePacked(address(42), uint256(1), bytes("42"), eta)) + ); + } + + /// @dev only admin can schedule transaction + function test_schedule_transaction_only_admin() public { + vm.startPrank(caller); + + vm.expectRevert(Error.CALLER_NOT_ADMIN.selector); + timelock.scheduleTransaction(address(42), 1, bytes("42")); + } + + /// @dev cannot call with target address of 0 + function test_schedule_transaction_zero_target_address() public { + vm.startPrank(admin); + + vm.expectRevert(Error.INVALID_TARGET.selector); + timelock.scheduleTransaction(address(0), 1, bytes("42")); + } + + /// @dev execute transaction + function test_execute_transaction() public { + vm.startPrank(admin); + + // schedule a transaction first + uint256 eta = block.timestamp + timelock.delay(); + timelock.scheduleTransaction(address(42), 1, bytes("42")); + + // let timelock pass + skip(timelock.delay()); + vm.startPrank(caller); + + vm.expectEmit(true, true, true, true, address(timelock)); + emit TransactionExecuted(1, address(42), uint256(1), bytes("42"), eta); + + timelock.executeTransaction{value: 1}(1, address(42), 1, bytes("42"), eta); + + assertTrue(timelock.isExecuted(1)); + } + + /// @dev cannot execute with zero tx ID + function test_execute_transaction_zero_tx_id() public { + vm.startPrank(caller); + + vm.expectRevert(Error.INVALID_TX_ID.selector); + timelock.executeTransaction(0, address(42), 0, bytes("42"), block.timestamp); + } + + /// @dev cannot execute with a tx ID too large + function test_execute_transaction_tx_id_too_large() public { + vm.startPrank(caller); + + vm.expectRevert(Error.INVALID_TX_ID.selector); + timelock.executeTransaction(1, address(42), 0, bytes("42"), block.timestamp); + } + + /// @dev cannot execute tx that is already executed + function test_execute_transaction_already_executed() public { + vm.startPrank(admin); + + uint256 eta = block.timestamp + timelock.delay(); + timelock.scheduleTransaction(address(42), 0, bytes("42")); + skip(timelock.delay()); + + vm.startPrank(caller); + + timelock.executeTransaction(1, address(42), 0, bytes("42"), eta); + + vm.expectRevert(Error.TX_ALREADY_EXECUTED.selector); + timelock.executeTransaction(1, address(42), 0, bytes("42"), eta); + } + + /// @dev cannot execute tx with wrong hash + function test_execute_transaction_invalid_input() public { + vm.startPrank(admin); + + uint256 eta = block.timestamp + timelock.delay(); + timelock.scheduleTransaction(address(42), 0, bytes("42")); + skip(timelock.delay()); + + vm.startPrank(caller); + + vm.expectRevert(Error.INVALID_TX_INPUT.selector); + timelock.executeTransaction(1, address(42), 0, bytes("42"), eta + 1); + } + + /// @dev cannot execute tx that is still timelocked + function test_execute_transaction_timelocked() public { + vm.startPrank(admin); + + uint256 eta = block.timestamp + timelock.delay(); + timelock.scheduleTransaction(address(42), 0, bytes("42")); + + vm.startPrank(caller); + + vm.expectRevert(Error.TX_TIMELOCKED.selector); + timelock.executeTransaction(1, address(42), 0, bytes("42"), eta); + } + + /// @dev cannot execute tx that has expired + function test_execute_transaction_expired() public { + vm.startPrank(admin); + + uint256 eta = block.timestamp + timelock.delay(); + timelock.scheduleTransaction(address(42), 0, bytes("42")); + skip(timelock.delay() + timelock.GRACE_PERIOD() + 1); + + vm.startPrank(caller); + + vm.expectRevert(Error.TX_EXPIRED.selector); + timelock.executeTransaction(1, address(42), 0, bytes("42"), eta); + } + + /// @dev cannot execute tx with invalid value + function test_execute_transaction_invalid_value() public { + vm.startPrank(admin); + + uint256 eta = block.timestamp + timelock.delay(); + timelock.scheduleTransaction(address(42), 1, bytes("42")); + skip(timelock.delay()); + + vm.startPrank(caller); + + vm.expectRevert(Error.INVALID_MSG_VALUE.selector); + timelock.executeTransaction(1, address(42), 1, bytes("42"), eta); + } + + /// @dev failed to execute tx on dst chain + function test_execute_transaction_fails_on_dst() public { + vm.startPrank(admin); + + uint256 eta = block.timestamp + timelock.delay(); + // Use admin as dummy target address + timelock.scheduleTransaction(admin, 0, bytes("42")); + skip(timelock.delay()); + + vm.startPrank(caller); + + vm.expectRevert(Error.EXECUTION_FAILS_ON_DST.selector); + timelock.executeTransaction(1, admin, 0, bytes("42"), eta); + } + + /// @dev sets delay + function test_set_delay() public { + vm.startPrank(address(timelock)); + + uint256 oldDelay = timelock.delay(); + vm.expectEmit(true, true, true, true, address(timelock)); + emit DelayUpdated(oldDelay, 7 days); + + timelock.setDelay(7 days); + } + + /// @dev only timelock can set delay + function test_set_delay_only_self() public { + vm.startPrank(caller); + + vm.expectRevert(Error.INVALID_SELF_CALLER.selector); + timelock.setDelay(7 days); + } + + /// @dev cannot set delay below minimum + function test_set_delay_below_minimum() public { + vm.startPrank(address(timelock)); + + uint256 minDelay = timelock.MINIMUM_DELAY(); + vm.expectRevert(Error.INVALID_DELAY_MIN.selector); + timelock.setDelay(minDelay - 1); + } + + /// @dev cannot set delay above maximum + function test_set_delay_above_maximum() public { + vm.startPrank(address(timelock)); + + uint256 maxDelay = timelock.MAXIMUM_DELAY(); + vm.expectRevert(Error.INVALID_DELAY_MAX.selector); + timelock.setDelay(maxDelay + 1); + } + + /// @dev sets admin + function test_set_admin() public { + vm.startPrank(address(timelock)); + + address oldAdmin = timelock.admin(); + vm.expectEmit(true, true, true, true, address(timelock)); + emit AdminUpdated(oldAdmin, address(42)); + + timelock.setAdmin(address(42)); + } + + /// @dev only timelock can set admin + function test_set_admin_only_self() public { + vm.startPrank(caller); + + vm.expectRevert(Error.INVALID_SELF_CALLER.selector); + timelock.setAdmin(address(42)); + } + + /// @dev cannot set admin to zero address + function test_set_admin_zero_address() public { + vm.startPrank(address(timelock)); + + vm.expectRevert(Error.ZERO_TIMELOCK_ADMIN.selector); + timelock.setAdmin(address(0)); + } + + /// @dev sets delay via scheduled transaction + function test_set_delay_scheduled() public { + vm.startPrank(address(admin)); + + bytes memory data = abi.encodeWithSelector(GovernanceTimelock.setDelay.selector, 10 days); + timelock.scheduleTransaction(address(timelock), 0, data); + uint256 eta = block.timestamp + timelock.delay(); + + skip(3 days); + + timelock.executeTransaction(1, address(timelock), 0, data, eta); + + assertEq(timelock.delay(), 10 days); + } + + /// @dev sets admin via scheduled transaction + function test_set_admin_scheduled() public { + vm.startPrank(address(admin)); + + bytes memory data = abi.encodeWithSelector(GovernanceTimelock.setAdmin.selector, address(42)); + timelock.scheduleTransaction(address(timelock), 0, data); + uint256 eta = block.timestamp + timelock.delay(); + + skip(3 days); + + timelock.executeTransaction(1, address(timelock), 0, data, eta); + + assertEq(timelock.admin(), address(42)); + } +} diff --git a/test/unit-tests/MultiMessageReceiver.t.sol b/test/unit-tests/MultiMessageReceiver.t.sol index 2d0f09e..b22881b 100644 --- a/test/unit-tests/MultiMessageReceiver.t.sol +++ b/test/unit-tests/MultiMessageReceiver.t.sol @@ -6,7 +6,6 @@ import {Vm} from "forge-std/Test.sol"; /// local imports import "test/Setup.t.sol"; -import "test/contracts-mock/MockUniswapReceiver.sol"; import "src/adapters/Wormhole/WormholeReceiverAdapter.sol"; import "src/libraries/Error.sol"; import "src/libraries/Message.sol"; @@ -20,7 +19,6 @@ contract MultiMessageReceiverTest is Setup { ); event MessageExecuted(bytes32 indexed msgId, address target, uint256 nativeValue, uint256 nonce, bytes callData); - uint256 constant DST_CHAIN_ID = 137; MultiMessageReceiver receiver; address wormholeAdapterAddr; address axelarAdapterAddr; @@ -155,7 +153,7 @@ contract MultiMessageReceiverTest is Setup { vm.startPrank(wormholeAdapterAddr); MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -170,9 +168,9 @@ contract MultiMessageReceiverTest is Setup { receiver.receiveMessage(message, "WORMHOLE"); - assertEq(receiver.isExecuted(msgId), false); + assertFalse(receiver.isExecuted(msgId)); - assertEq(receiver.isDuplicateAdapter(msgId, wormholeAdapterAddr), true); + assertTrue(receiver.isDuplicateAdapter(msgId, wormholeAdapterAddr)); assertEq(receiver.messageVotes(msgId), 1); @@ -190,7 +188,7 @@ contract MultiMessageReceiverTest is Setup { vm.startPrank(wormholeAdapterAddr); MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -215,7 +213,7 @@ contract MultiMessageReceiverTest is Setup { vm.expectRevert(Error.INVALID_RECEIVER_ADAPTER.selector); receiver.receiveMessage( MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(0), nonce: 0, @@ -234,8 +232,8 @@ contract MultiMessageReceiverTest is Setup { vm.expectRevert(Error.INVALID_DST_CHAIN.selector); receiver.receiveMessage( MessageLibrary.Message({ - srcChainId: 1, - dstChainId: 56, + srcChainId: SRC_CHAIN_ID, + dstChainId: BSC_CHAIN_ID, target: address(0), nonce: 0, callData: bytes(""), @@ -253,7 +251,7 @@ contract MultiMessageReceiverTest is Setup { vm.expectRevert(Error.INVALID_TARGET.selector); receiver.receiveMessage( MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(0), nonce: 0, @@ -272,7 +270,7 @@ contract MultiMessageReceiverTest is Setup { vm.expectRevert(Error.INVALID_SENDER_CHAIN_ID.selector); receiver.receiveMessage( MessageLibrary.Message({ - srcChainId: 56, + srcChainId: BSC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 0, @@ -289,7 +287,7 @@ contract MultiMessageReceiverTest is Setup { vm.startPrank(wormholeAdapterAddr); MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -307,7 +305,7 @@ contract MultiMessageReceiverTest is Setup { /// @dev executed message should be rejected function test_receiver_message_msg_id_already_executed() public { MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -336,7 +334,7 @@ contract MultiMessageReceiverTest is Setup { vm.startPrank(wormholeAdapterAddr); MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -356,7 +354,7 @@ contract MultiMessageReceiverTest is Setup { receiver.executeMessage(msgId); - assertEq(receiver.isExecuted(msgId), true); + assertTrue(receiver.isExecuted(msgId)); } /// @dev cannot executes message past deadline @@ -364,7 +362,7 @@ contract MultiMessageReceiverTest is Setup { vm.startPrank(wormholeAdapterAddr); MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -385,7 +383,7 @@ contract MultiMessageReceiverTest is Setup { vm.startPrank(wormholeAdapterAddr); MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -411,7 +409,7 @@ contract MultiMessageReceiverTest is Setup { vm.startPrank(wormholeAdapterAddr); MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -577,7 +575,7 @@ contract MultiMessageReceiverTest is Setup { vm.startPrank(wormholeAdapterAddr); MessageLibrary.Message memory message = MessageLibrary.Message({ - srcChainId: 1, + srcChainId: SRC_CHAIN_ID, dstChainId: DST_CHAIN_ID, target: address(42), nonce: 42, @@ -590,7 +588,7 @@ contract MultiMessageReceiverTest is Setup { receiver.receiveMessage(message, "WORMHOLE"); (bool isExecuted, uint256 msgCurrentVotes, string[] memory successfulBridge) = receiver.getMessageInfo(msgId); - assertEq(isExecuted, false); + assertFalse(isExecuted); assertEq(msgCurrentVotes, 1); assertEq(successfulBridge.length, 1); assertEq(successfulBridge[0], WormholeReceiverAdapter(wormholeAdapterAddr).name()); diff --git a/test/unit-tests/MultiMessageSender.t.sol b/test/unit-tests/MultiMessageSender.t.sol index 42c1e77..c672b49 100644 --- a/test/unit-tests/MultiMessageSender.t.sol +++ b/test/unit-tests/MultiMessageSender.t.sol @@ -30,9 +30,6 @@ contract MultiMessageSenderTest is Setup { event SenderAdapterUpdated(address senderAdapter, bool add); event ErrorSendMessage(address senderAdapter, MessageLibrary.Message message); - uint256 constant SRC_CHAIN_ID = 1; - uint256 constant DST_CHAIN_ID = 137; - MultiMessageSender sender; address receiver; IGAC gac; @@ -104,6 +101,7 @@ contract MultiMessageSenderTest is Setup { function test_remote_call_refund() public { vm.startPrank(caller); + // NOTE: caller is also configured as the refund address in this test setup uint256 expiration = block.timestamp + EXPIRATION_CONSTANT; uint256 nativeValue = 2 ether;