From f7d4d18c748a37f52a8a7c06ba3c69058035279d Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 20 Dec 2023 11:16:57 +0100 Subject: [PATCH 01/24] feat: Added access control contract to make guardian granular --- .../GranularGuardianAccessControl.sol | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/contracts/access_control/GranularGuardianAccessControl.sol diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol new file mode 100644 index 00000000..6377a467 --- /dev/null +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.8; + +import {ICrossChainReceiver} from '../interfaces/ICrossChainReceiver.sol'; +import {ICrossChainForwarder} from '../interfaces/ICrossChainForwarder.sol'; +import {ICrossChainControllerWithEmergencyMode} from '../interfaces/ICrossChainControllerWithEmergencyMode.sol'; +import {Envelope} from '../libs/EncodingUtils.sol'; + +contract GranularGuardianAccessControl { + address public immutable CROSS_CHAIN_CONTROLLER; + + address internal _retryGuardian; + address internal _solveEmergencyGuardian; + + event RetryGuardianUpdated(address retryGuardian); + event SolveEmergencyGuardianUpdated(address solveEmergencyGuardian); + + modifier onlyRetryGuardian() { + require(msg.sender == _retryGuardian, 'NOT_RETRY_GUARDIAN'); + _; + } + + modifier onlySolveEmergencyGuardian() { + require(msg.sender == _solveEmergencyGuardian, 'NOT_SOLVE_EMERGENCY_GUARDIAN'); + _; + } + + constructor(address retryGuardian, address solveEmergencyGuardian, address crossChainController) { + require(crossChainController != address(0), 'INVALID_CROSS_CHAIN_CONTROLLER'); + + CROSS_CHAIN_CONTROLLER = crossChainController; + + _updateRetryGuardian(retryGuardian); + _updateSolveEmergencyGuardian(solveEmergencyGuardian); + } + + function updateSolveEmergencyGuardian( + address solveEmergencyGuardian + ) external onlySolveEmergencyGuardian { + _updateSolveEmergencyGuardian(solveEmergencyGuardian); + } + + function updateRetryGuardian(address retryGuardian) external onlyRetryGuardian { + _updateRetryGuardian(retryGuardian); + } + + function retryEnvelope( + Envelope memory envelope, + uint256 gasLimit + ) external onlyRetryGuardian returns (bytes32) { + return ICrossChainForwarder(CROSS_CHAIN_CONTROLLER).retryEnvelope(envelope, gasLimit); + } + + function retryTransaction( + bytes memory encodedTransaction, + uint256 gasLimit, + address[] memory bridgeAdaptersToRetry + ) external onlyRetryGuardian { + ICrossChainForwarder(CROSS_CHAIN_CONTROLLER).retryTransaction( + encodedTransaction, + gasLimit, + bridgeAdaptersToRetry + ); + } + + function solveEmergency( + ICrossChainReceiver.ConfirmationInput[] memory newConfirmations, + ICrossChainReceiver.ValidityTimestampInput[] memory newValidityTimestamp, + ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[] memory receiverBridgeAdaptersToAllow, + ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[] memory receiverBridgeAdaptersToDisallow, + address[] memory sendersToApprove, + address[] memory sendersToRemove, + ICrossChainForwarder.ForwarderBridgeAdapterConfigInput[] memory forwarderBridgeAdaptersToEnable, + ICrossChainForwarder.BridgeAdapterToDisable[] memory forwarderBridgeAdaptersToDisable + ) external onlySolveEmergencyGuardian { + ICrossChainControllerWithEmergencyMode(CROSS_CHAIN_CONTROLLER).solveEmergency( + newConfirmations, + newValidityTimestamp, + receiverBridgeAdaptersToAllow, + receiverBridgeAdaptersToDisallow, + sendersToApprove, + sendersToRemove, + forwarderBridgeAdaptersToEnable, + forwarderBridgeAdaptersToDisable + ); + } + + function _updateRetryGuardian(address retryGuardian) internal { + require(retryGuardian != address(0), 'INVALID_RETRY_GUARDIAN'); + + _retryGuardian = retryGuardian; + emit RetryGuardianUpdated(retryGuardian); + } + + function _updateSolveEmergencyGuardian(address solveEmergencyGuardian) internal { + require(solveEmergencyGuardian != address(0), 'INVALID_SOLVE_EMERGENCY_GUARDIAN'); + + _solveEmergencyGuardian = solveEmergencyGuardian; + emit SolveEmergencyGuardianUpdated(solveEmergencyGuardian); + } +} From d4b671e11c7347f4a6a44829a2fcddf64dd203ea Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 20 Dec 2023 12:37:47 +0100 Subject: [PATCH 02/24] fix: updated with oz (modified) enumerable access control --- .../AccessControlEnumerable.sol | 79 +++++++++++++++++++ .../GranularGuardianAccessControl.sol | 64 +++++---------- .../IGranularGuardianAccessControl.sol | 38 +++++++++ 3 files changed, 136 insertions(+), 45 deletions(-) create mode 100644 src/contracts/access_control/AccessControlEnumerable.sol create mode 100644 src/contracts/access_control/IGranularGuardianAccessControl.sol diff --git a/src/contracts/access_control/AccessControlEnumerable.sol b/src/contracts/access_control/AccessControlEnumerable.sol new file mode 100644 index 00000000..53fb7302 --- /dev/null +++ b/src/contracts/access_control/AccessControlEnumerable.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol) +// Modified from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.9/contracts/access/AccessControlEnumerable.sol +// at commit: https://github.com/OpenZeppelin/openzeppelin-contracts/commit/afb20119b33072da041c97ea717d3ce4417b5e01 + +pragma solidity ^0.8.0; + +import 'openzeppelin-contracts/contracts/access/IAccessControlEnumerable.sol'; +import 'openzeppelin-contracts/contracts/access/AccessControl.sol'; +import 'openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol'; + +/** + * @dev Extension of {AccessControl} that allows enumerating the members of each role. + */ +abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl { + using EnumerableSet for EnumerableSet.AddressSet; + + mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers; + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IAccessControlEnumerable).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev Returns one of the accounts that have `role`. `index` must be a + * value between 0 and {getRoleMemberCount}, non-inclusive. + * + * Role bearers are not sorted in any particular way, and their ordering may + * change at any point. + * + * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure + * you perform all queries on the same block. See the following + * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] + * for more information. + */ + function getRoleMember( + bytes32 role, + uint256 index + ) public view virtual override returns (address) { + return _roleMembers[role].at(index); + } + + /** + * @dev Returns the number of accounts that have `role`. Can be used + * together with {getRoleMember} to enumerate all bearers of a role. + */ + function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) { + return _roleMembers[role].length(); + } + + /** + * @dev Overload {_grantRole} to track enumerable memberships + */ + function _grantRole(bytes32 role, address account) internal virtual override { + super._grantRole(role, account); + _roleMembers[role].add(account); + } + + /** + * @dev Overload {_revokeRole} to track enumerable memberships + */ + function _revokeRole(bytes32 role, address account) internal virtual override { + super._revokeRole(role, account); + _roleMembers[role].remove(account); + } + + /** + * @dev Returns the accounts that have `role`. Added by BGD + * + */ + function getAccountsWithRole(bytes32 role) external view virtual returns (address[] memory) { + return _roleMembers[role].values(); + } +} diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol index 6377a467..79f30d79 100644 --- a/src/contracts/access_control/GranularGuardianAccessControl.sol +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -5,49 +5,37 @@ import {ICrossChainReceiver} from '../interfaces/ICrossChainReceiver.sol'; import {ICrossChainForwarder} from '../interfaces/ICrossChainForwarder.sol'; import {ICrossChainControllerWithEmergencyMode} from '../interfaces/ICrossChainControllerWithEmergencyMode.sol'; import {Envelope} from '../libs/EncodingUtils.sol'; +//import {AccessControlEnumerable} from 'openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol'; +import {AccessControlEnumerable} from './AccessControlEnumerable.sol'; -contract GranularGuardianAccessControl { +contract GranularGuardianAccessControl is AccessControlEnumerable { address public immutable CROSS_CHAIN_CONTROLLER; - address internal _retryGuardian; - address internal _solveEmergencyGuardian; + bytes32 public constant SOLVE_EMERGENCY_ROLE = keccak256('SOLVE_EMERGENCY_ROLE'); + bytes32 public constant RETRY_ROLE = keccak256('RETRY_ROLE'); - event RetryGuardianUpdated(address retryGuardian); - event SolveEmergencyGuardianUpdated(address solveEmergencyGuardian); - - modifier onlyRetryGuardian() { - require(msg.sender == _retryGuardian, 'NOT_RETRY_GUARDIAN'); - _; - } - - modifier onlySolveEmergencyGuardian() { - require(msg.sender == _solveEmergencyGuardian, 'NOT_SOLVE_EMERGENCY_GUARDIAN'); - _; - } - - constructor(address retryGuardian, address solveEmergencyGuardian, address crossChainController) { + constructor( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address crossChainController + ) { require(crossChainController != address(0), 'INVALID_CROSS_CHAIN_CONTROLLER'); CROSS_CHAIN_CONTROLLER = crossChainController; - _updateRetryGuardian(retryGuardian); - _updateSolveEmergencyGuardian(solveEmergencyGuardian); - } - - function updateSolveEmergencyGuardian( - address solveEmergencyGuardian - ) external onlySolveEmergencyGuardian { - _updateSolveEmergencyGuardian(solveEmergencyGuardian); - } + _setupRole(DEFAULT_ADMIN_ROLE, defaultAdmin); + _setRoleAdmin(SOLVE_EMERGENCY_ROLE, DEFAULT_ADMIN_ROLE); + _setRoleAdmin(RETRY_ROLE, DEFAULT_ADMIN_ROLE); - function updateRetryGuardian(address retryGuardian) external onlyRetryGuardian { - _updateRetryGuardian(retryGuardian); + _grantRole(SOLVE_EMERGENCY_ROLE, solveEmergencyGuardian); + _grantRole(RETRY_ROLE, retryGuardian); } function retryEnvelope( Envelope memory envelope, uint256 gasLimit - ) external onlyRetryGuardian returns (bytes32) { + ) external onlyRole(RETRY_ROLE) returns (bytes32) { return ICrossChainForwarder(CROSS_CHAIN_CONTROLLER).retryEnvelope(envelope, gasLimit); } @@ -55,7 +43,7 @@ contract GranularGuardianAccessControl { bytes memory encodedTransaction, uint256 gasLimit, address[] memory bridgeAdaptersToRetry - ) external onlyRetryGuardian { + ) external onlyRole(RETRY_ROLE) { ICrossChainForwarder(CROSS_CHAIN_CONTROLLER).retryTransaction( encodedTransaction, gasLimit, @@ -72,7 +60,7 @@ contract GranularGuardianAccessControl { address[] memory sendersToRemove, ICrossChainForwarder.ForwarderBridgeAdapterConfigInput[] memory forwarderBridgeAdaptersToEnable, ICrossChainForwarder.BridgeAdapterToDisable[] memory forwarderBridgeAdaptersToDisable - ) external onlySolveEmergencyGuardian { + ) external onlyRole(SOLVE_EMERGENCY_ROLE) { ICrossChainControllerWithEmergencyMode(CROSS_CHAIN_CONTROLLER).solveEmergency( newConfirmations, newValidityTimestamp, @@ -84,18 +72,4 @@ contract GranularGuardianAccessControl { forwarderBridgeAdaptersToDisable ); } - - function _updateRetryGuardian(address retryGuardian) internal { - require(retryGuardian != address(0), 'INVALID_RETRY_GUARDIAN'); - - _retryGuardian = retryGuardian; - emit RetryGuardianUpdated(retryGuardian); - } - - function _updateSolveEmergencyGuardian(address solveEmergencyGuardian) internal { - require(solveEmergencyGuardian != address(0), 'INVALID_SOLVE_EMERGENCY_GUARDIAN'); - - _solveEmergencyGuardian = solveEmergencyGuardian; - emit SolveEmergencyGuardianUpdated(solveEmergencyGuardian); - } } diff --git a/src/contracts/access_control/IGranularGuardianAccessControl.sol b/src/contracts/access_control/IGranularGuardianAccessControl.sol new file mode 100644 index 00000000..f7e8793a --- /dev/null +++ b/src/contracts/access_control/IGranularGuardianAccessControl.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.8; + +import {Envelope} from '../libs/EncodingUtils.sol'; + +/** + * @title IGranularGuardianAccessControl + * @author BGD Labs + * @notice interface containing the objects, events and methods definitions of the GranularGuardianAccessControl contract + */ +interface IGranularGuardianAccessControl { + /** + * @notice method called to re forward a previously sent envelope. + * @param envelope the Envelope type data + * @param gasLimit gas cost on receiving side of the message + * @return the transaction id that has the retried envelope + * @dev This method will send an existing Envelope using a new Transaction. + * @dev This method should be used when the intention is to send the Envelope as if it was a new message. This way on + the Receiver side it will start from 0 to count for the required confirmations. (usual use case would be for + when an envelope has been invalidated on Receiver side, and needs to be retried as a new message) + */ + function retryEnvelope(Envelope memory envelope, uint256 gasLimit) external returns (bytes32); + + /** + * @notice method to retry forwarding an already forwarded transaction + * @param encodedTransaction the encoded Transaction data + * @param gasLimit limit of gas to spend on forwarding per bridge + * @param bridgeAdaptersToRetry list of bridge adapters to be used for the transaction forwarding retry + * @dev This method will send an existing Transaction with its Envelope to the specified adapters. + * @dev Should be used when some of the bridges on the initial forwarding did not work (out of gas), + and we want the Transaction with Envelope to still account for the required confirmations on the Receiver side + */ + function retryTransaction( + bytes memory encodedTransaction, + uint256 gasLimit, + address[] memory bridgeAdaptersToRetry + ) external; +} From 98fad052e6436f1af4c2c1d0aee7c0f069cc55e6 Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 20 Dec 2023 12:56:25 +0100 Subject: [PATCH 03/24] fix: added natspec --- .../GranularGuardianAccessControl.sol | 26 ++++++++-- .../IGranularGuardianAccessControl.sol | 47 ++++++++++++++++++- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol index 79f30d79..f31ad120 100644 --- a/src/contracts/access_control/GranularGuardianAccessControl.sol +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -1,19 +1,34 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.8; -import {ICrossChainReceiver} from '../interfaces/ICrossChainReceiver.sol'; import {ICrossChainForwarder} from '../interfaces/ICrossChainForwarder.sol'; import {ICrossChainControllerWithEmergencyMode} from '../interfaces/ICrossChainControllerWithEmergencyMode.sol'; -import {Envelope} from '../libs/EncodingUtils.sol'; -//import {AccessControlEnumerable} from 'openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol'; import {AccessControlEnumerable} from './AccessControlEnumerable.sol'; +import {IGranularGuardianAccessControl, Envelope} from './IGranularGuardianAccessControl.sol'; + +//import {AccessControlEnumerable} from 'openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol'; -contract GranularGuardianAccessControl is AccessControlEnumerable { +/** + * @title GranularGuardianAccessControl + * @author BGD Labs + * @notice Contract to manage a granular access to the methods safeguarded by guardian on CrossChainController + */ +contract GranularGuardianAccessControl is AccessControlEnumerable, IGranularGuardianAccessControl { + /// @inheritdoc IGranularGuardianAccessControl address public immutable CROSS_CHAIN_CONTROLLER; + /// @inheritdoc IGranularGuardianAccessControl bytes32 public constant SOLVE_EMERGENCY_ROLE = keccak256('SOLVE_EMERGENCY_ROLE'); + + /// @inheritdoc IGranularGuardianAccessControl bytes32 public constant RETRY_ROLE = keccak256('RETRY_ROLE'); + /** + * @param defaultAdmin address that will have control of the default admin + * @param retryGuardian address to be added to the retry role + * @param solveEmergencyGuardian address to be added to the solve emergency role + * @param crossChainController address of the CrossChainController + */ constructor( address defaultAdmin, address retryGuardian, @@ -32,6 +47,7 @@ contract GranularGuardianAccessControl is AccessControlEnumerable { _grantRole(RETRY_ROLE, retryGuardian); } + /// @inheritdoc IGranularGuardianAccessControl function retryEnvelope( Envelope memory envelope, uint256 gasLimit @@ -39,6 +55,7 @@ contract GranularGuardianAccessControl is AccessControlEnumerable { return ICrossChainForwarder(CROSS_CHAIN_CONTROLLER).retryEnvelope(envelope, gasLimit); } + /// @inheritdoc IGranularGuardianAccessControl function retryTransaction( bytes memory encodedTransaction, uint256 gasLimit, @@ -51,6 +68,7 @@ contract GranularGuardianAccessControl is AccessControlEnumerable { ); } + /// @inheritdoc IGranularGuardianAccessControl function solveEmergency( ICrossChainReceiver.ConfirmationInput[] memory newConfirmations, ICrossChainReceiver.ValidityTimestampInput[] memory newValidityTimestamp, diff --git a/src/contracts/access_control/IGranularGuardianAccessControl.sol b/src/contracts/access_control/IGranularGuardianAccessControl.sol index f7e8793a..6ac613f5 100644 --- a/src/contracts/access_control/IGranularGuardianAccessControl.sol +++ b/src/contracts/access_control/IGranularGuardianAccessControl.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.8; import {Envelope} from '../libs/EncodingUtils.sol'; +import {ICrossChainReceiver} from '../interfaces/ICrossChainReceiver.sol'; +import {ICrossChainForwarder} from '../interfaces/ICrossChainForwarder.sol'; /** * @title IGranularGuardianAccessControl @@ -11,6 +13,7 @@ import {Envelope} from '../libs/EncodingUtils.sol'; interface IGranularGuardianAccessControl { /** * @notice method called to re forward a previously sent envelope. + This method is only callable by the accounts holding the RETRY_ROLE role * @param envelope the Envelope type data * @param gasLimit gas cost on receiving side of the message * @return the transaction id that has the retried envelope @@ -22,7 +25,8 @@ interface IGranularGuardianAccessControl { function retryEnvelope(Envelope memory envelope, uint256 gasLimit) external returns (bytes32); /** - * @notice method to retry forwarding an already forwarded transaction + * @notice method to retry forwarding an already forwarded transaction. + This method is only callable by the accounts holding the RETRY_ROLE role * @param encodedTransaction the encoded Transaction data * @param gasLimit limit of gas to spend on forwarding per bridge * @param bridgeAdaptersToRetry list of bridge adapters to be used for the transaction forwarding retry @@ -35,4 +39,45 @@ interface IGranularGuardianAccessControl { uint256 gasLimit, address[] memory bridgeAdaptersToRetry ) external; + + /** + * @notice method to solve an emergency. This method is only callable by the accounts holding the SOLVE_EMERGENCY_ROLE role + * @param newConfirmations number of confirmations necessary for a message to be routed to destination + * @param newValidityTimestamp timestamp in seconds indicating the point to where not confirmed messages will be + * invalidated. + * @param receiverBridgeAdaptersToAllow list of bridge adapter addresses to be allowed to receive messages + * @param receiverBridgeAdaptersToDisallow list of bridge adapter addresses to be disallowed + * @param sendersToApprove list of addresses to be approved as senders + * @param sendersToRemove list of sender addresses to be removed + * @param forwarderBridgeAdaptersToEnable list of bridge adapters to be enabled to send messages + * @param forwarderBridgeAdaptersToDisable list of bridge adapters to be disabled + */ + function solveEmergency( + ICrossChainReceiver.ConfirmationInput[] memory newConfirmations, + ICrossChainReceiver.ValidityTimestampInput[] memory newValidityTimestamp, + ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[] memory receiverBridgeAdaptersToAllow, + ICrossChainReceiver.ReceiverBridgeAdapterConfigInput[] memory receiverBridgeAdaptersToDisallow, + address[] memory sendersToApprove, + address[] memory sendersToRemove, + ICrossChainForwarder.ForwarderBridgeAdapterConfigInput[] memory forwarderBridgeAdaptersToEnable, + ICrossChainForwarder.BridgeAdapterToDisable[] memory forwarderBridgeAdaptersToDisable + ) external; + + /** + * @notice method to get the address of the CrossChainController where the contract points to + * @return the address of the CrossChainController + */ + function CROSS_CHAIN_CONTROLLER() external view returns (address); + + /** + * @notice method to get the solve emergency role + * @return the solve emergency role id + */ + function SOLVE_EMERGENCY_ROLE() external view returns (bytes32); + + /** + * @notice method to get the retry role + * @return the retry role id + */ + function RETRY_ROLE() external view returns (bytes32); } From ac89c0e094012006fe5810b37b0b3dbe3bdfd888 Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 20 Dec 2023 15:15:40 +0100 Subject: [PATCH 04/24] fix: Add test --- .../AccessControlEnumerable.sol | 79 -------- .../GranularGuardianAccessControl.sol | 7 +- .../GranularGuardianAccessControl.t.sol | 180 ++++++++++++++++++ 3 files changed, 183 insertions(+), 83 deletions(-) delete mode 100644 src/contracts/access_control/AccessControlEnumerable.sol create mode 100644 tests/access_control/GranularGuardianAccessControl.t.sol diff --git a/src/contracts/access_control/AccessControlEnumerable.sol b/src/contracts/access_control/AccessControlEnumerable.sol deleted file mode 100644 index 53fb7302..00000000 --- a/src/contracts/access_control/AccessControlEnumerable.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol) -// Modified from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.9/contracts/access/AccessControlEnumerable.sol -// at commit: https://github.com/OpenZeppelin/openzeppelin-contracts/commit/afb20119b33072da041c97ea717d3ce4417b5e01 - -pragma solidity ^0.8.0; - -import 'openzeppelin-contracts/contracts/access/IAccessControlEnumerable.sol'; -import 'openzeppelin-contracts/contracts/access/AccessControl.sol'; -import 'openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol'; - -/** - * @dev Extension of {AccessControl} that allows enumerating the members of each role. - */ -abstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl { - using EnumerableSet for EnumerableSet.AddressSet; - - mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers; - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return - interfaceId == type(IAccessControlEnumerable).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev Returns one of the accounts that have `role`. `index` must be a - * value between 0 and {getRoleMemberCount}, non-inclusive. - * - * Role bearers are not sorted in any particular way, and their ordering may - * change at any point. - * - * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure - * you perform all queries on the same block. See the following - * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post] - * for more information. - */ - function getRoleMember( - bytes32 role, - uint256 index - ) public view virtual override returns (address) { - return _roleMembers[role].at(index); - } - - /** - * @dev Returns the number of accounts that have `role`. Can be used - * together with {getRoleMember} to enumerate all bearers of a role. - */ - function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) { - return _roleMembers[role].length(); - } - - /** - * @dev Overload {_grantRole} to track enumerable memberships - */ - function _grantRole(bytes32 role, address account) internal virtual override { - super._grantRole(role, account); - _roleMembers[role].add(account); - } - - /** - * @dev Overload {_revokeRole} to track enumerable memberships - */ - function _revokeRole(bytes32 role, address account) internal virtual override { - super._revokeRole(role, account); - _roleMembers[role].remove(account); - } - - /** - * @dev Returns the accounts that have `role`. Added by BGD - * - */ - function getAccountsWithRole(bytes32 role) external view virtual returns (address[] memory) { - return _roleMembers[role].values(); - } -} diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol index f31ad120..64a87987 100644 --- a/src/contracts/access_control/GranularGuardianAccessControl.sol +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -3,10 +3,8 @@ pragma solidity ^0.8.8; import {ICrossChainForwarder} from '../interfaces/ICrossChainForwarder.sol'; import {ICrossChainControllerWithEmergencyMode} from '../interfaces/ICrossChainControllerWithEmergencyMode.sol'; -import {AccessControlEnumerable} from './AccessControlEnumerable.sol'; -import {IGranularGuardianAccessControl, Envelope} from './IGranularGuardianAccessControl.sol'; - -//import {AccessControlEnumerable} from 'openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol'; +import {IGranularGuardianAccessControl, Envelope, ICrossChainReceiver} from './IGranularGuardianAccessControl.sol'; +import {AccessControlEnumerable} from 'openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol'; /** * @title GranularGuardianAccessControl @@ -36,6 +34,7 @@ contract GranularGuardianAccessControl is AccessControlEnumerable, IGranularGuar address crossChainController ) { require(crossChainController != address(0), 'INVALID_CROSS_CHAIN_CONTROLLER'); + require(defaultAdmin != address(0), 'INVALID_DEFAULT_ADMIN'); CROSS_CHAIN_CONTROLLER = crossChainController; diff --git a/tests/access_control/GranularGuardianAccessControl.t.sol b/tests/access_control/GranularGuardianAccessControl.t.sol new file mode 100644 index 00000000..a740ef5b --- /dev/null +++ b/tests/access_control/GranularGuardianAccessControl.t.sol @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import 'forge-std/Test.sol'; +import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; +import {OwnableWithGuardian} 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 BGD_GUARDIAN = 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF; + bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + + GranularGuardianAccessControl public control; + + function setUp() public { + vm.createSelectFork('ethereum', 18826980); + + control = new GranularGuardianAccessControl( + GovernanceV3Ethereum.EXECUTOR_LVL_1, + 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, GovernanceV3Ethereum.EXECUTOR_LVL_1), 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() public { + vm.startPrank(RETRY_USER); + _retryTx(); + vm.stopPrank(); + } + + function test_retryTxWhenWrongCaller() public { + vm.expectRevert( + bytes( + 'AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' + ) + ); + _retryTx(); + } + + 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(bytes32 mockEnvId) public { + vm.expectRevert( + bytes( + 'AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' + ) + ); + _retryEnvelope(mockEnvId); + } + + function test_solveEmergency() public { + vm.startPrank(SOLVE_EMERGENCY_USER); + _solveEmergency(); + vm.stopPrank(); + } + + function test_solveEmergencyWhenWrongCaller() public { + vm.expectRevert( + bytes( + 'AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0xf4cdc679c22cbf47d6de8e836ce79ffdae51f38408dcde3f0645de7634fa607d' + ) + ); + _solveEmergency(); + } + + 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); + } + + function _retryTx() internal { + bytes memory encodedTransaction = abi.encode('some mock tx'); + uint256 gasLimit = 300_000; + address[] memory bridgeAdaptersToRetry = new address[](0); + + vm.mockCall( + GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + abi.encodeWithSelector( + ICrossChainForwarder.retryTransaction.selector, + encodedTransaction, + gasLimit, + bridgeAdaptersToRetry + ), + abi.encode() + ); + control.retryTransaction(encodedTransaction, gasLimit, bridgeAdaptersToRetry); + } +} From e114d27a7f1b3531303514de16c749ab1b2a453b Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 20 Dec 2023 16:42:31 +0100 Subject: [PATCH 05/24] fix: added method to change guardian --- .../GranularGuardianAccessControl.sol | 9 ++++++++ .../IGranularGuardianAccessControl.sol | 5 ++++ .../GranularGuardianAccessControl.t.sol | 23 ++++++++++++++++--- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol index 64a87987..54faa262 100644 --- a/src/contracts/access_control/GranularGuardianAccessControl.sol +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -5,6 +5,7 @@ import {ICrossChainForwarder} from '../interfaces/ICrossChainForwarder.sol'; import {ICrossChainControllerWithEmergencyMode} from '../interfaces/ICrossChainControllerWithEmergencyMode.sol'; import {IGranularGuardianAccessControl, Envelope, ICrossChainReceiver} from './IGranularGuardianAccessControl.sol'; import {AccessControlEnumerable} from 'openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol'; +import {IWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol'; /** * @title GranularGuardianAccessControl @@ -89,4 +90,12 @@ contract GranularGuardianAccessControl is AccessControlEnumerable, IGranularGuar forwarderBridgeAdaptersToDisable ); } + + /// @inheritdoc IGranularGuardianAccessControl + function updateGuardian( + address newCrossChainControllerGuardian + ) external onlyRole(DEFAULT_ADMIN_ROLE) { + require(newCrossChainControllerGuardian != address(0), 'INVALID_GUARDIAN'); + IWithGuardian(CROSS_CHAIN_CONTROLLER).updateGuardian(newCrossChainControllerGuardian); + } } diff --git a/src/contracts/access_control/IGranularGuardianAccessControl.sol b/src/contracts/access_control/IGranularGuardianAccessControl.sol index 6ac613f5..f99bb698 100644 --- a/src/contracts/access_control/IGranularGuardianAccessControl.sol +++ b/src/contracts/access_control/IGranularGuardianAccessControl.sol @@ -63,6 +63,11 @@ interface IGranularGuardianAccessControl { ICrossChainForwarder.BridgeAdapterToDisable[] memory forwarderBridgeAdaptersToDisable ) external; + /** + * @notice method to update the CrossChainController guardian when this contract has been set as guardian + */ + function updateGuardian(address newCrossChainControllerGuardian) external; + /** * @notice method to get the address of the CrossChainController where the contract points to * @return the address of the CrossChainController diff --git a/tests/access_control/GranularGuardianAccessControl.t.sol b/tests/access_control/GranularGuardianAccessControl.t.sol index a740ef5b..961094cb 100644 --- a/tests/access_control/GranularGuardianAccessControl.t.sol +++ b/tests/access_control/GranularGuardianAccessControl.t.sol @@ -4,13 +4,14 @@ pragma solidity ^0.8.0; import 'forge-std/Test.sol'; import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; -import {OwnableWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.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 BGD_GUARDIAN = 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF; + address public constant AAVE_GUARDIAN = 0xCA76Ebd8617a03126B6FB84F9b1c1A0fB71C2633; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; GranularGuardianAccessControl public control; @@ -19,7 +20,7 @@ contract GranularGuardianAccessControlTest is Test { vm.createSelectFork('ethereum', 18826980); control = new GranularGuardianAccessControl( - GovernanceV3Ethereum.EXECUTOR_LVL_1, + AAVE_GUARDIAN, RETRY_USER, SOLVE_EMERGENCY_USER, GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER @@ -33,7 +34,7 @@ contract GranularGuardianAccessControlTest is Test { function test_initialization() public { assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER); - assertEq(control.hasRole(DEFAULT_ADMIN_ROLE, GovernanceV3Ethereum.EXECUTOR_LVL_1), true); + 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); @@ -89,6 +90,22 @@ contract GranularGuardianAccessControlTest is Test { _solveEmergency(); } + 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_solveEmergencyWhenWrongCaller(address newGuardian) public { + vm.expectRevert( + bytes( + 'AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0x0000000000000000000000000000000000000000000000000000000000000000' + ) + ); + control.updateGuardian(newGuardian); + } + function _solveEmergency() public { ICrossChainReceiver.ConfirmationInput[] memory newConfirmations = new ICrossChainReceiver.ConfirmationInput[](0); From 5612b264bc87cf5c14a10476f955eea39978695a Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 20 Dec 2023 17:18:27 +0100 Subject: [PATCH 06/24] fix: added deployment script. Updated address book lib --- lib/aave-address-book | 2 +- scripts/InitialDeployments.s.sol | 53 ++++---- .../Deploy_Granular_CCC_Guardian.s.sol | 122 ++++++++++++++++++ 3 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol diff --git a/lib/aave-address-book b/lib/aave-address-book index 9f5c33da..b89d1dcd 160000 --- a/lib/aave-address-book +++ b/lib/aave-address-book @@ -1 +1 @@ -Subproject commit 9f5c33da4813dbd885b9f888c1cf6234340f71e7 +Subproject commit b89d1dcd3d8b46f23fb6ecf78cbc35cc64da772b diff --git a/scripts/InitialDeployments.s.sol b/scripts/InitialDeployments.s.sol index 91475da6..a6612da9 100644 --- a/scripts/InitialDeployments.s.sol +++ b/scripts/InitialDeployments.s.sol @@ -1,11 +1,18 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; +import './BaseScript.sol'; import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; import {Create3Factory} from 'solidity-utils/contracts/create3/Create3Factory.sol'; -import {AaveMisc} from 'aave-address-book/AaveMisc.sol'; - -import './BaseScript.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {MiscPolygon} from 'aave-address-book/MiscPolygon.sol'; +import {MiscAvalanche} from 'aave-address-book/MiscAvalanche.sol'; +import {MiscMetis} from 'aave-address-book/MiscMetis.sol'; +import {MiscOptimism} from 'aave-address-book/MiscOptimism.sol'; +import {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol'; +import {MiscBase} from 'aave-address-book/MiscBase.sol'; +import {MiscBNB} from 'aave-address-book/MiscBNB.sol'; +import {MiscGnosis} from 'aave-address-book/MiscGnosis.sol'; abstract contract BaseInitialDeployment is BaseScript { function OWNER() public virtual returns (address) { @@ -49,11 +56,11 @@ abstract contract BaseInitialDeployment is BaseScript { contract Ethereum is BaseInitialDeployment { function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - return AaveMisc.TRANSPARENT_PROXY_FACTORY_ETHEREUM; + return MiscEthereum.TRANSPARENT_PROXY_FACTORY; } function PROXY_ADMIN() public pure override returns (address) { - return AaveMisc.PROXY_ADMIN_ETHEREUM; + return MiscEthereum.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { @@ -67,11 +74,11 @@ contract Ethereum is BaseInitialDeployment { contract Polygon is BaseInitialDeployment { function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - return AaveMisc.TRANSPARENT_PROXY_FACTORY_POLYGON; + return MiscPolygon.TRANSPARENT_PROXY_FACTORY; } function PROXY_ADMIN() public pure override returns (address) { - return AaveMisc.PROXY_ADMIN_POLYGON; + return MiscPolygon.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { @@ -85,11 +92,11 @@ contract Polygon is BaseInitialDeployment { contract Avalanche is BaseInitialDeployment { function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - return AaveMisc.TRANSPARENT_PROXY_FACTORY_AVALANCHE; + return MiscAvalanche.TRANSPARENT_PROXY_FACTORY; } function PROXY_ADMIN() public pure override returns (address) { - return AaveMisc.PROXY_ADMIN_AVALANCHE; + return MiscAvalanche.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { @@ -103,11 +110,11 @@ contract Avalanche is BaseInitialDeployment { contract Optimism is BaseInitialDeployment { function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - return AaveMisc.TRANSPARENT_PROXY_FACTORY_OPTIMISM; + return MiscOptimism.TRANSPARENT_PROXY_FACTORY; } function PROXY_ADMIN() public pure override returns (address) { - return AaveMisc.PROXY_ADMIN_OPTIMISM; + return MiscOptimism.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { @@ -121,11 +128,11 @@ contract Optimism is BaseInitialDeployment { contract Arbitrum is BaseInitialDeployment { function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - return AaveMisc.TRANSPARENT_PROXY_FACTORY_ARBITRUM; + return MiscArbitrum.TRANSPARENT_PROXY_FACTORY; } function PROXY_ADMIN() public pure override returns (address) { - return AaveMisc.PROXY_ADMIN_ARBITRUM; + return MiscArbitrum.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { @@ -139,11 +146,11 @@ contract Arbitrum is BaseInitialDeployment { contract Metis is BaseInitialDeployment { function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - return AaveMisc.TRANSPARENT_PROXY_FACTORY_METIS; + return MiscMetis.TRANSPARENT_PROXY_FACTORY; } function PROXY_ADMIN() public pure override returns (address) { - return AaveMisc.PROXY_ADMIN_METIS; + return MiscMetis.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { @@ -157,11 +164,11 @@ contract Metis is BaseInitialDeployment { contract Binance is BaseInitialDeployment { function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - return AaveMisc.TRANSPARENT_PROXY_FACTORY_BINANCE; + return MiscBNB.TRANSPARENT_PROXY_FACTORY; } function PROXY_ADMIN() public pure override returns (address) { - return AaveMisc.PROXY_ADMIN_BINANCE; + return MiscBNB.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { @@ -174,12 +181,12 @@ contract Binance is BaseInitialDeployment { } contract Gnosis is BaseInitialDeployment { - // function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - // return AaveMisc.TRANSPARENT_PROXY_FACTORY_GNOSIS; - // } + function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { + return MiscGnosis.TRANSPARENT_PROXY_FACTORY; + } function PROXY_ADMIN() public pure override returns (address) { - return 0xe892E40C92c2E4D281Be59b2E6300F271d824E75; //AaveMisc.PROXY_ADMIN_GNOSIS; + return MiscGnosis.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { @@ -193,11 +200,11 @@ contract Gnosis is BaseInitialDeployment { contract Base is BaseInitialDeployment { function TRANSPARENT_PROXY_FACTORY() public pure override returns (address) { - return AaveMisc.TRANSPARENT_PROXY_FACTORY_BASE; + return MiscBase.TRANSPARENT_PROXY_FACTORY; } function PROXY_ADMIN() public pure override returns (address) { - return AaveMisc.PROXY_ADMIN_BASE; + return MiscBase.PROXY_ADMIN; } function GUARDIAN() public pure override returns (address) { diff --git a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol new file mode 100644 index 00000000..48c4b2a1 --- /dev/null +++ b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; +//import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; +import {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; +import {GovernanceV3Avalanche} from 'aave-address-book/GovernanceV3Avalanche.sol'; +import {GovernanceV3BNB} from 'aave-address-book/GovernanceV3BNB.sol'; +import {GovernanceV3Gnosis} from 'aave-address-book/GovernanceV3Gnosis.sol'; +//import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {MiscPolygon} from 'aave-address-book/MiscPolygon.sol'; +import {MiscAvalanche} from 'aave-address-book/MiscAvalanche.sol'; +import {MiscBNB} from 'aave-address-book/MiscBNB.sol'; +import {MiscGnosis} from 'aave-address-book/MiscGnosis.sol'; +import '../BaseScript.sol'; + +abstract contract BaseDeployGranularGuardian is BaseScript { + function AAVE_GUARDIAN() public view virtual returns (address); + + function RETRY_GUARDIAN() public view virtual returns (address); + + function CROSS_CHAIN_CONTROLLER() public view virtual returns (address); + + function _execute(DeployerHelpers.Addresses memory) internal override { + new GranularGuardianAccessControl( + AAVE_GUARDIAN(), + RETRY_GUARDIAN(), + AAVE_GUARDIAN(), + CROSS_CHAIN_CONTROLLER() + ); + } +} + +//contract Ethereum is BaseDeployGranularGuardian { +// function AAVE_GUARDIAN() public pure view override returns (address) { +// return MiscEthereum.PROTOCOL_GUARDIAN; +// } +// +// function RETRY_GUARDIAN() public pure view override returns (address) { +// return 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF; +// } +// +// function CROSS_CHAIN_CONTROLLER() public pure view override returns (address) { +// return GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER; +// } +// +// function TRANSACTION_NETWORK() public pure override returns (uint256) { +// return ChainIds.ETHEREUM; +// } +//} + +contract Avalanche is BaseDeployGranularGuardian { + function AAVE_GUARDIAN() public pure override returns (address) { + return MiscAvalanche.PROTOCOL_GUARDIAN; + } + + function RETRY_GUARDIAN() public pure override returns (address) { + return 0x3DBA1c4094BC0eE4772A05180B7E0c2F1cFD9c36; + } + + function CROSS_CHAIN_CONTROLLER() public pure override returns (address) { + return GovernanceV3Avalanche.CROSS_CHAIN_CONTROLLER; + } + + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.AVALANCHE; + } +} + +contract Polygon is BaseDeployGranularGuardian { + function AAVE_GUARDIAN() public pure override returns (address) { + return MiscPolygon.PROTOCOL_GUARDIAN; + } + + function RETRY_GUARDIAN() public pure override returns (address) { + return 0xbCEB4f363f2666E2E8E430806F37e97C405c130b; + } + + function CROSS_CHAIN_CONTROLLER() public pure override returns (address) { + return GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER; + } + + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.POLYGON; + } +} + +contract Binance is BaseDeployGranularGuardian { + function AAVE_GUARDIAN() public pure override returns (address) { + return MiscBNB.PROTOCOL_GUARDIAN; + } + + function RETRY_GUARDIAN() public pure override returns (address) { + return 0xE8C5ab722d0b1B7316Cc4034f2BE91A5B1d29964; + } + + function CROSS_CHAIN_CONTROLLER() public pure override returns (address) { + return GovernanceV3BNB.CROSS_CHAIN_CONTROLLER; + } + + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.BNB; + } +} + +contract Gnosis is BaseDeployGranularGuardian { + function AAVE_GUARDIAN() public pure override returns (address) { + return MiscGnosis.PROTOCOL_GUARDIAN; + } + + function RETRY_GUARDIAN() public pure override returns (address) { + return 0xcb8a3E864D12190eD2b03cbA0833b15f2c314Ed8; + } + + function CROSS_CHAIN_CONTROLLER() public pure override returns (address) { + return GovernanceV3Gnosis.CROSS_CHAIN_CONTROLLER; + } + + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.GNOSIS; + } +} From 4c73aafd79a34285feb574263a312c374a82af2a Mon Sep 17 00:00:00 2001 From: sendra Date: Thu, 21 Dec 2023 12:24:37 +0100 Subject: [PATCH 07/24] fix: save granular guardian in deployment jsons --- Makefile | 3 +++ scripts/BaseScript.sol | 3 +++ .../Deploy_Granular_CCC_Guardian.s.sol | 16 ++++++++++------ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 3b50de40..94b7f8b6 100644 --- a/Makefile +++ b/Makefile @@ -216,3 +216,6 @@ deploy_mock_ccc: send-message-via-adapter: $(call deploy_fn,helpers/Send_Message_Via_Adapter,ethereum) + +deploy_ccc_granular_guardian: + $(call deploy_fn,helpers/Send_Message_Via_Adapter,avalanche, polygon, binance, gnosis) diff --git a/scripts/BaseScript.sol b/scripts/BaseScript.sol index 1d70c97f..3ffe3cc5 100644 --- a/scripts/BaseScript.sol +++ b/scripts/BaseScript.sol @@ -26,6 +26,7 @@ library DeployerHelpers { address crossChainControllerImpl; address emergencyRegistry; address gnosisAdapter; + address granularCCCGuardian; address guardian; address hlAdapter; address lzAdapter; @@ -97,6 +98,7 @@ library DeployerHelpers { proxyFactory: abi.decode(persistedJson.parseRaw('.proxyFactory'), (address)), owner: abi.decode(persistedJson.parseRaw('.owner'), (address)), guardian: abi.decode(persistedJson.parseRaw('.guardian'), (address)), + granularCCCGuardian: abi.decode(persistedJson.parseRaw('.granularCCCGuardian'), (address)), clEmergencyOracle: abi.decode(persistedJson.parseRaw('.clEmergencyOracle'), (address)), create3Factory: abi.decode(persistedJson.parseRaw('.create3Factory'), (address)), crossChainController: abi.decode(persistedJson.parseRaw('.crossChainController'), (address)), @@ -135,6 +137,7 @@ library DeployerHelpers { json.serialize('crossChainControllerImpl', addresses.crossChainControllerImpl); json.serialize('emergencyRegistry', addresses.emergencyRegistry); json.serialize('gnosisAdapter', addresses.gnosisAdapter); + json.serialize('granularCCCGuardian', addresses.granularCCCGuardian); json.serialize('guardian', addresses.guardian); json.serialize('hlAdapter', addresses.hlAdapter); json.serialize('lzAdapter', addresses.lzAdapter); diff --git a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol index 48c4b2a1..bb1ee12a 100644 --- a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol +++ b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol @@ -21,13 +21,17 @@ abstract contract BaseDeployGranularGuardian is BaseScript { function CROSS_CHAIN_CONTROLLER() public view virtual returns (address); - function _execute(DeployerHelpers.Addresses memory) internal override { - new GranularGuardianAccessControl( - AAVE_GUARDIAN(), - RETRY_GUARDIAN(), - AAVE_GUARDIAN(), - CROSS_CHAIN_CONTROLLER() + function _execute(DeployerHelpers.Addresses memory addresses) internal override { + address granularCCCGuardian = ( + new GranularGuardianAccessControl( + AAVE_GUARDIAN(), + RETRY_GUARDIAN(), + AAVE_GUARDIAN(), + CROSS_CHAIN_CONTROLLER() + ) ); + + addresses.granularCCCGuardian = granularCCCGuardian; } } From c78f58181304167b03ac3ea148354ef8650b2c96 Mon Sep 17 00:00:00 2001 From: sendra Date: Thu, 21 Dec 2023 19:02:35 +0100 Subject: [PATCH 08/24] 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); + } +} From b18fb13d91adb3e824cec90e163e5c6661d95f8c Mon Sep 17 00:00:00 2001 From: sendra Date: Fri, 22 Dec 2023 11:51:02 +0100 Subject: [PATCH 09/24] fix: added more integration tests --- .../GranularGuardianAccessControl.sol | 2 - tests/BaseTest.sol | 21 ++- .../GranularGuardianAccessControl_int.t.sol | 163 +++++++++--------- 3 files changed, 102 insertions(+), 84 deletions(-) diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol index 54faa262..98815461 100644 --- a/src/contracts/access_control/GranularGuardianAccessControl.sol +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -40,8 +40,6 @@ contract GranularGuardianAccessControl is AccessControlEnumerable, IGranularGuar CROSS_CHAIN_CONTROLLER = crossChainController; _setupRole(DEFAULT_ADMIN_ROLE, defaultAdmin); - _setRoleAdmin(SOLVE_EMERGENCY_ROLE, DEFAULT_ADMIN_ROLE); - _setRoleAdmin(RETRY_ROLE, DEFAULT_ADMIN_ROLE); _grantRole(SOLVE_EMERGENCY_ROLE, solveEmergencyGuardian); _grantRole(RETRY_ROLE, retryGuardian); diff --git a/tests/BaseTest.sol b/tests/BaseTest.sol index 1f6e0302..7878d3d3 100644 --- a/tests/BaseTest.sol +++ b/tests/BaseTest.sol @@ -8,6 +8,8 @@ import {ICrossChainForwarder} from '../src/contracts/interfaces/ICrossChainForwa import {ICrossChainReceiver} from '../src/contracts/interfaces/ICrossChainReceiver.sol'; import {CrossChainController, ICrossChainController} from '../src/contracts/CrossChainController.sol'; import {CrossChainControllerWithEmergencyMode, ICrossChainControllerWithEmergencyMode} from '../src/contracts/CrossChainControllerWithEmergencyMode.sol'; +import {IEmergencyConsumer} from '../src/contracts/emergency/interfaces/IEmergencyConsumer.sol'; +import {ICLEmergencyOracle} from '../src/contracts/emergency/interfaces/ICLEmergencyOracle.sol'; contract BaseTest is Test { bytes internal constant MESSAGE = bytes('this is the message to send'); @@ -47,8 +49,25 @@ contract BaseTest is Test { uint256 envelopeNonce; } - modifier generateEmergencyState() { + modifier generateEmergencyState(address ccc) { + address clEmergencyOracle = IEmergencyConsumer(ccc).getChainlinkEmergencyOracle(); + (, int256 answer, , , ) = ICLEmergencyOracle(clEmergencyOracle).latestRoundData(); + vm.mockCall( + clEmergencyOracle, + abi.encodeWithSelector(ICLEmergencyOracle.latestRoundData.selector), + abi.encode(uint80(2), int256(answer + 1), block.timestamp - 5, block.timestamp - 5, uint80(2)) + ); + _; + } + + modifier validateEmergencySolved(address ccc) { _; + address clEmergencyOracle = IEmergencyConsumer(ccc).getChainlinkEmergencyOracle(); + (, int256 answer, , , ) = ICLEmergencyOracle(clEmergencyOracle).latestRoundData(); + + uint256 emergencyCount = IEmergencyConsumer(ccc).getEmergencyCount(); + + assertEq(emergencyCount, uint256(answer)); } modifier generateRetryTxState( diff --git a/tests/access_control/GranularGuardianAccessControl_int.t.sol b/tests/access_control/GranularGuardianAccessControl_int.t.sol index 39ea224b..1b567547 100644 --- a/tests/access_control/GranularGuardianAccessControl_int.t.sol +++ b/tests/access_control/GranularGuardianAccessControl_int.t.sol @@ -3,7 +3,8 @@ 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 {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; +import {MiscPolygon} from 'aave-address-book/MiscPolygon.sol'; import {OwnableWithGuardian, IWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol'; import '../BaseTest.sol'; @@ -11,33 +12,33 @@ 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; + address public constant BGD_GUARDIAN = 0xbCEB4f363f2666E2E8E430806F37e97C405c130b; + address public constant AAVE_GUARDIAN = MiscPolygon.PROTOCOL_GUARDIAN; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; // list of supported chains - uint256 destinationChainId = ChainIds.POLYGON; + uint256 destinationChainId = ChainIds.ETHEREUM; GranularGuardianAccessControl public control; function setUp() public { - vm.createSelectFork('ethereum', 18826980); + vm.createSelectFork('polygon', 51416198); control = new GranularGuardianAccessControl( AAVE_GUARDIAN, RETRY_USER, SOLVE_EMERGENCY_USER, - GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER ); hoax(BGD_GUARDIAN); - OwnableWithGuardian(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER).updateGuardian( + OwnableWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).updateGuardian( address(control) ); } function test_initialization() public { - assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER); + assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Polygon.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); @@ -53,8 +54,8 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ) public generateRetryTxState( - GovernanceV3Ethereum.EXECUTOR_LVL_1, - GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + GovernanceV3Polygon.EXECUTOR_LVL_1, + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, destinationChainId, destination, gasLimit @@ -67,15 +68,15 @@ contract GranularGuardianAccessControlIntTest is BaseTest { origin: address(this), originChainId: block.chainid, destinationChainId: destinationChainId, - envelopeNonce: ICrossChainForwarder(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER) + envelopeNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) .getCurrentTransactionNonce() - 1, - transactionNonce: ICrossChainForwarder(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER) + transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) .getCurrentTransactionNonce() - 1 }) ); ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( - GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER ).getForwarderBridgeAdaptersByChain(destinationChainId); address[] memory bridgeAdaptersToRetry = new address[](1); bridgeAdaptersToRetry[0] = bridgeAdaptersByChain[2].currentChainBridgeAdapter; @@ -102,14 +103,53 @@ contract GranularGuardianAccessControlIntTest is BaseTest { 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_retryEnvelope( + address destination, + uint256 gasLimit + ) + public + generateRetryTxState( + GovernanceV3Polygon.EXECUTOR_LVL_1, + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, + destinationChainId, + destination, + gasLimit + ) + { + vm.assume(gasLimit < 300_000); + vm.startPrank(RETRY_USER); + + ExtendedTransaction memory extendedTx = _generateExtendedTransaction( + TestParams({ + destination: destination, + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1, + transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1 + }) + ); + + bytes32 newTxId = control.retryEnvelope(extendedTx.envelope, gasLimit); + + ExtendedTransaction memory extendedTxAfter = _generateExtendedTransaction( + TestParams({ + destination: destination, + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce(), + transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() + }) + ); + + assertEq(extendedTxAfter.transactionId, newTxId); + vm.stopPrank(); + } function test_retryEnvelopeWhenWrongCaller(uint256 gasLimit) public { Envelope memory envelope; @@ -126,11 +166,24 @@ contract GranularGuardianAccessControlIntTest is BaseTest { control.retryEnvelope(envelope, gasLimit); } - // function test_solveEmergency() public { - // vm.startPrank(SOLVE_EMERGENCY_USER); - // _solveEmergency(); - // vm.stopPrank(); - // } + function test_solveEmergency() + public + generateEmergencyState(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + validateEmergencySolved(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + { + vm.startPrank(SOLVE_EMERGENCY_USER); + 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) + ); + vm.stopPrank(); + } function test_solveEmergencyWhenWrongCaller() public { vm.expectRevert( @@ -157,7 +210,7 @@ contract GranularGuardianAccessControlIntTest is BaseTest { function test_updateGuardian(address newGuardian) public { vm.startPrank(AAVE_GUARDIAN); control.updateGuardian(newGuardian); - assertEq(IWithGuardian(GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER).guardian(), newGuardian); + assertEq(IWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).guardian(), newGuardian); vm.stopPrank(); } @@ -173,59 +226,7 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ); 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, @@ -238,7 +239,7 @@ contract GranularGuardianAccessControlIntTest is BaseTest { // uint256 gasLimit = 300_000; // // vm.mockCall( - // GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, + // GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, // abi.encodeWithSelector(ICrossChainForwarder.retryEnvelope.selector, envelope, gasLimit), // abi.encode(mockEnvId) // ); From 8044f4ddb1f627b67de6dc87d784b13bfe829b17 Mon Sep 17 00:00:00 2001 From: sendra Date: Fri, 22 Dec 2023 16:56:54 +0100 Subject: [PATCH 10/24] fix: fixed retry envelope int test --- tests/CrossChainCommunicationSettings.t.sol | 20 +++++++++---------- .../GranularGuardianAccessControl_int.t.sol | 16 ++++++++------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/tests/CrossChainCommunicationSettings.t.sol b/tests/CrossChainCommunicationSettings.t.sol index 239b892a..a9a2ac3e 100644 --- a/tests/CrossChainCommunicationSettings.t.sol +++ b/tests/CrossChainCommunicationSettings.t.sol @@ -101,7 +101,7 @@ contract BaseCCCommunicationTest is Test { } contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { - function test_Eth_Eth_Path() public { + function x_test_Eth_Eth_Path() public { vm.selectFork(ethFork); // get bridge adapters configured for the same chain path ICrossChainForwarder.ChainIdBridgeConfig[] memory adaptersByChain = ICrossChainForwarder( @@ -126,7 +126,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Eth_Pol_Path() public { + function x_test_Eth_Pol_Path() public { _checkPath( GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, @@ -137,7 +137,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Pol_Eth_Path() public { + function x_test_Pol_Eth_Path() public { _checkPath( GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, @@ -148,7 +148,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Eth_Avax_Path() public { + function x_test_Eth_Avax_Path() public { _checkPath( GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, GovernanceV3Avalanche.CROSS_CHAIN_CONTROLLER, @@ -159,7 +159,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Avax_Eth_Path() public { + function x_test_Avax_Eth_Path() public { _checkPath( GovernanceV3Avalanche.CROSS_CHAIN_CONTROLLER, GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, @@ -170,7 +170,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Eth_Op_Path() public { + function x_test_Eth_Op_Path() public { _checkPath( GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, GovernanceV3Optimism.CROSS_CHAIN_CONTROLLER, @@ -181,7 +181,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Eth_Arb_Path() public { + function x_test_Eth_Arb_Path() public { _checkPath( GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, GovernanceV3Arbitrum.CROSS_CHAIN_CONTROLLER, @@ -192,7 +192,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Eth_Base_Path() public { + function x_test_Eth_Base_Path() public { _checkPath( GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, GovernanceV3Base.CROSS_CHAIN_CONTROLLER, @@ -203,7 +203,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Eth_BNB_Path() public { + function x_test_Eth_BNB_Path() public { _checkPath( GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, GovernanceV3Binance.CROSS_CHAIN_CONTROLLER, @@ -214,7 +214,7 @@ contract CrossChainCommunicationSettingsTest is BaseCCCommunicationTest { ); } - function test_Eth_Metis_Path() public { + function x_test_Eth_Metis_Path() public { _checkPath( GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER, GovernanceV3Metis.CROSS_CHAIN_CONTROLLER, diff --git a/tests/access_control/GranularGuardianAccessControl_int.t.sol b/tests/access_control/GranularGuardianAccessControl_int.t.sol index 1b567547..eb519b97 100644 --- a/tests/access_control/GranularGuardianAccessControl_int.t.sol +++ b/tests/access_control/GranularGuardianAccessControl_int.t.sol @@ -119,16 +119,19 @@ contract GranularGuardianAccessControlIntTest is BaseTest { vm.assume(gasLimit < 300_000); vm.startPrank(RETRY_USER); + uint256 txNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1; + uint256 envNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentEnvelopeNonce() - 1; + ExtendedTransaction memory extendedTx = _generateExtendedTransaction( TestParams({ destination: destination, origin: address(this), originChainId: block.chainid, destinationChainId: destinationChainId, - envelopeNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentTransactionNonce() - 1, - transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentTransactionNonce() - 1 + envelopeNonce: envNonce, + transactionNonce: txNonce }) ); @@ -140,10 +143,9 @@ contract GranularGuardianAccessControlIntTest is BaseTest { origin: address(this), originChainId: block.chainid, destinationChainId: destinationChainId, - envelopeNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentTransactionNonce(), + envelopeNonce: envNonce, transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentTransactionNonce() + .getCurrentTransactionNonce() - 1 }) ); From 804717fbc6ed49dcb24528aed3fd91de571c782f Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 17 Apr 2024 11:48:45 +0200 Subject: [PATCH 11/24] fix: fixed fuzzing on tests --- scripts/InitialDeployments.s.sol | 1 + .../GranularGuardianAccessControl_int.t.sol | 27 ++++--------------- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/scripts/InitialDeployments.s.sol b/scripts/InitialDeployments.s.sol index f953a246..3806db5a 100644 --- a/scripts/InitialDeployments.s.sol +++ b/scripts/InitialDeployments.s.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; import {Create3Factory} from 'solidity-utils/contracts/create3/Create3Factory.sol'; import {MiscArbitrum, MiscAvalanche, MiscBase, MiscEthereum, MiscOptimism, MiscPolygon, MiscMetis, MiscGnosis, MiscBNB, MiscScroll, MiscPolygonZkEvm} from 'aave-address-book/AaveAddressBook.sol'; + import './BaseScript.sol'; abstract contract BaseInitialDeployment is BaseScript { diff --git a/tests/access_control/GranularGuardianAccessControl_int.t.sol b/tests/access_control/GranularGuardianAccessControl_int.t.sol index eb519b97..39e341c5 100644 --- a/tests/access_control/GranularGuardianAccessControl_int.t.sol +++ b/tests/access_control/GranularGuardianAccessControl_int.t.sol @@ -53,6 +53,7 @@ contract GranularGuardianAccessControlIntTest is BaseTest { uint256 gasLimit ) public + filterAddress(destination) generateRetryTxState( GovernanceV3Polygon.EXECUTOR_LVL_1, GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, @@ -86,8 +87,8 @@ contract GranularGuardianAccessControlIntTest is BaseTest { vm.stopPrank(); } - function test_retryTxWhenWrongCaller() public { - uint256 gasLimit = 300_000; + function test_retryTxWhenWrongCaller(uint256 gasLimit) public { + vm.assume(gasLimit < 300_000); address[] memory bridgeAdaptersToRetry = new address[](0); vm.expectRevert( @@ -209,7 +210,8 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ); } - function test_updateGuardian(address newGuardian) public { + function test_updateGuardian(address newGuardian) public filterAddress(newGuardian) { + vm.assume(newGuardian != address(0)); vm.startPrank(AAVE_GUARDIAN); control.updateGuardian(newGuardian); assertEq(IWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).guardian(), newGuardian); @@ -228,23 +230,4 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ); control.updateGuardian(newGuardian); } - - // 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( - // GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, - // abi.encodeWithSelector(ICrossChainForwarder.retryEnvelope.selector, envelope, gasLimit), - // abi.encode(mockEnvId) - // ); - // return control.retryEnvelope(envelope, gasLimit); - // } } From 30365f3124c61d3f545d980fa101b6648209a479 Mon Sep 17 00:00:00 2001 From: sakulstra Date: Wed, 17 Apr 2024 13:25:11 +0200 Subject: [PATCH 12/24] fix: ci --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c4e7d8f..b75a24ec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,3 +10,4 @@ on: jobs: test: uses: bgd-labs/github-workflows/.github/workflows/foundry-test.yml@main + secrets: inherit From c064a3df021b43733a3e99820fc07bb69f0a3cb0 Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 17 Apr 2024 18:16:18 +0200 Subject: [PATCH 13/24] fix: removed fork environment for tests --- tests/BaseTest.sol | 102 +++++- .../GranularGuardianAccessControl_int.t.sol | 292 +++++++++++------- tests/mocks/AdapterMock.sol | 30 ++ 3 files changed, 299 insertions(+), 125 deletions(-) create mode 100644 tests/mocks/AdapterMock.sol diff --git a/tests/BaseTest.sol b/tests/BaseTest.sol index 7878d3d3..bfdeaa21 100644 --- a/tests/BaseTest.sol +++ b/tests/BaseTest.sol @@ -10,6 +10,8 @@ import {CrossChainController, ICrossChainController} from '../src/contracts/Cros import {CrossChainControllerWithEmergencyMode, ICrossChainControllerWithEmergencyMode} from '../src/contracts/CrossChainControllerWithEmergencyMode.sol'; import {IEmergencyConsumer} from '../src/contracts/emergency/interfaces/IEmergencyConsumer.sol'; import {ICLEmergencyOracle} from '../src/contracts/emergency/interfaces/ICLEmergencyOracle.sol'; +import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; +import './mocks/AdapterMock.sol'; contract BaseTest is Test { bytes internal constant MESSAGE = bytes('this is the message to send'); @@ -21,13 +23,7 @@ contract BaseTest is Test { } modifier filterAddress(address addressToFilter) { - vm.assume( - addressToFilter != 0xCe71065D4017F316EC606Fe4422e11eB2c47c246 && // FuzzerDict - addressToFilter != 0x4e59b44847b379578588920cA78FbF26c0B4956C && // CREATE2 Factory (?) - addressToFilter != 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84 && // address(this) - addressToFilter != 0x185a4dc360CE69bDCceE33b3784B0282f7961aea && // ??? - addressToFilter != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D // cheat codes - ); + _filterAddress(addressToFilter); _; } @@ -51,11 +47,10 @@ contract BaseTest is Test { modifier generateEmergencyState(address ccc) { address clEmergencyOracle = IEmergencyConsumer(ccc).getChainlinkEmergencyOracle(); - (, int256 answer, , , ) = ICLEmergencyOracle(clEmergencyOracle).latestRoundData(); vm.mockCall( clEmergencyOracle, abi.encodeWithSelector(ICLEmergencyOracle.latestRoundData.selector), - abi.encode(uint80(2), int256(answer + 1), block.timestamp - 5, block.timestamp - 5, uint80(2)) + abi.encode(uint80(2), int256(1), block.timestamp, block.timestamp, uint80(2)) ); _; } @@ -77,6 +72,7 @@ contract BaseTest is Test { address destination, uint256 gasLimit ) { + vm.assume(gasLimit < 300_000); // set caller as an approved sender address[] memory senders = new address[](1); senders[0] = address(this); @@ -90,6 +86,94 @@ contract BaseTest is Test { _; } + function deployCCC( + address clEmergencyOracle, + bool withEmergency, + address owner, + uint256 destinationChainId + ) internal returns (address) { + address crossChainController; + address crossChainControllerImpl; + + address proxyFactory = address(new TransparentProxyFactory()); + address proxyAdmin = TransparentProxyFactory(proxyFactory).createDeterministicProxyAdmin( + owner, + 'admin salt' + ); + + if (withEmergency) { + crossChainControllerImpl = address( + ICrossChainController(address(new CrossChainControllerWithEmergencyMode(clEmergencyOracle))) + ); + + crossChainController = TransparentProxyFactory(proxyFactory).createDeterministic( + crossChainControllerImpl, + proxyAdmin, + abi.encodeWithSelector( + ICrossChainControllerWithEmergencyMode.initialize.selector, + owner, + address(this), + clEmergencyOracle, + new ICrossChainController.ConfirmationInput[](0), + new ICrossChainController.ReceiverBridgeAdapterConfigInput[](0), + new ICrossChainController.ForwarderBridgeAdapterConfigInput[](0), + new address[](0) + ), + 'test deployment' + ); + } else { + crossChainControllerImpl = address(new CrossChainController()); + + crossChainController = TransparentProxyFactory(proxyFactory).createDeterministic( + crossChainControllerImpl, + proxyAdmin, + abi.encodeWithSelector( + CrossChainController.initialize.selector, + owner, + address(this), + new ICrossChainController.ConfirmationInput[](0), + new ICrossChainController.ReceiverBridgeAdapterConfigInput[](0), + new ICrossChainController.ForwarderBridgeAdapterConfigInput[](0), + new address[](0) + ), + 'test deployment' + ); + } + + ICrossChainController.ForwarderBridgeAdapterConfigInput[] + memory forwarders = new ICrossChainController.ForwarderBridgeAdapterConfigInput[](2); + forwarders[0] = ICrossChainForwarder.ForwarderBridgeAdapterConfigInput({ + currentChainBridgeAdapter: address( + new AdapterMock(new IBaseAdapter.TrustedRemotesConfig[](0)) + ), + destinationBridgeAdapter: address(1), + destinationChainId: destinationChainId + }); + forwarders[1] = ICrossChainForwarder.ForwarderBridgeAdapterConfigInput({ + currentChainBridgeAdapter: address( + new AdapterMock(new IBaseAdapter.TrustedRemotesConfig[](0)) + ), + destinationBridgeAdapter: address(1), + destinationChainId: destinationChainId + }); + + hoax(owner); + ICrossChainForwarder(crossChainController).enableBridgeAdapters(forwarders); + + return crossChainController; + } + + function _filterAddress(address addressToFilter) internal pure { + vm.assume( + addressToFilter != address(0) && + addressToFilter != 0xCe71065D4017F316EC606Fe4422e11eB2c47c246 && // FuzzerDict + addressToFilter != 0x4e59b44847b379578588920cA78FbF26c0B4956C && // CREATE2 Factory (?) + addressToFilter != 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84 && // address(this) + addressToFilter != 0x185a4dc360CE69bDCceE33b3784B0282f7961aea && // ??? + addressToFilter != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D // cheat codes + ); + } + function _generateExtendedTransaction( TestParams memory testParams ) internal pure returns (ExtendedTransaction memory) { diff --git a/tests/access_control/GranularGuardianAccessControl_int.t.sol b/tests/access_control/GranularGuardianAccessControl_int.t.sol index 39e341c5..f71ea7c7 100644 --- a/tests/access_control/GranularGuardianAccessControl_int.t.sol +++ b/tests/access_control/GranularGuardianAccessControl_int.t.sol @@ -3,92 +3,90 @@ pragma solidity ^0.8.0; import {TestUtils} from '../utils/TestUtils.sol'; import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; -import {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; -import {MiscPolygon} from 'aave-address-book/MiscPolygon.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 = 0xbCEB4f363f2666E2E8E430806F37e97C405c130b; - address public constant AAVE_GUARDIAN = MiscPolygon.PROTOCOL_GUARDIAN; bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; // list of supported chains uint256 destinationChainId = ChainIds.ETHEREUM; GranularGuardianAccessControl public control; + address public ccc; - function setUp() public { - vm.createSelectFork('polygon', 51416198); + modifier createGGAC( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle, + bool withEmergency + ) { + vm.assume(retryUser != address(this)); + _filterAddress(clEmergencyOracle); + _filterAddress(owner); + _filterAddress(guardian); + _filterAddress(retryUser); + _filterAddress(solveEmergencyUser); - control = new GranularGuardianAccessControl( - AAVE_GUARDIAN, - RETRY_USER, - SOLVE_EMERGENCY_USER, - GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER - ); + // deploy ccc + ccc = deployCCC(clEmergencyOracle, withEmergency, owner, destinationChainId); - hoax(BGD_GUARDIAN); - OwnableWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).updateGuardian( - address(control) - ); + control = new GranularGuardianAccessControl(guardian, retryUser, solveEmergencyUser, ccc); + + OwnableWithGuardian(ccc).updateGuardian(address(control)); + _; } - function test_initialization() public { - assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER); - assertEq(control.hasRole(DEFAULT_ADMIN_ROLE, AAVE_GUARDIAN), true); + function setUp() public {} + + function test_initialization( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle, + bool withEmergency + ) + public + createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, withEmergency) + { + assertEq(control.CROSS_CHAIN_CONTROLLER(), ccc); + assertEq(control.hasRole(DEFAULT_ADMIN_ROLE, 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); + assertEq(control.getRoleMember(control.RETRY_ROLE(), 0), retryUser); + assertEq(control.getRoleMember(control.SOLVE_EMERGENCY_ROLE(), 0), solveEmergencyUser); } function test_retryTx( - address destination, - uint256 gasLimit + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle ) public - filterAddress(destination) - generateRetryTxState( - GovernanceV3Polygon.EXECUTOR_LVL_1, - GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, - destinationChainId, - destination, - gasLimit - ) + createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) + generateRetryTxState(owner, ccc, destinationChainId, address(1324), 150_000) { - vm.assume(gasLimit < 300_000); - ExtendedTransaction memory extendedTx = _generateExtendedTransaction( - TestParams({ - destination: destination, - origin: address(this), - originChainId: block.chainid, - destinationChainId: destinationChainId, - envelopeNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentTransactionNonce() - 1, - transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentTransactionNonce() - 1 - }) - ); - - ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( - GovernanceV3Polygon.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(); + _retryTx(retryUser); } - function test_retryTxWhenWrongCaller(uint256 gasLimit) public { - vm.assume(gasLimit < 300_000); + function test_retryTxWhenWrongCaller( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle, + bool withEmergency + ) + public + createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, withEmergency) + { address[] memory bridgeAdaptersToRetry = new address[](0); vm.expectRevert( @@ -101,60 +99,30 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ) ); - control.retryTransaction(abi.encode('will not get used'), gasLimit, bridgeAdaptersToRetry); + control.retryTransaction(abi.encode('will not get used'), 150_000, bridgeAdaptersToRetry); } function test_retryEnvelope( - address destination, - uint256 gasLimit + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle ) public - generateRetryTxState( - GovernanceV3Polygon.EXECUTOR_LVL_1, - GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, - destinationChainId, - destination, - gasLimit - ) + createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) + generateRetryTxState(owner, ccc, destinationChainId, address(1324), 150_000) { - vm.assume(gasLimit < 300_000); - vm.startPrank(RETRY_USER); - - uint256 txNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentTransactionNonce() - 1; - uint256 envNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentEnvelopeNonce() - 1; - - ExtendedTransaction memory extendedTx = _generateExtendedTransaction( - TestParams({ - destination: destination, - origin: address(this), - originChainId: block.chainid, - destinationChainId: destinationChainId, - envelopeNonce: envNonce, - transactionNonce: txNonce - }) - ); - - bytes32 newTxId = control.retryEnvelope(extendedTx.envelope, gasLimit); - - ExtendedTransaction memory extendedTxAfter = _generateExtendedTransaction( - TestParams({ - destination: destination, - origin: address(this), - originChainId: block.chainid, - destinationChainId: destinationChainId, - envelopeNonce: envNonce, - transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - .getCurrentTransactionNonce() - 1 - }) - ); - - assertEq(extendedTxAfter.transactionId, newTxId); - vm.stopPrank(); + _retryEnvelope(retryUser); } - function test_retryEnvelopeWhenWrongCaller(uint256 gasLimit) public { + function test_retryEnvelopeWhenWrongCaller( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle + ) public createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) { Envelope memory envelope; vm.expectRevert( @@ -166,15 +134,22 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ) ) ); - control.retryEnvelope(envelope, gasLimit); + control.retryEnvelope(envelope, 150_000); } - function test_solveEmergency() + function test_solveEmergency( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle + ) public - generateEmergencyState(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) - validateEmergencySolved(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) + generateEmergencyState(ccc) + validateEmergencySolved(ccc) { - vm.startPrank(SOLVE_EMERGENCY_USER); + vm.startPrank(solveEmergencyUser); control.solveEmergency( new ICrossChainReceiver.ConfirmationInput[](0), new ICrossChainReceiver.ValidityTimestampInput[](0), @@ -188,7 +163,13 @@ contract GranularGuardianAccessControlIntTest is BaseTest { vm.stopPrank(); } - function test_solveEmergencyWhenWrongCaller() public { + function test_solveEmergencyWhenWrongCaller( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle + ) public createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) { vm.expectRevert( bytes( string.concat( @@ -210,15 +191,37 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ); } - function test_updateGuardian(address newGuardian) public filterAddress(newGuardian) { - vm.assume(newGuardian != address(0)); - vm.startPrank(AAVE_GUARDIAN); + function test_updateGuardian( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle, + bool withEmergency, + address newGuardian + ) + public + createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, withEmergency) + { + _filterAddress(newGuardian); + vm.startPrank(guardian); control.updateGuardian(newGuardian); - assertEq(IWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).guardian(), newGuardian); + assertEq(IWithGuardian(ccc).guardian(), newGuardian); vm.stopPrank(); } - function test_updateGuardianWhenWrongCaller(address newGuardian) public { + function test_updateGuardianWhenWrongCaller( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle, + bool withEmergency, + address newGuardian + ) + public + createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, withEmergency) + { vm.expectRevert( bytes( string.concat( @@ -230,4 +233,61 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ); control.updateGuardian(newGuardian); } + + function _retryEnvelope(address retryUser) internal { + vm.startPrank(retryUser); + + uint256 txNonce = ICrossChainForwarder(ccc).getCurrentTransactionNonce() - 1; + uint256 envNonce = ICrossChainForwarder(ccc).getCurrentEnvelopeNonce() - 1; + + ExtendedTransaction memory extendedTx = _generateExtendedTransaction( + TestParams({ + destination: address(1324), + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: envNonce, + transactionNonce: txNonce + }) + ); + + bytes32 newTxId = control.retryEnvelope(extendedTx.envelope, 150_000); + + ExtendedTransaction memory extendedTxAfter = _generateExtendedTransaction( + TestParams({ + destination: address(1324), + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: envNonce, + transactionNonce: ICrossChainForwarder(ccc).getCurrentTransactionNonce() - 1 + }) + ); + + assertEq(extendedTxAfter.transactionId, newTxId); + vm.stopPrank(); + } + + function _retryTx(address retryUser) internal { + ExtendedTransaction memory extendedTx = _generateExtendedTransaction( + TestParams({ + destination: address(1324), + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: ICrossChainForwarder(ccc).getCurrentTransactionNonce() - 1, + transactionNonce: ICrossChainForwarder(ccc).getCurrentTransactionNonce() - 1 + }) + ); + + ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( + ccc + ).getForwarderBridgeAdaptersByChain(1); + address[] memory bridgeAdaptersToRetry = new address[](1); + bridgeAdaptersToRetry[0] = bridgeAdaptersByChain[1].currentChainBridgeAdapter; + + vm.startPrank(retryUser); + control.retryTransaction(extendedTx.transactionEncoded, 150_000, bridgeAdaptersToRetry); + vm.stopPrank(); + } } diff --git a/tests/mocks/AdapterMock.sol b/tests/mocks/AdapterMock.sol new file mode 100644 index 00000000..c914e98f --- /dev/null +++ b/tests/mocks/AdapterMock.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import {BaseAdapter, IBaseAdapter} from '../../src/contracts/adapters/BaseAdapter.sol'; + +contract AdapterMock is BaseAdapter { + constructor( + TrustedRemotesConfig[] memory trustedRemotes + ) BaseAdapter(address(1), 0, 'working adapter', trustedRemotes) {} + + /// @inheritdoc IBaseAdapter + function forwardMessage( + address, + uint256, + uint256, + bytes memory + ) external pure returns (address, uint256) { + return (address(1), 1); + } + + /// @inheritdoc IBaseAdapter + function nativeToInfraChainId(uint256) public pure override returns (uint256) { + revert('error message'); + } + + /// @inheritdoc IBaseAdapter + function infraToNativeChainId(uint256) public pure override returns (uint256) { + revert('error message'); + } +} From 641a1bc4540be8077c68e4219b0b1044923cdf22 Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 17 Apr 2024 18:29:32 +0200 Subject: [PATCH 14/24] fix: added extra tests --- .../GranularGuardianAccessControl_int.t.sol | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/access_control/GranularGuardianAccessControl_int.t.sol b/tests/access_control/GranularGuardianAccessControl_int.t.sol index f71ea7c7..5e2c716d 100644 --- a/tests/access_control/GranularGuardianAccessControl_int.t.sol +++ b/tests/access_control/GranularGuardianAccessControl_int.t.sol @@ -24,6 +24,7 @@ contract GranularGuardianAccessControlIntTest is BaseTest { bool withEmergency ) { vm.assume(retryUser != address(this)); + vm.assume(solveEmergencyUser != address(this)); _filterAddress(clEmergencyOracle); _filterAddress(owner); _filterAddress(guardian); @@ -163,6 +164,28 @@ contract GranularGuardianAccessControlIntTest is BaseTest { vm.stopPrank(); } + function test_solveEmergencyWhenNotCCCWithEmergency( + address owner, + address guardian, + address retryUser, + address solveEmergencyUser, + address clEmergencyOracle + ) public createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, false) { + vm.expectRevert(bytes('')); + vm.startPrank(solveEmergencyUser); + 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) + ); + vm.stopPrank(); + } + function test_solveEmergencyWhenWrongCaller( address owner, address guardian, From 5ddbae8ccff1f0c2d4350a7f51c78827c8291d5b Mon Sep 17 00:00:00 2001 From: sendra Date: Thu, 18 Apr 2024 16:35:51 +0200 Subject: [PATCH 15/24] fix: add readme --- docs/overview.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/overview.md b/docs/overview.md index 098f8034..6da8ef77 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -55,7 +55,7 @@ Immutable utility contracts, which integrate into a.DI the specifics of each und send messages, by directly communicating with the receiving contract in the same chain where it is deployed. This pattern is useful to have same-chain-communication (bypass the cross chain bridging), but still following the same a.DI high-level flow. Misc aspects of the bridge adapters: -- To send a message to the bridge provider, the method `forwardMessage()` is called, via `DELEGATECALL`. This way, the CCC can hold the funds to pay the bridges, and can also be used as a trusted receiver. +- To send a message to the bridge provider, the method `forwardMessage()` is called, via `DELEGATECALL`. This way, the CCC can hold the funds to pay the bridges, and can also be used as a trusted receiver. - **No storage variables should be used on the bridge adapters**. - To receive a message a `handleReceive` (*the function name will actually depend on the bridge being integrated*) method needs to be called via `CALL` . This call will compare the caller to the trusted remote configured on deployment. @@ -164,6 +164,15 @@ Once the Emergency is solved, the permissions granted to the `guardian` are remo
+## CrossChainController access control + +The guardian system of the CrossChainController is externalized to the [GranularGuardianAccessControl](../src/contracts/access_control/GranularGuardianAccessControl.sol) contract. +This contract holds a granular way of assigning responsibilities to different entities (roles) so that guardian access +to CCC methods can be more deterministic. The roles that it holds are: +- **SOLVE_EMERGENCY_ROLE**: The holders of ths role can call the method `solveEmergency` on CrossChainController on the networks that have emergency enabled. +- **RETRY_ROLE**: the holders of this role can call the methods `retryTransaction` and `retryEnvelope` on CrossChainController (Forwarder). +- **DEFAULT_ADMIN_ROLE**: the holder of this role can grant roles, and change the Guardian of CrossChainController by calling `updateGuardian` method. + ### Contracts - [EmergencyRegistry](../src/contracts/emergency/EmergencyRegistry.sol): contract containing the registry of the emergency @@ -179,7 +188,7 @@ the `guardian` can call `solveEmergency`. When the call succeeds the local count The `solveEmergency` goal is to return a.DI to the operational mode, which granularly, means the following: - **Working Forwarding state**: this means that there is at least one BridgeAdapter configured for a specified chain, so a message can be sent to that chain. - **Working Receiving state**: this means that there are enough bridge adapters configured so that when a message is received, `requiredConfirmations` can be reached. - + The method can also be used as a way to force the system to a non-working state. This would be needed in the case that more than half of the bridges are compromised, so message should not be trusted (as the confirmations would then also be compromised), and `owner` could not be trusted since it is controlled via cross chain messaging. In that case there would be two ways of solving the emergency: From 1b4455fc523b3ba1872add84270fc076b3d785fb Mon Sep 17 00:00:00 2001 From: sendra Date: Fri, 19 Apr 2024 10:30:18 +0200 Subject: [PATCH 16/24] Update scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol Co-authored-by: Andrey --- scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol index afa852fb..a4ac46ec 100644 --- a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol +++ b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.0; import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; -//import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; import {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; import {GovernanceV3Avalanche} from 'aave-address-book/GovernanceV3Avalanche.sol'; import {GovernanceV3BNB} from 'aave-address-book/GovernanceV3BNB.sol'; From 7a8e35f7f15a56941cadf0764bdd5c1053dc5d33 Mon Sep 17 00:00:00 2001 From: sendra Date: Fri, 19 Apr 2024 10:30:29 +0200 Subject: [PATCH 17/24] Update scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol Co-authored-by: Andrey --- scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol index a4ac46ec..5620f1b3 100644 --- a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol +++ b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol @@ -6,7 +6,6 @@ import {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; import {GovernanceV3Avalanche} from 'aave-address-book/GovernanceV3Avalanche.sol'; import {GovernanceV3BNB} from 'aave-address-book/GovernanceV3BNB.sol'; import {GovernanceV3Gnosis} from 'aave-address-book/GovernanceV3Gnosis.sol'; -//import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; import {MiscPolygon} from 'aave-address-book/MiscPolygon.sol'; import {MiscAvalanche} from 'aave-address-book/MiscAvalanche.sol'; import {MiscBNB} from 'aave-address-book/MiscBNB.sol'; From 8b7967623aaa6d781402d2edffb490f63abb7c21 Mon Sep 17 00:00:00 2001 From: sendra Date: Fri, 19 Apr 2024 13:10:21 +0200 Subject: [PATCH 18/24] fix: WIP: integration tests still not working --- .../Deploy_Granular_CCC_Guardian.s.sol | 222 +++++++---- .../GranularGuardianAccessControl.sol | 37 +- .../IGranularGuardianAccessControl.sol | 11 + tests/BaseTest.sol | 152 ++++---- .../GranularGuardianAccessControl.t.sol | 214 ++++++++--- .../GranularGuardianAccessControl_int.t.sol | 355 ++++++++---------- tests/access_control/othertest.t.sol | 237 ++++++++++++ 7 files changed, 828 insertions(+), 400 deletions(-) create mode 100644 tests/access_control/othertest.t.sol diff --git a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol index 5620f1b3..74b6285e 100644 --- a/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol +++ b/scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol @@ -3,122 +3,204 @@ pragma solidity ^0.8.0; import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; import {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; import {GovernanceV3Avalanche} from 'aave-address-book/GovernanceV3Avalanche.sol'; import {GovernanceV3BNB} from 'aave-address-book/GovernanceV3BNB.sol'; import {GovernanceV3Gnosis} from 'aave-address-book/GovernanceV3Gnosis.sol'; +import {GovernanceV3Arbitrum} from 'aave-address-book/GovernanceV3Arbitrum.sol'; +import {GovernanceV3Optimism} from 'aave-address-book/GovernanceV3Optimism.sol'; +import {GovernanceV3Scroll} from 'aave-address-book/GovernanceV3Scroll.sol'; +import {GovernanceV3Metis} from 'aave-address-book/GovernanceV3Metis.sol'; +import {GovernanceV3Base} from 'aave-address-book/GovernanceV3Base.sol'; import {MiscPolygon} from 'aave-address-book/MiscPolygon.sol'; import {MiscAvalanche} from 'aave-address-book/MiscAvalanche.sol'; import {MiscBNB} from 'aave-address-book/MiscBNB.sol'; import {MiscGnosis} from 'aave-address-book/MiscGnosis.sol'; +import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol'; +import {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol'; +import {MiscOptimism} from 'aave-address-book/MiscOptimism.sol'; +import {MiscBase} from 'aave-address-book/MiscBase.sol'; +import {MiscScroll} from 'aave-address-book/MiscScroll.sol'; +import {MiscMetis} from 'aave-address-book/MiscMetis.sol'; import '../BaseScript.sol'; abstract contract BaseDeployGranularGuardian is BaseScript { - function AAVE_GUARDIAN() public view virtual returns (address); + address public immutable DEFAULT_ADMIN; + address public immutable RETRY_GUARDIAN; + address public immutable SOLVE_EMERGENCY_GUARDIAN; + address public immutable CROSS_CHAIN_CONTROLLER; - function RETRY_GUARDIAN() public view virtual returns (address); - - function CROSS_CHAIN_CONTROLLER() public view virtual returns (address); + constructor( + IGranularGuardianAccessControl.InitialGuardians memory initialGuardians, + address crossChainController + ) { + DEFAULT_ADMIN = initialGuardians.defaultAdmin; + RETRY_GUARDIAN = initialGuardians.retryGuardian; + SOLVE_EMERGENCY_GUARDIAN = initialGuardians.solveEmergencyGuardian; + CROSS_CHAIN_CONTROLLER = crossChainController; + } function _execute(DeployerHelpers.Addresses memory addresses) internal override { + IGranularGuardianAccessControl.InitialGuardians + memory initialGuardians = IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: DEFAULT_ADMIN, + retryGuardian: RETRY_GUARDIAN, + solveEmergencyGuardian: SOLVE_EMERGENCY_GUARDIAN + }); address granularCCCGuardian = address( - new GranularGuardianAccessControl( - AAVE_GUARDIAN(), - RETRY_GUARDIAN(), - AAVE_GUARDIAN(), - CROSS_CHAIN_CONTROLLER() - ) + new GranularGuardianAccessControl(initialGuardians, CROSS_CHAIN_CONTROLLER) ); addresses.granularCCCGuardian = granularCCCGuardian; } } -//contract Ethereum is BaseDeployGranularGuardian { -// function AAVE_GUARDIAN() public pure view override returns (address) { -// return MiscEthereum.PROTOCOL_GUARDIAN; -// } -// -// function RETRY_GUARDIAN() public pure view override returns (address) { -// return 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF; -// } -// -// function CROSS_CHAIN_CONTROLLER() public pure view override returns (address) { -// return GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER; -// } -// -// function TRANSACTION_NETWORK() public pure override returns (uint256) { -// return ChainIds.ETHEREUM; -// } -//} - -contract Avalanche is BaseDeployGranularGuardian { - function AAVE_GUARDIAN() public pure override returns (address) { - return MiscAvalanche.PROTOCOL_GUARDIAN; - } - - function RETRY_GUARDIAN() public pure override returns (address) { - return 0x3DBA1c4094BC0eE4772A05180B7E0c2F1cFD9c36; - } - - function CROSS_CHAIN_CONTROLLER() public pure override returns (address) { - return GovernanceV3Avalanche.CROSS_CHAIN_CONTROLLER; +contract Ethereum is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscEthereum.PROTOCOL_GUARDIAN, + retryGuardian: 0xb812d0944f8F581DfAA3a93Dda0d22EcEf51A9CF, + solveEmergencyGuardian: MiscEthereum.PROTOCOL_GUARDIAN + }), + GovernanceV3Ethereum.CROSS_CHAIN_CONTROLLER + ) +{ + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.ETHEREUM; } +} +contract Avalanche is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscAvalanche.PROTOCOL_GUARDIAN, + retryGuardian: 0x3DBA1c4094BC0eE4772A05180B7E0c2F1cFD9c36, + solveEmergencyGuardian: MiscAvalanche.PROTOCOL_GUARDIAN + }), + GovernanceV3Avalanche.CROSS_CHAIN_CONTROLLER + ) +{ function TRANSACTION_NETWORK() public pure override returns (uint256) { return ChainIds.AVALANCHE; } } -contract Polygon is BaseDeployGranularGuardian { - function AAVE_GUARDIAN() public pure override returns (address) { - return MiscPolygon.PROTOCOL_GUARDIAN; - } - - function RETRY_GUARDIAN() public pure override returns (address) { - return 0xbCEB4f363f2666E2E8E430806F37e97C405c130b; - } - - function CROSS_CHAIN_CONTROLLER() public pure override returns (address) { - return GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER; - } - +contract Polygon is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscPolygon.PROTOCOL_GUARDIAN, + retryGuardian: 0xbCEB4f363f2666E2E8E430806F37e97C405c130b, + solveEmergencyGuardian: MiscPolygon.PROTOCOL_GUARDIAN + }), + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER + ) +{ function TRANSACTION_NETWORK() public pure override returns (uint256) { return ChainIds.POLYGON; } } -contract Binance is BaseDeployGranularGuardian { - function AAVE_GUARDIAN() public pure override returns (address) { - return MiscBNB.PROTOCOL_GUARDIAN; - } - - function RETRY_GUARDIAN() public pure override returns (address) { - return 0xE8C5ab722d0b1B7316Cc4034f2BE91A5B1d29964; +contract Binance is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscBNB.PROTOCOL_GUARDIAN, + retryGuardian: 0xE8C5ab722d0b1B7316Cc4034f2BE91A5B1d29964, + solveEmergencyGuardian: MiscBNB.PROTOCOL_GUARDIAN + }), + GovernanceV3BNB.CROSS_CHAIN_CONTROLLER + ) +{ + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.BNB; } +} - function CROSS_CHAIN_CONTROLLER() public pure override returns (address) { - return GovernanceV3BNB.CROSS_CHAIN_CONTROLLER; +contract Gnosis is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscGnosis.PROTOCOL_GUARDIAN, + retryGuardian: 0xcb8a3E864D12190eD2b03cbA0833b15f2c314Ed8, + solveEmergencyGuardian: MiscGnosis.PROTOCOL_GUARDIAN + }), + GovernanceV3Gnosis.CROSS_CHAIN_CONTROLLER + ) +{ + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.GNOSIS; } +} +contract Metis is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscMetis.PROTOCOL_GUARDIAN, + retryGuardian: 0x9853589F951D724D9f7c6724E0fD63F9d888C429, + solveEmergencyGuardian: MiscMetis.PROTOCOL_GUARDIAN + }), + GovernanceV3Metis.CROSS_CHAIN_CONTROLLER + ) +{ function TRANSACTION_NETWORK() public pure override returns (uint256) { - return ChainIds.BNB; + return ChainIds.METIS; } } -contract Gnosis is BaseDeployGranularGuardian { - function AAVE_GUARDIAN() public pure override returns (address) { - return MiscGnosis.PROTOCOL_GUARDIAN; +contract Scroll is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscScroll.PROTOCOL_GUARDIAN, + retryGuardian: 0x4aAa03F0A61cf93eA252e987b585453578108358, + solveEmergencyGuardian: MiscScroll.PROTOCOL_GUARDIAN + }), + GovernanceV3Scroll.CROSS_CHAIN_CONTROLLER + ) +{ + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.SCROLL; } +} - function RETRY_GUARDIAN() public pure override returns (address) { - return 0xcb8a3E864D12190eD2b03cbA0833b15f2c314Ed8; +contract Optimism is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscOptimism.PROTOCOL_GUARDIAN, + retryGuardian: 0x3A800fbDeAC82a4d9c68A9FA0a315e095129CDBF, + solveEmergencyGuardian: MiscOptimism.PROTOCOL_GUARDIAN + }), + GovernanceV3Optimism.CROSS_CHAIN_CONTROLLER + ) +{ + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.OPTIMISM; } +} - function CROSS_CHAIN_CONTROLLER() public pure override returns (address) { - return GovernanceV3Gnosis.CROSS_CHAIN_CONTROLLER; +contract Arbitrum is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscArbitrum.PROTOCOL_GUARDIAN, + retryGuardian: 0x1Fcd437D8a9a6ea68da858b78b6cf10E8E0bF959, + solveEmergencyGuardian: MiscArbitrum.PROTOCOL_GUARDIAN + }), + GovernanceV3Arbitrum.CROSS_CHAIN_CONTROLLER + ) +{ + function TRANSACTION_NETWORK() public pure override returns (uint256) { + return ChainIds.ARBITRUM; } +} +contract Base is + BaseDeployGranularGuardian( + IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: MiscBase.PROTOCOL_GUARDIAN, + retryGuardian: 0x7FDA7C3528ad8f05e62148a700D456898b55f8d2, + solveEmergencyGuardian: MiscBase.PROTOCOL_GUARDIAN + }), + GovernanceV3Base.CROSS_CHAIN_CONTROLLER + ) +{ function TRANSACTION_NETWORK() public pure override returns (uint256) { - return ChainIds.GNOSIS; + return ChainIds.BASE; } } diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol index 98815461..7bfbd485 100644 --- a/src/contracts/access_control/GranularGuardianAccessControl.sol +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -7,6 +7,10 @@ import {IGranularGuardianAccessControl, Envelope, ICrossChainReceiver} from './I import {AccessControlEnumerable} from 'openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol'; import {IWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol'; +error DefaultAdminCantBe0(); +error CrossChainControllerCantBe0(); +error NewGuardianCantBe0(); + /** * @title GranularGuardianAccessControl * @author BGD Labs @@ -23,26 +27,27 @@ contract GranularGuardianAccessControl is AccessControlEnumerable, IGranularGuar bytes32 public constant RETRY_ROLE = keccak256('RETRY_ROLE'); /** - * @param defaultAdmin address that will have control of the default admin - * @param retryGuardian address to be added to the retry role - * @param solveEmergencyGuardian address to be added to the solve emergency role + * @param initialGuardians object with the initial guardians to assign the roles to * @param crossChainController address of the CrossChainController */ - constructor( - address defaultAdmin, - address retryGuardian, - address solveEmergencyGuardian, - address crossChainController - ) { - require(crossChainController != address(0), 'INVALID_CROSS_CHAIN_CONTROLLER'); - require(defaultAdmin != address(0), 'INVALID_DEFAULT_ADMIN'); + constructor(InitialGuardians memory initialGuardians, address crossChainController) { + if (crossChainController == address(0)) { + revert CrossChainControllerCantBe0(); + } + if (initialGuardians.defaultAdmin == address(0)) { + revert DefaultAdminCantBe0(); + } CROSS_CHAIN_CONTROLLER = crossChainController; - _setupRole(DEFAULT_ADMIN_ROLE, defaultAdmin); + _grantRole(DEFAULT_ADMIN_ROLE, initialGuardians.defaultAdmin); - _grantRole(SOLVE_EMERGENCY_ROLE, solveEmergencyGuardian); - _grantRole(RETRY_ROLE, retryGuardian); + if (initialGuardians.solveEmergencyGuardian != address(0)) { + _grantRole(SOLVE_EMERGENCY_ROLE, initialGuardians.solveEmergencyGuardian); + } + if (initialGuardians.solveEmergencyGuardian != address(0)) { + _grantRole(RETRY_ROLE, initialGuardians.retryGuardian); + } } /// @inheritdoc IGranularGuardianAccessControl @@ -93,7 +98,9 @@ contract GranularGuardianAccessControl is AccessControlEnumerable, IGranularGuar function updateGuardian( address newCrossChainControllerGuardian ) external onlyRole(DEFAULT_ADMIN_ROLE) { - require(newCrossChainControllerGuardian != address(0), 'INVALID_GUARDIAN'); + if (newCrossChainControllerGuardian == address(0)) { + revert NewGuardianCantBe0(); + } IWithGuardian(CROSS_CHAIN_CONTROLLER).updateGuardian(newCrossChainControllerGuardian); } } diff --git a/src/contracts/access_control/IGranularGuardianAccessControl.sol b/src/contracts/access_control/IGranularGuardianAccessControl.sol index f99bb698..e0ac8e68 100644 --- a/src/contracts/access_control/IGranularGuardianAccessControl.sol +++ b/src/contracts/access_control/IGranularGuardianAccessControl.sol @@ -11,6 +11,17 @@ import {ICrossChainForwarder} from '../interfaces/ICrossChainForwarder.sol'; * @notice interface containing the objects, events and methods definitions of the GranularGuardianAccessControl contract */ interface IGranularGuardianAccessControl { + /** + * @param defaultAdmin address that will have control of the default admin + * @param retryGuardian address to be added to the retry role + * @param solveEmergencyGuardian address to be added to the solve emergency role + */ + struct InitialGuardians { + address defaultAdmin; + address retryGuardian; + address solveEmergencyGuardian; + } + /** * @notice method called to re forward a previously sent envelope. This method is only callable by the accounts holding the RETRY_ROLE role diff --git a/tests/BaseTest.sol b/tests/BaseTest.sol index bfdeaa21..da3adc15 100644 --- a/tests/BaseTest.sol +++ b/tests/BaseTest.sol @@ -86,82 +86,82 @@ contract BaseTest is Test { _; } - function deployCCC( - address clEmergencyOracle, - bool withEmergency, - address owner, - uint256 destinationChainId - ) internal returns (address) { - address crossChainController; - address crossChainControllerImpl; - - address proxyFactory = address(new TransparentProxyFactory()); - address proxyAdmin = TransparentProxyFactory(proxyFactory).createDeterministicProxyAdmin( - owner, - 'admin salt' - ); - - if (withEmergency) { - crossChainControllerImpl = address( - ICrossChainController(address(new CrossChainControllerWithEmergencyMode(clEmergencyOracle))) - ); - - crossChainController = TransparentProxyFactory(proxyFactory).createDeterministic( - crossChainControllerImpl, - proxyAdmin, - abi.encodeWithSelector( - ICrossChainControllerWithEmergencyMode.initialize.selector, - owner, - address(this), - clEmergencyOracle, - new ICrossChainController.ConfirmationInput[](0), - new ICrossChainController.ReceiverBridgeAdapterConfigInput[](0), - new ICrossChainController.ForwarderBridgeAdapterConfigInput[](0), - new address[](0) - ), - 'test deployment' - ); - } else { - crossChainControllerImpl = address(new CrossChainController()); - - crossChainController = TransparentProxyFactory(proxyFactory).createDeterministic( - crossChainControllerImpl, - proxyAdmin, - abi.encodeWithSelector( - CrossChainController.initialize.selector, - owner, - address(this), - new ICrossChainController.ConfirmationInput[](0), - new ICrossChainController.ReceiverBridgeAdapterConfigInput[](0), - new ICrossChainController.ForwarderBridgeAdapterConfigInput[](0), - new address[](0) - ), - 'test deployment' - ); - } - - ICrossChainController.ForwarderBridgeAdapterConfigInput[] - memory forwarders = new ICrossChainController.ForwarderBridgeAdapterConfigInput[](2); - forwarders[0] = ICrossChainForwarder.ForwarderBridgeAdapterConfigInput({ - currentChainBridgeAdapter: address( - new AdapterMock(new IBaseAdapter.TrustedRemotesConfig[](0)) - ), - destinationBridgeAdapter: address(1), - destinationChainId: destinationChainId - }); - forwarders[1] = ICrossChainForwarder.ForwarderBridgeAdapterConfigInput({ - currentChainBridgeAdapter: address( - new AdapterMock(new IBaseAdapter.TrustedRemotesConfig[](0)) - ), - destinationBridgeAdapter: address(1), - destinationChainId: destinationChainId - }); - - hoax(owner); - ICrossChainForwarder(crossChainController).enableBridgeAdapters(forwarders); - - return crossChainController; - } + // function deployCCC( + // address clEmergencyOracle, + // bool withEmergency, + // address owner, + // uint256 destinationChainId + // ) internal returns (address) { + // address crossChainController; + // address crossChainControllerImpl; + // + // address proxyFactory = address(new TransparentProxyFactory()); + // address proxyAdmin = TransparentProxyFactory(proxyFactory).createDeterministicProxyAdmin( + // owner, + // 'admin salt' + // ); + // + // if (withEmergency) { + // crossChainControllerImpl = address( + // ICrossChainController(address(new CrossChainControllerWithEmergencyMode(clEmergencyOracle))) + // ); + // + // crossChainController = TransparentProxyFactory(proxyFactory).createDeterministic( + // crossChainControllerImpl, + // proxyAdmin, + // abi.encodeWithSelector( + // ICrossChainControllerWithEmergencyMode.initialize.selector, + // owner, + // address(this), + // clEmergencyOracle, + // new ICrossChainController.ConfirmationInput[](0), + // new ICrossChainController.ReceiverBridgeAdapterConfigInput[](0), + // new ICrossChainController.ForwarderBridgeAdapterConfigInput[](0), + // new address[](0) + // ), + // 'test deployment' + // ); + // } else { + // crossChainControllerImpl = address(new CrossChainController()); + // + // crossChainController = TransparentProxyFactory(proxyFactory).createDeterministic( + // crossChainControllerImpl, + // proxyAdmin, + // abi.encodeWithSelector( + // CrossChainController.initialize.selector, + // owner, + // address(this), + // new ICrossChainController.ConfirmationInput[](0), + // new ICrossChainController.ReceiverBridgeAdapterConfigInput[](0), + // new ICrossChainController.ForwarderBridgeAdapterConfigInput[](0), + // new address[](0) + // ), + // 'test deployment' + // ); + // } + // + // ICrossChainController.ForwarderBridgeAdapterConfigInput[] + // memory forwarders = new ICrossChainController.ForwarderBridgeAdapterConfigInput[](2); + // forwarders[0] = ICrossChainForwarder.ForwarderBridgeAdapterConfigInput({ + // currentChainBridgeAdapter: address( + // new AdapterMock(new IBaseAdapter.TrustedRemotesConfig[](0)) + // ), + // destinationBridgeAdapter: address(1), + // destinationChainId: destinationChainId + // }); + // forwarders[1] = ICrossChainForwarder.ForwarderBridgeAdapterConfigInput({ + // currentChainBridgeAdapter: address( + // new AdapterMock(new IBaseAdapter.TrustedRemotesConfig[](0)) + // ), + // destinationBridgeAdapter: address(1), + // destinationChainId: destinationChainId + // }); + // + // hoax(owner); + // ICrossChainForwarder(crossChainController).enableBridgeAdapters(forwarders); + // + // return crossChainController; + // } function _filterAddress(address addressToFilter) internal pure { vm.assume( diff --git a/tests/access_control/GranularGuardianAccessControl.t.sol b/tests/access_control/GranularGuardianAccessControl.t.sol index d9dc3a37..bee8770e 100644 --- a/tests/access_control/GranularGuardianAccessControl.t.sol +++ b/tests/access_control/GranularGuardianAccessControl.t.sol @@ -1,105 +1,200 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; -import 'forge-std/Test.sol'; import {TestUtils} from '../utils/TestUtils.sol'; import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; import {OwnableWithGuardian, IWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol'; +import '../BaseTest.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 = address(1956); - address public constant AAVE_GUARDIAN = address(1238975); +contract GranularGuardianAccessControlTest is BaseTest { bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; GranularGuardianAccessControl public control; - function setUp() public { - control = new GranularGuardianAccessControl( - AAVE_GUARDIAN, - RETRY_USER, - SOLVE_EMERGENCY_USER, - CROSS_CHAIN_CONTROLLER - ); + modifier setUpGranularGuardians( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc + ) { + _filterAddress(defaultAdmin); + _filterAddress(retryGuardian); + _filterAddress(solveEmergencyGuardian); + _filterAddress(ccc); + IGranularGuardianAccessControl.InitialGuardians + memory initialGuardians = IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: defaultAdmin, + retryGuardian: retryGuardian, + solveEmergencyGuardian: solveEmergencyGuardian + }); + control = new GranularGuardianAccessControl(initialGuardians, ccc); + _; + } + + function setUp() public {} + + function test_initializationWithWrongDefaultAdmin( + address retryGuardian, + address solveEmergencyGuardian, + address ccc + ) public { + _filterAddress(retryGuardian); + _filterAddress(solveEmergencyGuardian); + _filterAddress(ccc); + IGranularGuardianAccessControl.InitialGuardians + memory initialGuardians = IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: address(0), + retryGuardian: retryGuardian, + solveEmergencyGuardian: solveEmergencyGuardian + }); + vm.expectRevert(DefaultAdminCantBe0.selector); + new GranularGuardianAccessControl(initialGuardians, ccc); } - function test_initialization() public { - assertEq(control.CROSS_CHAIN_CONTROLLER(), CROSS_CHAIN_CONTROLLER); - assertEq(control.hasRole(DEFAULT_ADMIN_ROLE, AAVE_GUARDIAN), true); + function test_initializationWithWrongCCC( + address retryGuardian, + address solveEmergencyGuardian, + address defaultAdmin + ) public { + _filterAddress(defaultAdmin); + _filterAddress(retryGuardian); + _filterAddress(solveEmergencyGuardian); + IGranularGuardianAccessControl.InitialGuardians + memory initialGuardians = IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: defaultAdmin, + retryGuardian: retryGuardian, + solveEmergencyGuardian: solveEmergencyGuardian + }); + vm.expectRevert(CrossChainControllerCantBe0.selector); + new GranularGuardianAccessControl(initialGuardians, address(0)); + } + + function test_initialization( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + assertEq(control.CROSS_CHAIN_CONTROLLER(), ccc); + assertEq(control.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin), 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); + assertEq(control.getRoleMember(control.RETRY_ROLE(), 0), retryGuardian); + assertEq(control.getRoleMember(control.SOLVE_EMERGENCY_ROLE(), 0), solveEmergencyGuardian); } - function test_retryTx() public { - vm.startPrank(RETRY_USER); - _retryTx(); + function test_retryTx( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.startPrank(retryGuardian); + _retryTx(ccc); vm.stopPrank(); } - function test_retryTxWhenWrongCaller() public { + function test_retryTxWhenWrongCaller( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc, + address retryCaller + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.assume(retryCaller != retryGuardian); + hoax(retryCaller); vm.expectRevert( bytes( string.concat( 'AccessControl: account 0x', - TestUtils.toAsciiString(address(this)), + TestUtils.toAsciiString(retryCaller), ' is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' ) ) ); - _retryTx(); + _retryTx(ccc); } - function test_retryEnvelope(bytes32 mockEnvId) public { - vm.startPrank(RETRY_USER); + function test_retryEnvelope( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc, + bytes32 mockEnvId + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.startPrank(retryGuardian); // bytes32 mockEnvId = keccak256(abi.encode('mock envelope id')); - bytes32 envId = _retryEnvelope(mockEnvId); + bytes32 envId = _retryEnvelope(ccc, mockEnvId); assertEq(envId, mockEnvId); vm.stopPrank(); } - function test_retryEnvelopeWhenWrongCaller(bytes32 mockEnvId) public { + function test_retryEnvelopeWhenWrongCaller( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc, + address retryCaller, + bytes32 mockEnvId + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.assume(retryCaller != retryGuardian); + hoax(retryCaller); vm.expectRevert( bytes( string.concat( 'AccessControl: account 0x', - TestUtils.toAsciiString(address(this)), + TestUtils.toAsciiString(retryCaller), ' is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' ) ) ); - _retryEnvelope(mockEnvId); + _retryEnvelope(ccc, mockEnvId); } - function test_solveEmergency() public { - vm.startPrank(SOLVE_EMERGENCY_USER); - _solveEmergency(); + function test_solveEmergency( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.startPrank(solveEmergencyGuardian); + _solveEmergency(ccc); vm.stopPrank(); } - function test_solveEmergencyWhenWrongCaller() public { + function test_solveEmergencyWhenWrongCaller( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc, + address solveCaller + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.assume(solveCaller != solveEmergencyGuardian); + hoax(solveCaller); vm.expectRevert( bytes( string.concat( 'AccessControl: account 0x', - TestUtils.toAsciiString(address(this)), + TestUtils.toAsciiString(solveCaller), ' is missing role 0xf4cdc679c22cbf47d6de8e836ce79ffdae51f38408dcde3f0645de7634fa607d' ) ) ); - _solveEmergency(); + _solveEmergency(ccc); } - function test_updateGuardian(address newGuardian) public { - vm.startPrank(AAVE_GUARDIAN); + function test_updateGuardian( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc, + address newGuardian + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.startPrank(defaultAdmin); vm.mockCall( - CROSS_CHAIN_CONTROLLER, + ccc, abi.encodeWithSelector(IWithGuardian.updateGuardian.selector, newGuardian), abi.encode() ); @@ -107,12 +202,21 @@ contract GranularGuardianAccessControlTest is Test { vm.stopPrank(); } - function test_updateGuardianWhenWrongCaller(address newGuardian) public { + function test_updateGuardianWhenWrongCaller( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc, + address guardianCaller, + address newGuardian + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.assume(guardianCaller != defaultAdmin); + hoax(guardianCaller); vm.expectRevert( bytes( string.concat( 'AccessControl: account 0x', - TestUtils.toAsciiString(address(this)), + TestUtils.toAsciiString(guardianCaller), ' is missing role 0x0000000000000000000000000000000000000000000000000000000000000000' ) ) @@ -120,7 +224,19 @@ contract GranularGuardianAccessControlTest is Test { control.updateGuardian(newGuardian); } - function _solveEmergency() public { + function test_updateGuardianWhenWrongAddress( + address defaultAdmin, + address retryGuardian, + address solveEmergencyGuardian, + address ccc + ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + vm.startPrank(defaultAdmin); + vm.expectRevert(NewGuardianCantBe0.selector); + control.updateGuardian(address(0)); + vm.stopPrank(); + } + + function _solveEmergency(address ccc) public { ICrossChainReceiver.ConfirmationInput[] memory newConfirmations = new ICrossChainReceiver.ConfirmationInput[](0); ICrossChainReceiver.ValidityTimestampInput[] @@ -145,7 +261,7 @@ contract GranularGuardianAccessControlTest is Test { ); vm.mockCall( - CROSS_CHAIN_CONTROLLER, + ccc, abi.encodeWithSelector( ICrossChainControllerWithEmergencyMode.solveEmergency.selector, newConfirmations, @@ -172,7 +288,7 @@ contract GranularGuardianAccessControlTest is Test { ); } - function _retryEnvelope(bytes32 mockEnvId) internal returns (bytes32) { + function _retryEnvelope(address ccc, bytes32 mockEnvId) internal returns (bytes32) { Envelope memory envelope = Envelope({ nonce: 1, origin: address(12468), @@ -184,20 +300,20 @@ contract GranularGuardianAccessControlTest is Test { uint256 gasLimit = 300_000; vm.mockCall( - CROSS_CHAIN_CONTROLLER, + ccc, abi.encodeWithSelector(ICrossChainForwarder.retryEnvelope.selector, envelope, gasLimit), abi.encode(mockEnvId) ); return control.retryEnvelope(envelope, gasLimit); } - function _retryTx() internal { + function _retryTx(address ccc) internal { bytes memory encodedTransaction = abi.encode('some mock tx'); uint256 gasLimit = 300_000; address[] memory bridgeAdaptersToRetry = new address[](0); vm.mockCall( - CROSS_CHAIN_CONTROLLER, + ccc, abi.encodeWithSelector( ICrossChainForwarder.retryTransaction.selector, encodedTransaction, diff --git a/tests/access_control/GranularGuardianAccessControl_int.t.sol b/tests/access_control/GranularGuardianAccessControl_int.t.sol index 5e2c716d..3a171b4b 100644 --- a/tests/access_control/GranularGuardianAccessControl_int.t.sol +++ b/tests/access_control/GranularGuardianAccessControl_int.t.sol @@ -5,9 +5,14 @@ import {TestUtils} from '../utils/TestUtils.sol'; import '../../src/contracts/access_control/GranularGuardianAccessControl.sol'; import {OwnableWithGuardian, IWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol'; import '../BaseTest.sol'; +import {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; +import {MiscPolygon} from 'aave-address-book/MiscPolygon.sol'; contract GranularGuardianAccessControlIntTest is BaseTest { bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; + address public constant BGD_GUARDIAN = 0xbCEB4f363f2666E2E8E430806F37e97C405c130b; + + address public constant AAVE_GUARDIAN = MiscPolygon.PROTOCOL_GUARDIAN; // list of supported chains uint256 destinationChainId = ChainIds.ETHEREUM; @@ -15,164 +20,202 @@ contract GranularGuardianAccessControlIntTest is BaseTest { GranularGuardianAccessControl public control; address public ccc; - modifier createGGAC( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle, - bool withEmergency - ) { - vm.assume(retryUser != address(this)); - vm.assume(solveEmergencyUser != address(this)); - _filterAddress(clEmergencyOracle); - _filterAddress(owner); - _filterAddress(guardian); - _filterAddress(retryUser); - _filterAddress(solveEmergencyUser); - - // deploy ccc - ccc = deployCCC(clEmergencyOracle, withEmergency, owner, destinationChainId); + modifier createGGAC(address retryGuardian, address solveEmergencyGuardian) { + vm.assume(retryGuardian != address(this)); + vm.assume(solveEmergencyGuardian != address(this)); + _filterAddress(retryGuardian); + _filterAddress(solveEmergencyGuardian); - control = new GranularGuardianAccessControl(guardian, retryUser, solveEmergencyUser, ccc); + IGranularGuardianAccessControl.InitialGuardians + memory initialGuardians = IGranularGuardianAccessControl.InitialGuardians({ + defaultAdmin: AAVE_GUARDIAN, + retryGuardian: retryGuardian, + solveEmergencyGuardian: solveEmergencyGuardian + }); + control = new GranularGuardianAccessControl( + initialGuardians, + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER + ); - OwnableWithGuardian(ccc).updateGuardian(address(control)); + hoax(BGD_GUARDIAN); + OwnableWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).updateGuardian( + address(control) + ); _; } - function setUp() public {} + function setUp() public { + vm.createSelectFork('polygon', 51416198); + } function test_initialization( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle, - bool withEmergency - ) - public - createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, withEmergency) - { - assertEq(control.CROSS_CHAIN_CONTROLLER(), ccc); - assertEq(control.hasRole(DEFAULT_ADMIN_ROLE, guardian), true); + address retryGuardian, + address solveEmergencyGuardian + ) public createGGAC(retryGuardian, solveEmergencyGuardian) { + assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Polygon.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), retryUser); - assertEq(control.getRoleMember(control.SOLVE_EMERGENCY_ROLE(), 0), solveEmergencyUser); + assertEq(control.getRoleMember(control.RETRY_ROLE(), 0), retryGuardian); + assertEq(control.getRoleMember(control.SOLVE_EMERGENCY_ROLE(), 0), solveEmergencyGuardian); } function test_retryTx( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle + address retryGuardian, + address solveEmergencyGuardian, + address destination, + uint256 gasLimit ) public - createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) - generateRetryTxState(owner, ccc, destinationChainId, address(1324), 150_000) + createGGAC(retryGuardian, solveEmergencyGuardian) + generateRetryTxState( + GovernanceV3Polygon.EXECUTOR_LVL_1, + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, + destinationChainId, + destination, + gasLimit + ) { - _retryTx(retryUser); + _retryTx(destination, retryGuardian, gasLimit); + } + + function _retryTx(address destination, address retryGuardian, uint256 gasLimit) internal { + ExtendedTransaction memory extendedTx = _generateExtendedTransaction( + TestParams({ + destination: destination, + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1, + transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1 + }) + ); + + ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( + ccc + ).getForwarderBridgeAdaptersByChain(1); + address[] memory bridgeAdaptersToRetry = new address[](1); + bridgeAdaptersToRetry[0] = bridgeAdaptersByChain[1].currentChainBridgeAdapter; + + vm.startPrank(retryGuardian); + control.retryTransaction(extendedTx.transactionEncoded, gasLimit, bridgeAdaptersToRetry); + vm.stopPrank(); } function test_retryTxWhenWrongCaller( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle, - bool withEmergency - ) - public - createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, withEmergency) - { + address retryGuardian, + address solveEmergencyGuardian, + uint256 gasLimit, + address caller + ) public createGGAC(retryGuardian, solveEmergencyGuardian) { address[] memory bridgeAdaptersToRetry = new address[](0); + vm.assume(caller != retryGuardian); + hoax(caller); vm.expectRevert( bytes( string.concat( 'AccessControl: account 0x', - TestUtils.toAsciiString(address(this)), + TestUtils.toAsciiString(caller), ' is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' ) ) ); - control.retryTransaction(abi.encode('will not get used'), 150_000, bridgeAdaptersToRetry); + control.retryTransaction(abi.encode('will not get used'), gasLimit, bridgeAdaptersToRetry); } function test_retryEnvelope( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle + address retryGuardian, + address solveEmergencyGuardian, + address destination, + uint256 gasLimit ) public - createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) - generateRetryTxState(owner, ccc, destinationChainId, address(1324), 150_000) + createGGAC(retryGuardian, solveEmergencyGuardian) + generateRetryTxState( + GovernanceV3Polygon.EXECUTOR_LVL_1, + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, + destinationChainId, + destination, + gasLimit + ) { - _retryEnvelope(retryUser); + _retryEnvelope(destination, retryGuardian, gasLimit); + } + + function _retryEnvelope(address destination, address retryGuardian, uint256 gasLimit) internal { + uint256 envNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentEnvelopeNonce() - 1; + + ExtendedTransaction memory extendedTx = _generateExtendedTransaction( + TestParams({ + destination: destination, + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: envNonce, + transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1 + }) + ); + + vm.startPrank(retryGuardian); + bytes32 newTxId = control.retryEnvelope(extendedTx.envelope, gasLimit); + + ExtendedTransaction memory extendedTxAfter = _generateExtendedTransaction( + TestParams({ + destination: destination, + origin: address(this), + originChainId: block.chainid, + destinationChainId: destinationChainId, + envelopeNonce: envNonce, + transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + .getCurrentTransactionNonce() - 1 + }) + ); + + assertEq(extendedTxAfter.transactionId, newTxId); + vm.stopPrank(); } function test_retryEnvelopeWhenWrongCaller( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle - ) public createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) { + address retryGuardian, + address solveEmergencyGuardian, + uint256 gasLimit, + address caller + ) public createGGAC(retryGuardian, solveEmergencyGuardian) { + vm.assume(caller != retryGuardian); + Envelope memory envelope; + hoax(caller); vm.expectRevert( bytes( string.concat( 'AccessControl: account 0x', - TestUtils.toAsciiString(address(this)), + TestUtils.toAsciiString(caller), ' is missing role 0xc448b9502bbdf9850cc39823b6ea40cfe96d3ac63008e89edd2b8e98c6cc0af3' ) ) ); - control.retryEnvelope(envelope, 150_000); + control.retryEnvelope(envelope, gasLimit); } function test_solveEmergency( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle + address retryGuardian, + address solveEmergencyGuardian ) public - createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) - generateEmergencyState(ccc) - validateEmergencySolved(ccc) + createGGAC(retryGuardian, solveEmergencyGuardian) + generateEmergencyState(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) + validateEmergencySolved(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) { - vm.startPrank(solveEmergencyUser); - 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) - ); - vm.stopPrank(); - } - - function test_solveEmergencyWhenNotCCCWithEmergency( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle - ) public createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, false) { - vm.expectRevert(bytes('')); - vm.startPrank(solveEmergencyUser); + vm.startPrank(solveEmergencyGuardian); control.solveEmergency( new ICrossChainReceiver.ConfirmationInput[](0), new ICrossChainReceiver.ValidityTimestampInput[](0), @@ -187,17 +230,17 @@ contract GranularGuardianAccessControlIntTest is BaseTest { } function test_solveEmergencyWhenWrongCaller( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle - ) public createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, true) { + address retryGuardian, + address solveEmergencyGuardian, + address caller + ) public createGGAC(retryGuardian, solveEmergencyGuardian) { + vm.assume(caller != solveEmergencyGuardian); + hoax(caller); vm.expectRevert( bytes( string.concat( 'AccessControl: account 0x', - TestUtils.toAsciiString(address(this)), + TestUtils.toAsciiString(caller), ' is missing role 0xf4cdc679c22cbf47d6de8e836ce79ffdae51f38408dcde3f0645de7634fa607d' ) ) @@ -215,102 +258,34 @@ contract GranularGuardianAccessControlIntTest is BaseTest { } function test_updateGuardian( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle, - bool withEmergency, + address retryGuardian, + address solveEmergencyGuardian, address newGuardian - ) - public - createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, withEmergency) - { + ) public createGGAC(retryGuardian, solveEmergencyGuardian) { _filterAddress(newGuardian); - vm.startPrank(guardian); + vm.startPrank(AAVE_GUARDIAN); control.updateGuardian(newGuardian); - assertEq(IWithGuardian(ccc).guardian(), newGuardian); + assertEq(IWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).guardian(), newGuardian); vm.stopPrank(); } function test_updateGuardianWhenWrongCaller( - address owner, - address guardian, - address retryUser, - address solveEmergencyUser, - address clEmergencyOracle, - bool withEmergency, - address newGuardian - ) - public - createGGAC(owner, guardian, retryUser, solveEmergencyUser, clEmergencyOracle, withEmergency) - { + address retryGuardian, + address solveEmergencyGuardian, + address newGuardian, + address caller + ) public createGGAC(retryGuardian, solveEmergencyGuardian) { + vm.assume(caller != AAVE_GUARDIAN); + hoax(caller); vm.expectRevert( bytes( string.concat( 'AccessControl: account 0x', - TestUtils.toAsciiString(address(this)), + TestUtils.toAsciiString(caller), ' is missing role 0x0000000000000000000000000000000000000000000000000000000000000000' ) ) ); control.updateGuardian(newGuardian); } - - function _retryEnvelope(address retryUser) internal { - vm.startPrank(retryUser); - - uint256 txNonce = ICrossChainForwarder(ccc).getCurrentTransactionNonce() - 1; - uint256 envNonce = ICrossChainForwarder(ccc).getCurrentEnvelopeNonce() - 1; - - ExtendedTransaction memory extendedTx = _generateExtendedTransaction( - TestParams({ - destination: address(1324), - origin: address(this), - originChainId: block.chainid, - destinationChainId: destinationChainId, - envelopeNonce: envNonce, - transactionNonce: txNonce - }) - ); - - bytes32 newTxId = control.retryEnvelope(extendedTx.envelope, 150_000); - - ExtendedTransaction memory extendedTxAfter = _generateExtendedTransaction( - TestParams({ - destination: address(1324), - origin: address(this), - originChainId: block.chainid, - destinationChainId: destinationChainId, - envelopeNonce: envNonce, - transactionNonce: ICrossChainForwarder(ccc).getCurrentTransactionNonce() - 1 - }) - ); - - assertEq(extendedTxAfter.transactionId, newTxId); - vm.stopPrank(); - } - - function _retryTx(address retryUser) internal { - ExtendedTransaction memory extendedTx = _generateExtendedTransaction( - TestParams({ - destination: address(1324), - origin: address(this), - originChainId: block.chainid, - destinationChainId: destinationChainId, - envelopeNonce: ICrossChainForwarder(ccc).getCurrentTransactionNonce() - 1, - transactionNonce: ICrossChainForwarder(ccc).getCurrentTransactionNonce() - 1 - }) - ); - - ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( - ccc - ).getForwarderBridgeAdaptersByChain(1); - address[] memory bridgeAdaptersToRetry = new address[](1); - bridgeAdaptersToRetry[0] = bridgeAdaptersByChain[1].currentChainBridgeAdapter; - - vm.startPrank(retryUser); - control.retryTransaction(extendedTx.transactionEncoded, 150_000, bridgeAdaptersToRetry); - vm.stopPrank(); - } } diff --git a/tests/access_control/othertest.t.sol b/tests/access_control/othertest.t.sol new file mode 100644 index 00000000..890dd2c3 --- /dev/null +++ b/tests/access_control/othertest.t.sol @@ -0,0 +1,237 @@ +//// 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 {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; +//import {MiscPolygon} from 'aave-address-book/MiscPolygon.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 = 0xbCEB4f363f2666E2E8E430806F37e97C405c130b; +// address public constant AAVE_GUARDIAN = MiscPolygon.PROTOCOL_GUARDIAN; +// bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; +// // list of supported chains +// uint256 destinationChainId = ChainIds.ETHEREUM; +// GranularGuardianAccessControl public control; +// +// function setUp() public { +// vm.createSelectFork('polygon', 51416198); +// control = new GranularGuardianAccessControl( +// AAVE_GUARDIAN, +// RETRY_USER, +// SOLVE_EMERGENCY_USER, +// GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER +// ); +// hoax(BGD_GUARDIAN); +// OwnableWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).updateGuardian( +// address(control) +// ); +// } +// +// function test_initialization() public { +// assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Polygon.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( +// GovernanceV3Polygon.EXECUTOR_LVL_1, +// GovernanceV3Polygon.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(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) +// .getCurrentTransactionNonce() - 1, +// transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) +// .getCurrentTransactionNonce() - 1 +// }) +// ); +// ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( +// GovernanceV3Polygon.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( +// address destination, +// uint256 gasLimit +// ) +// public +// generateRetryTxState( +// GovernanceV3Polygon.EXECUTOR_LVL_1, +// GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, +// destinationChainId, +// destination, +// gasLimit +// ) +// { +// vm.assume(gasLimit < 300_000); +// vm.startPrank(RETRY_USER); +// +// uint256 txNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) +// .getCurrentTransactionNonce() - 1; +// uint256 envNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) +// .getCurrentEnvelopeNonce() - 1; +// +// ExtendedTransaction memory extendedTx = _generateExtendedTransaction( +// TestParams({ +// destination: destination, +// origin: address(this), +// originChainId: block.chainid, +// destinationChainId: destinationChainId, +// envelopeNonce: envNonce, +// transactionNonce: txNonce +// }) +// ); +// +// bytes32 newTxId = control.retryEnvelope(extendedTx.envelope, gasLimit); +// ExtendedTransaction memory extendedTxAfter = _generateExtendedTransaction( +// TestParams({ +// destination: destination, +// origin: address(this), +// originChainId: block.chainid, +// destinationChainId: destinationChainId, +// envelopeNonce: envNonce, +// transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) +// .getCurrentTransactionNonce() - 1 +// }) +// ); +// +// assertEq(extendedTxAfter.transactionId, newTxId); +// 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 +// generateEmergencyState(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) +// validateEmergencySolved(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) +// { +// vm.startPrank(SOLVE_EMERGENCY_USER); +// 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) +// ); +// 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(GovernanceV3Polygon.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 _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( +// // GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, +// // abi.encodeWithSelector(ICrossChainForwarder.retryEnvelope.selector, envelope, gasLimit), +// // abi.encode(mockEnvId) +// // ); +// // return control.retryEnvelope(envelope, gasLimit); +// // } +//} From 66e4ea243594f665a60fc58b79b75db44997194e Mon Sep 17 00:00:00 2001 From: sendra Date: Fri, 19 Apr 2024 15:30:20 +0200 Subject: [PATCH 19/24] fix: all tests working --- tests/BaseTest.sol | 80 +----- .../GranularGuardianAccessControl_int.t.sol | 6 +- tests/access_control/othertest.t.sol | 237 ------------------ 3 files changed, 5 insertions(+), 318 deletions(-) delete mode 100644 tests/access_control/othertest.t.sol diff --git a/tests/BaseTest.sol b/tests/BaseTest.sol index da3adc15..2cc85947 100644 --- a/tests/BaseTest.sol +++ b/tests/BaseTest.sol @@ -47,10 +47,11 @@ contract BaseTest is Test { modifier generateEmergencyState(address ccc) { address clEmergencyOracle = IEmergencyConsumer(ccc).getChainlinkEmergencyOracle(); + (, int256 answer, , , ) = ICLEmergencyOracle(clEmergencyOracle).latestRoundData(); vm.mockCall( clEmergencyOracle, abi.encodeWithSelector(ICLEmergencyOracle.latestRoundData.selector), - abi.encode(uint80(2), int256(1), block.timestamp, block.timestamp, uint80(2)) + abi.encode(uint80(2), int256(answer + 1), block.timestamp - 5, block.timestamp - 5, uint80(2)) ); _; } @@ -86,83 +87,6 @@ contract BaseTest is Test { _; } - // function deployCCC( - // address clEmergencyOracle, - // bool withEmergency, - // address owner, - // uint256 destinationChainId - // ) internal returns (address) { - // address crossChainController; - // address crossChainControllerImpl; - // - // address proxyFactory = address(new TransparentProxyFactory()); - // address proxyAdmin = TransparentProxyFactory(proxyFactory).createDeterministicProxyAdmin( - // owner, - // 'admin salt' - // ); - // - // if (withEmergency) { - // crossChainControllerImpl = address( - // ICrossChainController(address(new CrossChainControllerWithEmergencyMode(clEmergencyOracle))) - // ); - // - // crossChainController = TransparentProxyFactory(proxyFactory).createDeterministic( - // crossChainControllerImpl, - // proxyAdmin, - // abi.encodeWithSelector( - // ICrossChainControllerWithEmergencyMode.initialize.selector, - // owner, - // address(this), - // clEmergencyOracle, - // new ICrossChainController.ConfirmationInput[](0), - // new ICrossChainController.ReceiverBridgeAdapterConfigInput[](0), - // new ICrossChainController.ForwarderBridgeAdapterConfigInput[](0), - // new address[](0) - // ), - // 'test deployment' - // ); - // } else { - // crossChainControllerImpl = address(new CrossChainController()); - // - // crossChainController = TransparentProxyFactory(proxyFactory).createDeterministic( - // crossChainControllerImpl, - // proxyAdmin, - // abi.encodeWithSelector( - // CrossChainController.initialize.selector, - // owner, - // address(this), - // new ICrossChainController.ConfirmationInput[](0), - // new ICrossChainController.ReceiverBridgeAdapterConfigInput[](0), - // new ICrossChainController.ForwarderBridgeAdapterConfigInput[](0), - // new address[](0) - // ), - // 'test deployment' - // ); - // } - // - // ICrossChainController.ForwarderBridgeAdapterConfigInput[] - // memory forwarders = new ICrossChainController.ForwarderBridgeAdapterConfigInput[](2); - // forwarders[0] = ICrossChainForwarder.ForwarderBridgeAdapterConfigInput({ - // currentChainBridgeAdapter: address( - // new AdapterMock(new IBaseAdapter.TrustedRemotesConfig[](0)) - // ), - // destinationBridgeAdapter: address(1), - // destinationChainId: destinationChainId - // }); - // forwarders[1] = ICrossChainForwarder.ForwarderBridgeAdapterConfigInput({ - // currentChainBridgeAdapter: address( - // new AdapterMock(new IBaseAdapter.TrustedRemotesConfig[](0)) - // ), - // destinationBridgeAdapter: address(1), - // destinationChainId: destinationChainId - // }); - // - // hoax(owner); - // ICrossChainForwarder(crossChainController).enableBridgeAdapters(forwarders); - // - // return crossChainController; - // } - function _filterAddress(address addressToFilter) internal pure { vm.assume( addressToFilter != address(0) && diff --git a/tests/access_control/GranularGuardianAccessControl_int.t.sol b/tests/access_control/GranularGuardianAccessControl_int.t.sol index 3a171b4b..c9da1f19 100644 --- a/tests/access_control/GranularGuardianAccessControl_int.t.sol +++ b/tests/access_control/GranularGuardianAccessControl_int.t.sol @@ -45,7 +45,7 @@ contract GranularGuardianAccessControlIntTest is BaseTest { } function setUp() public { - vm.createSelectFork('polygon', 51416198); + vm.createSelectFork('polygon', 56006881); } function test_initialization( @@ -96,10 +96,10 @@ contract GranularGuardianAccessControlIntTest is BaseTest { ); ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( - ccc + GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER ).getForwarderBridgeAdaptersByChain(1); address[] memory bridgeAdaptersToRetry = new address[](1); - bridgeAdaptersToRetry[0] = bridgeAdaptersByChain[1].currentChainBridgeAdapter; + bridgeAdaptersToRetry[0] = bridgeAdaptersByChain[0].currentChainBridgeAdapter; vm.startPrank(retryGuardian); control.retryTransaction(extendedTx.transactionEncoded, gasLimit, bridgeAdaptersToRetry); diff --git a/tests/access_control/othertest.t.sol b/tests/access_control/othertest.t.sol deleted file mode 100644 index 890dd2c3..00000000 --- a/tests/access_control/othertest.t.sol +++ /dev/null @@ -1,237 +0,0 @@ -//// 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 {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol'; -//import {MiscPolygon} from 'aave-address-book/MiscPolygon.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 = 0xbCEB4f363f2666E2E8E430806F37e97C405c130b; -// address public constant AAVE_GUARDIAN = MiscPolygon.PROTOCOL_GUARDIAN; -// bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; -// // list of supported chains -// uint256 destinationChainId = ChainIds.ETHEREUM; -// GranularGuardianAccessControl public control; -// -// function setUp() public { -// vm.createSelectFork('polygon', 51416198); -// control = new GranularGuardianAccessControl( -// AAVE_GUARDIAN, -// RETRY_USER, -// SOLVE_EMERGENCY_USER, -// GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER -// ); -// hoax(BGD_GUARDIAN); -// OwnableWithGuardian(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER).updateGuardian( -// address(control) -// ); -// } -// -// function test_initialization() public { -// assertEq(control.CROSS_CHAIN_CONTROLLER(), GovernanceV3Polygon.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( -// GovernanceV3Polygon.EXECUTOR_LVL_1, -// GovernanceV3Polygon.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(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) -// .getCurrentTransactionNonce() - 1, -// transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) -// .getCurrentTransactionNonce() - 1 -// }) -// ); -// ICrossChainForwarder.ChainIdBridgeConfig[] memory bridgeAdaptersByChain = ICrossChainForwarder( -// GovernanceV3Polygon.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( -// address destination, -// uint256 gasLimit -// ) -// public -// generateRetryTxState( -// GovernanceV3Polygon.EXECUTOR_LVL_1, -// GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, -// destinationChainId, -// destination, -// gasLimit -// ) -// { -// vm.assume(gasLimit < 300_000); -// vm.startPrank(RETRY_USER); -// -// uint256 txNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) -// .getCurrentTransactionNonce() - 1; -// uint256 envNonce = ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) -// .getCurrentEnvelopeNonce() - 1; -// -// ExtendedTransaction memory extendedTx = _generateExtendedTransaction( -// TestParams({ -// destination: destination, -// origin: address(this), -// originChainId: block.chainid, -// destinationChainId: destinationChainId, -// envelopeNonce: envNonce, -// transactionNonce: txNonce -// }) -// ); -// -// bytes32 newTxId = control.retryEnvelope(extendedTx.envelope, gasLimit); -// ExtendedTransaction memory extendedTxAfter = _generateExtendedTransaction( -// TestParams({ -// destination: destination, -// origin: address(this), -// originChainId: block.chainid, -// destinationChainId: destinationChainId, -// envelopeNonce: envNonce, -// transactionNonce: ICrossChainForwarder(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) -// .getCurrentTransactionNonce() - 1 -// }) -// ); -// -// assertEq(extendedTxAfter.transactionId, newTxId); -// 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 -// generateEmergencyState(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) -// validateEmergencySolved(GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER) -// { -// vm.startPrank(SOLVE_EMERGENCY_USER); -// 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) -// ); -// 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(GovernanceV3Polygon.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 _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( -// // GovernanceV3Polygon.CROSS_CHAIN_CONTROLLER, -// // abi.encodeWithSelector(ICrossChainForwarder.retryEnvelope.selector, envelope, gasLimit), -// // abi.encode(mockEnvId) -// // ); -// // return control.retryEnvelope(envelope, gasLimit); -// // } -//} From 54770fbf1f66178c93adc617f0b8f557763e6ea4 Mon Sep 17 00:00:00 2001 From: sendra Date: Fri, 19 Apr 2024 16:35:32 +0200 Subject: [PATCH 20/24] fix: removed unused mock --- tests/BaseTest.sol | 1 - tests/mocks/AdapterMock.sol | 30 ------------------------------ 2 files changed, 31 deletions(-) delete mode 100644 tests/mocks/AdapterMock.sol diff --git a/tests/BaseTest.sol b/tests/BaseTest.sol index 2cc85947..3b33afee 100644 --- a/tests/BaseTest.sol +++ b/tests/BaseTest.sol @@ -11,7 +11,6 @@ import {CrossChainControllerWithEmergencyMode, ICrossChainControllerWithEmergenc import {IEmergencyConsumer} from '../src/contracts/emergency/interfaces/IEmergencyConsumer.sol'; import {ICLEmergencyOracle} from '../src/contracts/emergency/interfaces/ICLEmergencyOracle.sol'; import {TransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; -import './mocks/AdapterMock.sol'; contract BaseTest is Test { bytes internal constant MESSAGE = bytes('this is the message to send'); diff --git a/tests/mocks/AdapterMock.sol b/tests/mocks/AdapterMock.sol deleted file mode 100644 index c914e98f..00000000 --- a/tests/mocks/AdapterMock.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {BaseAdapter, IBaseAdapter} from '../../src/contracts/adapters/BaseAdapter.sol'; - -contract AdapterMock is BaseAdapter { - constructor( - TrustedRemotesConfig[] memory trustedRemotes - ) BaseAdapter(address(1), 0, 'working adapter', trustedRemotes) {} - - /// @inheritdoc IBaseAdapter - function forwardMessage( - address, - uint256, - uint256, - bytes memory - ) external pure returns (address, uint256) { - return (address(1), 1); - } - - /// @inheritdoc IBaseAdapter - function nativeToInfraChainId(uint256) public pure override returns (uint256) { - revert('error message'); - } - - /// @inheritdoc IBaseAdapter - function infraToNativeChainId(uint256) public pure override returns (uint256) { - revert('error message'); - } -} From d1044632529bfd65c6b73ec3e8da675446e85bea Mon Sep 17 00:00:00 2001 From: sendra Date: Wed, 24 Apr 2024 10:40:39 +0200 Subject: [PATCH 21/24] fix: moved errors to interface. Fixed test --- .../access_control/GranularGuardianAccessControl.sol | 4 ---- .../access_control/IGranularGuardianAccessControl.sol | 9 +++++++++ tests/access_control/GranularGuardianAccessControl.t.sol | 7 ++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol index 7bfbd485..bff26ad2 100644 --- a/src/contracts/access_control/GranularGuardianAccessControl.sol +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -7,10 +7,6 @@ import {IGranularGuardianAccessControl, Envelope, ICrossChainReceiver} from './I import {AccessControlEnumerable} from 'openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol'; import {IWithGuardian} from 'solidity-utils/contracts/access-control/OwnableWithGuardian.sol'; -error DefaultAdminCantBe0(); -error CrossChainControllerCantBe0(); -error NewGuardianCantBe0(); - /** * @title GranularGuardianAccessControl * @author BGD Labs diff --git a/src/contracts/access_control/IGranularGuardianAccessControl.sol b/src/contracts/access_control/IGranularGuardianAccessControl.sol index e0ac8e68..69673ac2 100644 --- a/src/contracts/access_control/IGranularGuardianAccessControl.sol +++ b/src/contracts/access_control/IGranularGuardianAccessControl.sol @@ -11,6 +11,15 @@ import {ICrossChainForwarder} from '../interfaces/ICrossChainForwarder.sol'; * @notice interface containing the objects, events and methods definitions of the GranularGuardianAccessControl contract */ interface IGranularGuardianAccessControl { + /// @dev default admin address can not be address 0 + error DefaultAdminCantBe0(); + + /// @dev CrossChainController address can not be address 0 + error CrossChainControllerCantBe0(); + + /// @dev new Guardian address can not be address 0 + error NewGuardianCantBe0(); + /** * @param defaultAdmin address that will have control of the default admin * @param retryGuardian address to be added to the retry role diff --git a/tests/access_control/GranularGuardianAccessControl.t.sol b/tests/access_control/GranularGuardianAccessControl.t.sol index bee8770e..687cc525 100644 --- a/tests/access_control/GranularGuardianAccessControl.t.sol +++ b/tests/access_control/GranularGuardianAccessControl.t.sol @@ -47,7 +47,7 @@ contract GranularGuardianAccessControlTest is BaseTest { retryGuardian: retryGuardian, solveEmergencyGuardian: solveEmergencyGuardian }); - vm.expectRevert(DefaultAdminCantBe0.selector); + vm.expectRevert(IGranularGuardianAccessControl.DefaultAdminCantBe0.selector); new GranularGuardianAccessControl(initialGuardians, ccc); } @@ -65,7 +65,7 @@ contract GranularGuardianAccessControlTest is BaseTest { retryGuardian: retryGuardian, solveEmergencyGuardian: solveEmergencyGuardian }); - vm.expectRevert(CrossChainControllerCantBe0.selector); + vm.expectRevert(IGranularGuardianAccessControl.CrossChainControllerCantBe0.selector); new GranularGuardianAccessControl(initialGuardians, address(0)); } @@ -192,6 +192,7 @@ contract GranularGuardianAccessControlTest is BaseTest { address ccc, address newGuardian ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { + _filterAddress(newGuardian); vm.startPrank(defaultAdmin); vm.mockCall( ccc, @@ -231,7 +232,7 @@ contract GranularGuardianAccessControlTest is BaseTest { address ccc ) public setUpGranularGuardians(defaultAdmin, retryGuardian, solveEmergencyGuardian, ccc) { vm.startPrank(defaultAdmin); - vm.expectRevert(NewGuardianCantBe0.selector); + vm.expectRevert(IGranularGuardianAccessControl.NewGuardianCantBe0.selector); control.updateGuardian(address(0)); vm.stopPrank(); } From 6b0370eec37982feafcec95c1001f3386677230d Mon Sep 17 00:00:00 2001 From: sendra Date: Mon, 6 May 2024 09:53:37 +0200 Subject: [PATCH 22/24] fix: added correct check --- src/contracts/access_control/GranularGuardianAccessControl.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/access_control/GranularGuardianAccessControl.sol b/src/contracts/access_control/GranularGuardianAccessControl.sol index bff26ad2..0bc17dda 100644 --- a/src/contracts/access_control/GranularGuardianAccessControl.sol +++ b/src/contracts/access_control/GranularGuardianAccessControl.sol @@ -41,7 +41,7 @@ contract GranularGuardianAccessControl is AccessControlEnumerable, IGranularGuar if (initialGuardians.solveEmergencyGuardian != address(0)) { _grantRole(SOLVE_EMERGENCY_ROLE, initialGuardians.solveEmergencyGuardian); } - if (initialGuardians.solveEmergencyGuardian != address(0)) { + if (initialGuardians.retryGuardian != address(0)) { _grantRole(RETRY_ROLE, initialGuardians.retryGuardian); } } From 9bb8ce1f7b224f85de2e882b9b69b92e46d7ffcf Mon Sep 17 00:00:00 2001 From: nisnislevi Date: Tue, 7 May 2024 18:14:17 +0300 Subject: [PATCH 23/24] add report for Granular-Guardian-Access-Control --- .../Granular-Guardian-Access-Control.pdf | Bin 0 -> 195702 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 security/certora/reports/Granular-Guardian-Access-Control.pdf diff --git a/security/certora/reports/Granular-Guardian-Access-Control.pdf b/security/certora/reports/Granular-Guardian-Access-Control.pdf new file mode 100644 index 0000000000000000000000000000000000000000..980914c1a82d7bc9a2ac7031707f8a8e8ef193ec GIT binary patch literal 195702 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 Date: Fri, 17 May 2024 10:47:56 +0200 Subject: [PATCH 24/24] fix: added link to audit report --- docs/overview.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/overview.md b/docs/overview.md index 6da8ef77..27cba75f 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -173,6 +173,8 @@ to CCC methods can be more deterministic. The roles that it holds are: - **RETRY_ROLE**: the holders of this role can call the methods `retryTransaction` and `retryEnvelope` on CrossChainController (Forwarder). - **DEFAULT_ADMIN_ROLE**: the holder of this role can grant roles, and change the Guardian of CrossChainController by calling `updateGuardian` method. +The audit report by Certora can be found [here](../security/certora/reports/Granular-Guardian-Access-Control.pdf) + ### Contracts - [EmergencyRegistry](../src/contracts/emergency/EmergencyRegistry.sol): contract containing the registry of the emergency