From c78f58181304167b03ac3ea148354ef8650b2c96 Mon Sep 17 00:00:00 2001 From: sendra Date: Thu, 21 Dec 2023 19:02:35 +0100 Subject: [PATCH] fix: add integration tests --- .../Deploy_Granular_CCC_Guardian.s.sol | 2 +- tests/BaseTest.sol | 29 ++ .../GranularGuardianAccessControl.t.sol | 56 ++-- .../GranularGuardianAccessControl_int.t.sol | 247 ++++++++++++++++++ tests/utils/TestUtils.sol | 21 ++ 5 files changed, 333 insertions(+), 22 deletions(-) create mode 100644 tests/access_control/GranularGuardianAccessControl_int.t.sol create mode 100644 tests/utils/TestUtils.sol diff --git a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol index bb1ee12a..afa852fb 100644 --- a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol +++ b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol @@ -22,7 +22,7 @@ abstract contract BaseDeployGranularGuardian is BaseScript { function CROSS_CHAIN_CONTROLLER() public view virtual returns (address); function _execute(DeployerHelpers.Addresses memory addresses) internal override { - address granularCCCGuardian = ( + address granularCCCGuardian = address( new GranularGuardianAccessControl( AAVE_GUARDIAN(), RETRY_GUARDIAN(), diff --git a/tests/BaseTest.sol b/tests/BaseTest.sol index 91f21149..1f6e0302 100644 --- a/tests/BaseTest.sol +++ b/tests/BaseTest.sol @@ -2,7 +2,12 @@ pragma solidity ^0.8.0; import 'forge-std/Test.sol'; +import {ChainIds} from '../src/contracts/libs/ChainIds.sol'; import {Transaction, EncodedTransaction, Envelope, EncodedEnvelope} from '../src/contracts/libs/EncodingUtils.sol'; +import {ICrossChainForwarder} from '../src/contracts/interfaces/ICrossChainForwarder.sol'; +import {ICrossChainReceiver} from '../src/contracts/interfaces/ICrossChainReceiver.sol'; +import {CrossChainController, ICrossChainController} from '../src/contracts/CrossChainController.sol'; +import {CrossChainControllerWithEmergencyMode, ICrossChainControllerWithEmergencyMode} from '../src/contracts/CrossChainControllerWithEmergencyMode.sol'; contract BaseTest is Test { bytes internal constant MESSAGE = bytes('this is the message to send'); @@ -42,6 +47,30 @@ contract BaseTest is Test { uint256 envelopeNonce; } + modifier generateEmergencyState() { + _; + } + + modifier generateRetryTxState( + address cccOwner, + address ccc, + uint256 destinationChainId, + address destination, + uint256 gasLimit + ) { + // set caller as an approved sender + address[] memory senders = new address[](1); + senders[0] = address(this); + + vm.startPrank(cccOwner); + ICrossChainForwarder(ccc).approveSenders(senders); + vm.stopPrank(); + + // call forward message + ICrossChainForwarder(ccc).forwardMessage(destinationChainId, destination, gasLimit, MESSAGE); + _; + } + function _generateExtendedTransaction( TestParams memory testParams ) internal pure returns (ExtendedTransaction memory) { diff --git a/tests/access_control/GranularGuardianAccessControl.t.sol b/tests/access_control/GranularGuardianAccessControl.t.sol index 961094cb..d9dc3a37 100644 --- a/tests/access_control/GranularGuardianAccessControl.t.sol +++ b/tests/access_control/GranularGuardianAccessControl.t.sol @@ -2,38 +2,32 @@ pragma solidity ^0.8.0; import 'forge-std/Test.sol'; +import {TestUtils} from '../utils/TestUtils.sol'; import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; -import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; import {OwnableWithGuardian, IWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol'; contract GranularGuardianAccessControlTest is Test { address public constant RETRY_USER = address(123); address public constant SOLVE_EMERGENCY_USER = address(1234); + address public constant CROSS_CHAIN_CONTROLLER = address(12345); - address public constant BGD_GUARDIAN = 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF; - address public constant AAVE_GUARDIAN = 0xCA76Ebd8617a03126B6FB84F9b1c1A0fB71C2633; + address public constant BGD_GUARDIAN = address(1956); + address public constant AAVE_GUARDIAN = address(1238975); bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; GranularGuardianAccessControl public control; function setUp() public { - vm.createSelectFork('ethereum', 18826980); - control = new GranularGuardianAccessControl( AAVE_GUARDIAN, RETRY_USER, SOLVE_EMERGENCY_USER, - GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER - ); - - hoax(BGD_GUARDIAN); - OwnableWithGuardian(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER).updateGuardian( - address(control) + CROSS_CHAIN_CONTROLLER ); } function test_initialization() public { - assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER); + assertEq(control.CROSS_CHAIN_CONTROLLER(), CROSS_CHAIN_CONTROLLER); assertEq(control.hasRole(DEFAULT_ADMIN_ROLE, AAVE_GUARDIAN), true); assertEq(control.getRoleAdmin(control.RETRY_ROLE()), DEFAULT_ADMIN_ROLE); assertEq(control.getRoleAdmin(control.SOLVE_EMERGENCY_ROLE()), DEFAULT_ADMIN_ROLE); @@ -52,7 +46,11 @@ contract GranularGuardianAccessControlTest is Test { function test_retryTxWhenWrongCaller() public { vm.expectRevert( bytes( - 'AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' + string.concat( + 'AccessControl: account 0x', + TestUtils.toAsciiString(address(this)), + ' is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' + ) ) ); _retryTx(); @@ -69,7 +67,11 @@ contract GranularGuardianAccessControlTest is Test { function test_retryEnvelopeWhenWrongCaller(bytes32 mockEnvId) public { vm.expectRevert( bytes( - 'AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' + string.concat( + 'AccessControl: account 0x', + TestUtils.toAsciiString(address(this)), + ' is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' + ) ) ); _retryEnvelope(mockEnvId); @@ -84,7 +86,11 @@ contract GranularGuardianAccessControlTest is Test { function test_solveEmergencyWhenWrongCaller() public { vm.expectRevert( bytes( - 'AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0xf4cdc679c22cbf47d6de8e836ce79ffdae51f38408dcde3f0645de7634fa607d' + string.concat( + 'AccessControl: account 0x', + TestUtils.toAsciiString(address(this)), + ' is missing role 0xf4cdc679c22cbf47d6de8e836ce79ffdae51f38408dcde3f0645de7634fa607d' + ) ) ); _solveEmergency(); @@ -92,15 +98,23 @@ contract GranularGuardianAccessControlTest is Test { function test_updateGuardian(address newGuardian) public { vm.startPrank(AAVE_GUARDIAN); + vm.mockCall( + CROSS_CHAIN_CONTROLLER, + abi.encodeWithSelector(IWithGuardian.updateGuardian.selector, newGuardian), + abi.encode() + ); control.updateGuardian(newGuardian); - assertEq(IWithGuardian(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER).guardian(), newGuardian); vm.stopPrank(); } - function test_solveEmergencyWhenWrongCaller(address newGuardian) public { + function test_updateGuardianWhenWrongCaller(address newGuardian) public { vm.expectRevert( bytes( - 'AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000' + string.concat( + 'AccessControl: account 0x', + TestUtils.toAsciiString(address(this)), + ' is missing role 0x0000000000000000000000000000000000000000000000000000000000000000' + ) ) ); control.updateGuardian(newGuardian); @@ -131,7 +145,7 @@ contract GranularGuardianAccessControlTest is Test { ); vm.mockCall( - GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + CROSS_CHAIN_CONTROLLER, abi.encodeWithSelector( ICrossChainControllerWithEmergencyMode.solveEmergency.selector, newConfirmations, @@ -170,7 +184,7 @@ contract GranularGuardianAccessControlTest is Test { uint256 gasLimit = 300_000; vm.mockCall( - GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + CROSS_CHAIN_CONTROLLER, abi.encodeWithSelector(ICrossChainForwarder.retryEnvelope.selector, envelope, gasLimit), abi.encode(mockEnvId) ); @@ -183,7 +197,7 @@ contract GranularGuardianAccessControlTest is Test { address[] memory bridgeAdaptersToRetry = new address[](0); vm.mockCall( - GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + CROSS_CHAIN_CONTROLLER, abi.encodeWithSelector( ICrossChainForwarder.retryTransaction.selector, encodedTransaction, diff --git a/tests/access_control/GranularGuardianAccessControl_int.t.sol b/tests/access_control/GranularGuardianAccessControl_int.t.sol new file mode 100644 index 00000000..39ea224b --- /dev/null +++ b/tests/access_control/GranularGuardianAccessControl_int.t.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {TestUtils} from '../utils/TestUtils.sol'; +import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; +import {OwnableWithGuardian, IWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol'; +import '../BaseTest.sol'; + +contract GranularGuardianAccessControlIntTest is BaseTest { + address public constant RETRY_USER = address(123); + address public constant SOLVE_EMERGENCY_USER = address(1234); + + address public constant BGD_GUARDIAN = 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF; + address public constant AAVE_GUARDIAN = 0xCA76Ebd8617a03126B6FB84F9b1c1A0fB71C2633; + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + // list of supported chains + uint256 destinationChainId = ChainIds.POLYGON; + + GranularGuardianAccessControl public control; + + function setUp() public { + vm.createSelectFork('ethereum', 18826980); + + control = new GranularGuardianAccessControl( + AAVE_GUARDIAN, + RETRY_USER, + SOLVE_EMERGENCY_USER, + GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER + ); + + hoax(BGD_GUARDIAN); + OwnableWithGuardian(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER).updateGuardian( + address(control) + ); + } + + function test_initialization() public { + assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER); + assertEq(control.hasRole(DEFAULT_ADMIN_ROLE, AAVE_GUARDIAN), true); + assertEq(control.getRoleAdmin(control.RETRY_ROLE()), DEFAULT_ADMIN_ROLE); + assertEq(control.getRoleAdmin(control.SOLVE_EMERGENCY_ROLE()), DEFAULT_ADMIN_ROLE); + assertEq(control.getRoleMemberCount(control.RETRY_ROLE()), 1); + assertEq(control.getRoleMemberCount(control.SOLVE_EMERGENCY_ROLE()), 1); + assertEq(control.getRoleMember(control.RETRY_ROLE(), 0), RETRY_USER); + assertEq(control.getRoleMember(control.SOLVE_EMERGENCY_ROLE(), 0), SOLVE_EMERGENCY_USER); + } + + function test_retryTx( + address destination, + uint256 gasLimit + ) + public + generateRetryTxState( + GovernanceV3Ethereum.EXECUTOR_LVL_1, + GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + destinationChainId, + destination, + gasLimit + ) + { + vm.assume(gasLimit < 300_000); + ExtendedTransaction memory extendedTx = _generateExtendedTransaction( + TestParams({ + destination: destination, + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: ICrossChainForwarder(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1, + transactionNonce: ICrossChainForwarder(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1 + }) + ); + + ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( + GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER + ).getForwarderBridgeAdaptersByChain(destinationChainId); + address[] memory bridgeAdaptersToRetry = new address[](1); + bridgeAdaptersToRetry[0] = bridgeAdaptersByChain[2].currentChainBridgeAdapter; + + vm.startPrank(RETRY_USER); + control.retryTransaction(extendedTx.transactionEncoded, gasLimit, bridgeAdaptersToRetry); + vm.stopPrank(); + } + + function test_retryTxWhenWrongCaller() public { + uint256 gasLimit = 300_000; + address[] memory bridgeAdaptersToRetry = new address[](0); + + vm.expectRevert( + bytes( + string.concat( + 'AccessControl: account 0x', + TestUtils.toAsciiString(address(this)), + ' is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' + ) + ) + ); + + control.retryTransaction(abi.encode('will not get used'), gasLimit, bridgeAdaptersToRetry); + } + + // + // function test_retryEnvelope(bytes32 mockEnvId) public { + // vm.startPrank(RETRY_USER); + // // bytes32 mockEnvId = keccak256(abi.encode('mock envelope id')); + // bytes32 envId = _retryEnvelope(mockEnvId); + // assertEq(envId, mockEnvId); + // vm.stopPrank(); + // } + + function test_retryEnvelopeWhenWrongCaller(uint256 gasLimit) public { + Envelope memory envelope; + + vm.expectRevert( + bytes( + string.concat( + 'AccessControl: account 0x', + TestUtils.toAsciiString(address(this)), + ' is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' + ) + ) + ); + control.retryEnvelope(envelope, gasLimit); + } + + // function test_solveEmergency() public { + // vm.startPrank(SOLVE_EMERGENCY_USER); + // _solveEmergency(); + // vm.stopPrank(); + // } + + function test_solveEmergencyWhenWrongCaller() public { + vm.expectRevert( + bytes( + string.concat( + 'AccessControl: account 0x', + TestUtils.toAsciiString(address(this)), + ' is missing role 0xf4cdc679c22cbf47d6de8e836ce79ffdae51f38408dcde3f0645de7634fa607d' + ) + ) + ); + control.solveEmergency( + new ICrossChainReceiver.ConfirmationInput[](0), + new ICrossChainReceiver.ValidityTimestampInput[](0), + new ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[](0), + new ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[](0), + new address[](0), + new address[](0), + new ICrossChainForwarder.ForwarderBridgeAdapterConfigInput[](0), + new ICrossChainForwarder.BridgeAdapterToDisable[](0) + ); + } + + function test_updateGuardian(address newGuardian) public { + vm.startPrank(AAVE_GUARDIAN); + control.updateGuardian(newGuardian); + assertEq(IWithGuardian(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER).guardian(), newGuardian); + vm.stopPrank(); + } + + function test_updateGuardianWhenWrongCaller(address newGuardian) public { + vm.expectRevert( + bytes( + string.concat( + 'AccessControl: account 0x', + TestUtils.toAsciiString(address(this)), + ' is missing role 0x0000000000000000000000000000000000000000000000000000000000000000' + ) + ) + ); + control.updateGuardian(newGuardian); + } + // + // function _solveEmergency() public { + // ICrossChainReceiver.ConfirmationInput[] + // memory newConfirmations = new ICrossChainReceiver.ConfirmationInput[](0); + // ICrossChainReceiver.ValidityTimestampInput[] + // memory newValidityTimestamp = new ICrossChainReceiver.ValidityTimestampInput[](0); + // ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[] + // memory receiverBridgeAdaptersToAllow = new ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[]( + // 0 + // ); + // ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[] + // memory receiverBridgeAdaptersToDisallow = new ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[]( + // 0 + // ); + // address[] memory sendersToApprove = new address[](0); + // address[] memory sendersToRemove = new address[](0); + // ICrossChainForwarder.ForwarderBridgeAdapterConfigInput[] + // memory forwarderBridgeAdaptersToEnable = new ICrossChainForwarder.ForwarderBridgeAdapterConfigInput[]( + // 0 + // ); + // ICrossChainForwarder.BridgeAdapterToDisable[] + // memory forwarderBridgeAdaptersToDisable = new ICrossChainForwarder.BridgeAdapterToDisable[]( + // 0 + // ); + // + // vm.mockCall( + // GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + // abi.encodeWithSelector( + // ICrossChainControllerWithEmergencyMode.solveEmergency.selector, + // newConfirmations, + // newValidityTimestamp, + // receiverBridgeAdaptersToAllow, + // receiverBridgeAdaptersToDisallow, + // sendersToApprove, + // sendersToRemove, + // forwarderBridgeAdaptersToEnable, + // forwarderBridgeAdaptersToDisable + // ), + // abi.encode() + // ); + // return + // control.solveEmergency( + // newConfirmations, + // newValidityTimestamp, + // receiverBridgeAdaptersToAllow, + // receiverBridgeAdaptersToDisallow, + // sendersToApprove, + // sendersToRemove, + // forwarderBridgeAdaptersToEnable, + // forwarderBridgeAdaptersToDisable + // ); + // } + // + // function _retryEnvelope(bytes32 mockEnvId) internal returns (bytes32) { + // Envelope memory envelope = Envelope({ + // nonce: 1, + // origin: address(12468), + // destination: address(2341), + // originChainId: 1, + // destinationChainId: 137, + // message: abi.encode('mock message') + // }); + // uint256 gasLimit = 300_000; + // + // vm.mockCall( + // GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + // abi.encodeWithSelector(ICrossChainForwarder.retryEnvelope.selector, envelope, gasLimit), + // abi.encode(mockEnvId) + // ); + // return control.retryEnvelope(envelope, gasLimit); + // } +} diff --git a/tests/utils/TestUtils.sol b/tests/utils/TestUtils.sol new file mode 100644 index 00000000..218be293 --- /dev/null +++ b/tests/utils/TestUtils.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +library TestUtils { + function toAsciiString(address x) internal pure returns (string memory) { + bytes memory s = new bytes(40); + for (uint i = 0; i < 20; i++) { + bytes1 b = bytes1(uint8(uint(uint160(x)) / (2 ** (8 * (19 - i))))); + bytes1 hi = bytes1(uint8(b) / 16); + bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi)); + s[2 * i] = char(hi); + s[2 * i + 1] = char(lo); + } + return string(s); + } + + function char(bytes1 b) internal pure returns (bytes1 c) { + if (uint8(b) < 10) return bytes1(uint8(b) + 0x30); + else return bytes1(uint8(b) + 0x57); + } +}