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

Add utilities for CrossChain messaging #3183

Merged
merged 42 commits into from Mar 30, 2022
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
07aa7b9
move ICompoundTimelock to a vendor/compound
Amxx Feb 10, 2022
f6e286b
add CrossChainEnabled and support for some chains
Amxx Feb 10, 2022
36d479a
add missing dependencies
Amxx Feb 10, 2022
23b82b7
web3js and ethers testing + lint
Amxx Feb 10, 2022
b98fae5
testing AccessControlCrossChain
Amxx Feb 14, 2022
3ee00b6
fix lint
Amxx Feb 14, 2022
670f597
don't run slither on the mocks
Amxx Feb 14, 2022
5ed8f33
refactor testing
Amxx Feb 14, 2022
5eaab89
add CrossChainEnabledPolygonChild
Amxx Feb 16, 2022
03294aa
add polygon child testing
Amxx Feb 16, 2022
80ec9e2
rename params for clarity
Amxx Feb 22, 2022
9faf612
add CrossChain documentation
Amxx Feb 24, 2022
0f6e613
Merge branch 'master' into feature/crosschain
Amxx Feb 24, 2022
4928727
fix spelling
Amxx Feb 24, 2022
27c18a5
remove ethers.js version of crosschain testing
Amxx Feb 24, 2022
be3543a
remove ethers/waffle dependencies
Amxx Feb 24, 2022
3b3cd58
add link to optimism bridges deployments
Amxx Feb 25, 2022
2313a06
Merge branch 'master' into feature/crosschain
frangio Mar 22, 2022
d9f6513
reset lockfile
frangio Mar 22, 2022
147528b
Apply suggestions from code review
Amxx Mar 23, 2022
4ebb13d
wording
Amxx Mar 23, 2022
2260e5a
Merge remote-tracking branch 'Amxx/feature/crosschain' into feature/c…
Amxx Mar 23, 2022
e73d815
fix lint
Amxx Mar 23, 2022
b300b6b
check custom error
Amxx Mar 23, 2022
5e1dfa2
add reverts to crossChainSender if call is not crosschain
Amxx Mar 24, 2022
ab77189
add custom error check
Amxx Mar 24, 2022
b2a4418
bump required solidity version
Amxx Mar 24, 2022
afb52f1
fix inconsistency in tests
Amxx Mar 25, 2022
6ac95cd
improve custom error detection
Amxx Mar 25, 2022
345a9b0
expect rewrite
Amxx Mar 25, 2022
bc2b083
sometimes the linter is not really smart
Amxx Mar 25, 2022
69cdf15
revert to simpler error detection
Amxx Mar 25, 2022
41cd634
add changelog entry
frangio Mar 28, 2022
1d4c86d
add customError helper
Amxx Mar 29, 2022
19752c8
Merge branch 'master' into feature/crosschain
Amxx Mar 29, 2022
1252614
add PR link in changelog entry
Amxx Mar 29, 2022
10acf59
print full revert string if customError detection fails
Amxx Mar 29, 2022
c1f0793
use bridge address instead of inbox for arbitrumL1
Amxx Mar 29, 2022
5ea8297
remove console.error
frangio Mar 29, 2022
04f4629
avoid mutating a dependency
frangio Mar 29, 2022
b0e9cdb
refactor crosschain helper to avoid global state
frangio Mar 29, 2022
cc2dd04
fix custom error tests when optimizations are on
frangio Mar 29, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@
* `Governor`: Add a way to parameterize votes. This can be used to implement voting systems such as fractionalized voting, ERC721 based voting, or any number of other systems. The `params` argument added to `_countVote` method, and included in the newly added `_getVotes` method, can be used by counting and voting modules respectively for such purposes.
* `TimelockController`: Add a separate canceller role for the ability to cancel. ([#3165](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3165))
* `draft-ERC20Permit`: replace `immutable` with `constant` for `_PERMIT_TYPEHASH` since the `keccak256` of string literals is treated specially and the hash is evaluated at compile time. ([#3196](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3196))
* `crosschain`: Add a new set of contracts for cross-chain applications. `CrossChainEnabled` is a base contract with instantiations for several chains and bridges, and `AccessControlCrossChain` is an extension of access control that allows cross-chain operation.

### Breaking changes

Expand Down
44 changes: 44 additions & 0 deletions contracts/access/AccessControlCrossChain.sol
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "./AccessControl.sol";
import "../crosschain/CrossChainEnabled.sol";

/**
* @dev An extension to {AccessControl} with support for cross-chain access management.
* For each role, is extension implements an equivalent "aliased" role that is used for
* restricting calls originating from other chains.
*
* For example, if a function `myFunction` is protected by `onlyRole(SOME_ROLE)`, and
* if an address `x` has role `SOME_ROLE`, it would be able to call `myFunction` directly.
* A wallet or contract at the same address on another chain would however not be able
* to call this function. In order to do so, it would require to have the role
* `_crossChainRoleAlias(SOME_ROLE)`.
*
* This aliasing is required to protect against multiple contracts living at the same
* address on different chains but controlled by conflicting entities.
*
* _Available since v4.6._
*/
abstract contract AccessControlCrossChain is AccessControl, CrossChainEnabled {
bytes32 public constant CROSSCHAIN_ALIAS = keccak256("CROSSCHAIN_ALIAS");
frangio marked this conversation as resolved.
Show resolved Hide resolved

/**
* @dev See {AccessControl-_checkRole}.
*/
function _checkRole(bytes32 role) internal view virtual override {
if (_isCrossChain()) {
_checkRole(_crossChainRoleAlias(role), _crossChainSender());
} else {
super._checkRole(role);
}
}

/**
* @dev Returns the aliased role corresponding to `role`.
*/
function _crossChainRoleAlias(bytes32 role) internal pure virtual returns (bytes32) {
return role ^ CROSSCHAIN_ALIAS;
}
}
2 changes: 2 additions & 0 deletions contracts/access/README.adoc
Expand Up @@ -16,6 +16,8 @@ This directory provides ways to restrict who can access the functions of a contr

{{AccessControl}}

{{AccessControlCrossChain}}

{{IAccessControlEnumerable}}

{{AccessControlEnumerable}}
53 changes: 53 additions & 0 deletions contracts/crosschain/CrossChainEnabled.sol
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "./errors.sol";

/**
* @dev Provides information for building cross-chain aware contracts. This
* abstract contract provides accessors and modifiers to control the execution
* flow when receiving cross-chain messages.
*
* Actual implementations of cross-chain aware contracts, which are based on
* this abstraction, will have to inherit from a bridge-specific
* specialization. Such specializations are provided under
* `crosschain/<chain>/CrossChainEnabled<chain>.sol`.
*
* _Available since v4.6._
*/
abstract contract CrossChainEnabled {
/**
* @dev Throws if the current function call is not the result of a
* cross-chain execution.
*/
modifier onlyCrossChain() {
if (!_isCrossChain()) revert NotCrossChainCall();
_;
}

/**
* @dev Throws if the current function call is not the result of a
* cross-chain execution initiated by `account`.
*/
modifier onlyCrossChainSender(address expected) {
address actual = _crossChainSender();
if (expected != actual) revert InvalidCrossChainSender(actual, expected);
_;
}

/**
* @dev Returns whether the current function call is the result of a
* cross-chain message.
*/
function _isCrossChain() internal view virtual returns (bool);

/**
* @dev Returns the address of the sender of the cross-chain message that
* triggered the current function call.
*
* IMPORTANT: Should revert with `NotCrossChainCall` if the current function
* call is not the result of a cross-chain message.
*/
function _crossChainSender() internal view virtual returns (address);
}
34 changes: 34 additions & 0 deletions contracts/crosschain/README.adoc
@@ -0,0 +1,34 @@
= Cross Chain Awareness

[.readme-notice]
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/crosschain

This directory provides building blocks to improve cross-chain awareness of smart contracts.

- {CrossChainEnabled} is an abstraction that contains accessors and modifiers to control the execution flow when receiving cross-chain messages.

== CrossChainEnabled specializations

The following specializations of {CrossChainEnabled} provide implementations of the {CrossChainEnabled} abstraction for specific bridges. This can be used to complexe cross-chain aware components such as {AccessControlCrossChain}.

{{CrossChainEnabledAMB}}

{{CrossChainEnabledArbitrumL1}}

{{CrossChainEnabledArbitrumL2}}

{{CrossChainEnabledOptimism}}

{{CrossChainEnabledPolygonChild}}

== Libraries for cross-chain

In addition to the {CrossChainEnable} abstraction, cross-chain awareness is also available through libraries. These libraries can be used to build complex designs such as contracts with the ability to interact with multiple bridges.

{{LibAMB}}

{{LibArbitrumL1}}

{{LibArbitrumL2}}

{{LibOptimism}}
47 changes: 47 additions & 0 deletions contracts/crosschain/amb/CrossChainEnabledAMB.sol
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "../CrossChainEnabled.sol";
import "./LibAMB.sol";

/**
* @dev [AMB](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge)
* specialization or the {CrossChainEnabled} abstraction.
*
* As of february 2020, AMB bridges are available between the following chains:
* - [ETH <> xDai](https://docs.tokenbridge.net/eth-xdai-amb-bridge/about-the-eth-xdai-amb)
* - [ETH <> qDai](https://docs.tokenbridge.net/eth-qdai-bridge/about-the-eth-qdai-amb)
* - [ETH <> ETC](https://docs.tokenbridge.net/eth-etc-amb-bridge/about-the-eth-etc-amb)
* - [ETH <> BSC](https://docs.tokenbridge.net/eth-bsc-amb/about-the-eth-bsc-amb)
* - [ETH <> POA](https://docs.tokenbridge.net/eth-poa-amb-bridge/about-the-eth-poa-amb)
* - [BSC <> xDai](https://docs.tokenbridge.net/bsc-xdai-amb/about-the-bsc-xdai-amb)
* - [POA <> xDai](https://docs.tokenbridge.net/poa-xdai-amb/about-the-poa-xdai-amb)
* - [Rinkeby <> xDai](https://docs.tokenbridge.net/rinkeby-xdai-amb-bridge/about-the-rinkeby-xdai-amb)
* - [Kovan <> Sokol](https://docs.tokenbridge.net/kovan-sokol-amb-bridge/about-the-kovan-sokol-amb)
*
* _Available since v4.6._
*/
contract CrossChainEnabledAMB is CrossChainEnabled {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable _bridge;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address bridge) {
_bridge = bridge;
}

/**
* @dev see {CrossChainEnabled-_isCrossChain}
*/
function _isCrossChain() internal view virtual override returns (bool) {
return LibAMB.isCrossChain(_bridge);
}

/**
* @dev see {CrossChainEnabled-_crossChainSender}
*/
function _crossChainSender() internal view virtual override onlyCrossChain returns (address) {
return LibAMB.crossChainSender(_bridge);
}
}
34 changes: 34 additions & 0 deletions contracts/crosschain/amb/LibAMB.sol
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import {IAMB as AMB_Bridge} from "../../vendor/amb/IAMB.sol";
import "../errors.sol";

/**
* @dev Primitives for cross-chain aware contracts using the
* [AMB](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge)
* family of bridges.
*/
library LibAMB {
/**
* @dev Returns whether the current function call is the result of a
* cross-chain message relayed by `bridge`.
*/
function isCrossChain(address bridge) internal view returns (bool) {
return msg.sender == bridge;
}

/**
* @dev Returns the address of the sender that triggered the current
* cross-chain message through `bridge`.
*
* NOTE: {isCrossChain} should be checked before trying to recover the
* sender, as it will revert with `NotCrossChainCall` if the current
* function call is not the result of a cross-chain message.
*/
function crossChainSender(address bridge) internal view returns (address) {
if (!isCrossChain(bridge)) revert NotCrossChainCall();
return AMB_Bridge(bridge).messageSender();
frangio marked this conversation as resolved.
Show resolved Hide resolved
}
}
43 changes: 43 additions & 0 deletions contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "../CrossChainEnabled.sol";
import "./LibArbitrumL1.sol";

/**
* @dev [Arbitrum](https://arbitrum.io/) specialization or the
* {CrossChainEnabled} abstraction the L1 side (mainnet).
*
* This version should only be deployed on L1 to process cross-chain messages
* originating from L2. For the other side, use {CrossChainEnabledArbitrumL2}.
*
* The inbox contract is provided and maintained by the arbitrum team. You can
* find the address of this contract on the rinkeby testnet in
* [Arbitrum's developer documentation](https://developer.offchainlabs.com/docs/public_testnet#l1).
*
* _Available since v4.6._
*/
abstract contract CrossChainEnabledArbitrumL1 is CrossChainEnabled {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable _inbox;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address inbox) {
_inbox = inbox;
}

/**
* @dev see {CrossChainEnabled-_isCrossChain}
*/
function _isCrossChain() internal view virtual override returns (bool) {
return LibArbitrumL1.isCrossChain(_inbox);
}

/**
* @dev see {CrossChainEnabled-_crossChainSender}
*/
function _crossChainSender() internal view virtual override onlyCrossChain returns (address) {
return LibArbitrumL1.crossChainSender(_inbox);
}
}
34 changes: 34 additions & 0 deletions contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "../CrossChainEnabled.sol";
import "./LibArbitrumL2.sol";

/**
* @dev [Arbitrum](https://arbitrum.io/) specialization or the
* {CrossChainEnabled} abstraction the L2 side (arbitrum).
*
* This version should only be deployed on L2 to process cross-chain messages
* originating from L1. For the other side, use {CrossChainEnabledArbitrumL1}.
*
* Arbitrum L2 includes the `ArbSys` contract at a fixed address. Therefore,
* this specialization of {CrossChainEnabled} does not include a constructor.
*
* _Available since v4.6._
*/
abstract contract CrossChainEnabledArbitrumL2 is CrossChainEnabled {
/**
* @dev see {CrossChainEnabled-_isCrossChain}
*/
function _isCrossChain() internal view virtual override returns (bool) {
return LibArbitrumL2.isCrossChain(LibArbitrumL2.ARBSYS);
}

/**
* @dev see {CrossChainEnabled-_crossChainSender}
*/
function _crossChainSender() internal view virtual override onlyCrossChain returns (address) {
return LibArbitrumL2.crossChainSender(LibArbitrumL2.ARBSYS);
}
}
43 changes: 43 additions & 0 deletions contracts/crosschain/arbitrum/LibArbitrumL1.sol
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import {IBridge as ArbitrumL1_Bridge} from "../../vendor/arbitrum/IBridge.sol";
import {IInbox as ArbitrumL1_Inbox} from "../../vendor/arbitrum/IInbox.sol";
import {IOutbox as ArbitrumL1_Outbox} from "../../vendor/arbitrum/IOutbox.sol";
import "../errors.sol";

/**
* @dev Primitives for cross-chain aware contracts for
* [Arbitrum](https://arbitrum.io/).
*
* This version should only be used on L1 to process cross-chain messages
* originating from L2. For the other side, use {LibArbitrumL2}.
*/
library LibArbitrumL1 {
/**
* @dev Returns whether the current function call is the result of a
* cross-chain message relayed by the bridge attached to `inbox`.
*/
function isCrossChain(address inbox) internal view returns (bool) {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
return msg.sender == ArbitrumL1_Inbox(inbox).bridge();
}

/**
* @dev Returns the address of the sender that triggered the current
* cross-chain message through the bridge attached to `inbox`.
*
* NOTE: {isCrossChain} should be checked before trying to recover the
* sender, as it will revert with `NotCrossChainCall` if the current
* function call is not the result of a cross-chain message.
*/
function crossChainSender(address inbox) internal view returns (address) {
if (!isCrossChain(inbox)) revert NotCrossChainCall();

address sender = ArbitrumL1_Outbox(ArbitrumL1_Bridge(ArbitrumL1_Inbox(inbox).bridge()).activeOutbox())
.l2ToL1Sender();
require(sender != address(0), "LibArbitrumL1: system messages without sender");

return sender;
}
}
42 changes: 42 additions & 0 deletions contracts/crosschain/arbitrum/LibArbitrumL2.sol
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import {IArbSys as ArbitrumL2_Bridge} from "../../vendor/arbitrum/IArbSys.sol";
import "../errors.sol";

/**
* @dev Primitives for cross-chain aware contracts for
* [Arbitrum](https://arbitrum.io/).
*
* This version should only be used on L2 to process cross-chain messages
* originating from L1. For the other side, use {LibArbitrumL1}.
*/
library LibArbitrumL2 {
/**
* @dev Returns whether the current function call is the result of a
* cross-chain message relayed by `arbsys`.
*/
address public constant ARBSYS = 0x0000000000000000000000000000000000000064;

function isCrossChain(address arbsys) internal view returns (bool) {
return ArbitrumL2_Bridge(arbsys).isTopLevelCall();
}

/**
* @dev Returns the address of the sender that triggered the current
* cross-chain message through `arbsys`.
*
* NOTE: {isCrossChain} should be checked before trying to recover the
* sender, as it will revert with `NotCrossChainCall` if the current
* function call is not the result of a cross-chain message.
*/
function crossChainSender(address arbsys) internal view returns (address) {
if (!isCrossChain(arbsys)) revert NotCrossChainCall();

return
ArbitrumL2_Bridge(arbsys).wasMyCallersAddressAliased()
? ArbitrumL2_Bridge(arbsys).myCallersAddressWithoutAliasing()
: msg.sender;
}
}