Skip to content

Commit

Permalink
Merge pull request #20 from bgd-labs/feat/granular-access-control
Browse files Browse the repository at this point in the history
feat: Added access control contract to make guardian granular
  • Loading branch information
kyzia551 authored May 17, 2024
2 parents d8f324a + 0542da4 commit b5fa914
Show file tree
Hide file tree
Showing 12 changed files with 1,138 additions and 9 deletions.
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
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,
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
) 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

0 comments on commit b5fa914

Please sign in to comment.