Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added access control contract to make guardian granular #20

Merged
merged 27 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f7d4d18
feat: Added access control contract to make guardian granular
sendra Dec 20, 2023
d4b671e
fix: updated with oz (modified) enumerable access control
sendra Dec 20, 2023
98fad05
fix: added natspec
sendra Dec 20, 2023
ac89c0e
fix: Add test
sendra Dec 20, 2023
e114d27
fix: added method to change guardian
sendra Dec 20, 2023
5612b26
fix: added deployment script. Updated address book lib
sendra Dec 20, 2023
4c73aaf
fix: save granular guardian in deployment jsons
sendra Dec 21, 2023
c78f581
fix: add integration tests
sendra Dec 21, 2023
b18fb13
fix: added more integration tests
sendra Dec 22, 2023
8044f4d
fix: fixed retry envelope int test
sendra Dec 22, 2023
c1ff698
fix: solved conflicts
sendra Apr 17, 2024
804717f
fix: fixed fuzzing on tests
sendra Apr 17, 2024
30365f3
fix: ci
sakulstra Apr 17, 2024
c064a3d
fix: removed fork environment for tests
sendra Apr 17, 2024
641a1bc
fix: added extra tests
sendra Apr 17, 2024
5ddbae8
fix: add readme
sendra Apr 18, 2024
1b4455f
Update scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol
sendra Apr 19, 2024
7a8e35f
Update scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol
sendra Apr 19, 2024
8b79676
fix: WIP: integration tests still not working
sendra Apr 19, 2024
66e4ea2
fix: all tests working
sendra Apr 19, 2024
54770fb
fix: removed unused mock
sendra Apr 19, 2024
d104463
fix: moved errors to interface. Fixed test
sendra Apr 24, 2024
6b0370e
fix: added correct check
sendra May 6, 2024
9bb8ce1
add report for Granular-Guardian-Access-Control
nisnislevi May 7, 2024
6197b19
Merge pull request #52 from Certora/certora-for-granular-access-control
sendra May 17, 2024
df417af
Merge branch 'feat/phase-3' of github.com:bgd-labs/aave-delivery-infr…
sendra May 17, 2024
0542da4
fix: added link to audit report
sendra May 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ on:
jobs:
test:
uses: bgd-labs/github-workflows/.github/workflows/foundry-test.yml@main
secrets: inherit
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ 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)

deploy-ccc-revision-and-update:
$(call deploy_fn,CCC/UpdateCCC,ethereum)

Expand Down
15 changes: 13 additions & 2 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -164,6 +164,17 @@ Once the Emergency is solved, the permissions granted to the `guardian` are remo

<br>

## 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.

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
Expand All @@ -179,7 +190,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:
Expand Down
3 changes: 3 additions & 0 deletions scripts/BaseScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ library DeployerHelpers {
address crossChainControllerImpl;
address emergencyRegistry;
address gnosisAdapter;
address granularCCCGuardian;
address guardian;
address hlAdapter;
address lzAdapter;
Expand Down Expand Up @@ -105,6 +106,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)),
Expand Down Expand Up @@ -145,6 +147,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);
Expand Down
206 changes: 206 additions & 0 deletions scripts/access_control/Deploy_Granular_CCC_Guardian.s.sol
sendra marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: BUSL-1.1
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 {
address public immutable DEFAULT_ADMIN;
address public immutable RETRY_GUARDIAN;
address public immutable SOLVE_EMERGENCY_GUARDIAN;
address public immutable CROSS_CHAIN_CONTROLLER;

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(initialGuardians, CROSS_CHAIN_CONTROLLER)
);

addresses.granularCCCGuardian = granularCCCGuardian;
}
}

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(
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(
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;
}
}

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.METIS;
}
}

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;
}
}

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;
}
}

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.BASE;
}
}
Binary file not shown.
102 changes: 102 additions & 0 deletions src/contracts/access_control/GranularGuardianAccessControl.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.8;

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
* @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 initialGuardians object with the initial guardians to assign the roles to
* @param crossChainController address of the CrossChainController
*/
constructor(InitialGuardians memory initialGuardians, address crossChainController) {
if (crossChainController == address(0)) {
revert CrossChainControllerCantBe0();
}
if (initialGuardians.defaultAdmin == address(0)) {
revert DefaultAdminCantBe0();
}

CROSS_CHAIN_CONTROLLER = crossChainController;

_grantRole(DEFAULT_ADMIN_ROLE, initialGuardians.defaultAdmin);

if (initialGuardians.solveEmergencyGuardian != address(0)) {
_grantRole(SOLVE_EMERGENCY_ROLE, initialGuardians.solveEmergencyGuardian);
}
if (initialGuardians.retryGuardian != address(0)) {
_grantRole(RETRY_ROLE, initialGuardians.retryGuardian);
}
}

/// @inheritdoc IGranularGuardianAccessControl
function retryEnvelope(
Envelope memory envelope,
brotherlymite marked this conversation as resolved.
Show resolved Hide resolved
uint256 gasLimit
) external onlyRole(RETRY_ROLE) returns (bytes32) {
return ICrossChainForwarder(CROSS_CHAIN_CONTROLLER).retryEnvelope(envelope, gasLimit);
}

/// @inheritdoc IGranularGuardianAccessControl
function retryTransaction(
bytes memory encodedTransaction,
uint256 gasLimit,
address[] memory bridgeAdaptersToRetry
brotherlymite marked this conversation as resolved.
Show resolved Hide resolved
) external onlyRole(RETRY_ROLE) {
ICrossChainForwarder(CROSS_CHAIN_CONTROLLER).retryTransaction(
encodedTransaction,
gasLimit,
bridgeAdaptersToRetry
);
}

/// @inheritdoc IGranularGuardianAccessControl
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 onlyRole(SOLVE_EMERGENCY_ROLE) {
ICrossChainControllerWithEmergencyMode(CROSS_CHAIN_CONTROLLER).solveEmergency(
newConfirmations,
newValidityTimestamp,
receiverBridgeAdaptersToAllow,
receiverBridgeAdaptersToDisallow,
sendersToApprove,
sendersToRemove,
forwarderBridgeAdaptersToEnable,
forwarderBridgeAdaptersToDisable
);
}

/// @inheritdoc IGranularGuardianAccessControl
function updateGuardian(
address newCrossChainControllerGuardian
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (newCrossChainControllerGuardian == address(0)) {
revert NewGuardianCantBe0();
}
IWithGuardian(CROSS_CHAIN_CONTROLLER).updateGuardian(newCrossChainControllerGuardian);
}
}
Loading
Loading