Skip to content

Commit 668a648

Browse files
Amxxfrangio
andauthored
Add utilities for CrossChain messaging (OpenZeppelin#3183)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
1 parent 02fcc75 commit 668a648

36 files changed

+1477
-73
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* `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. ([#3183](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3183))
56
* `AccessControl`: add a virtual `_checkRole(bytes32)` function that can be overridden to alter the `onlyRole` modifier behavior. ([#3137](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3137))
67
* `EnumerableMap`: add new `AddressToUintMap` map type. ([#3150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3150))
78
* `EnumerableMap`: add new `Bytes32ToBytes32Map` map type. ([#3192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3192))
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.4;
4+
5+
import "./AccessControl.sol";
6+
import "../crosschain/CrossChainEnabled.sol";
7+
8+
/**
9+
* @dev An extension to {AccessControl} with support for cross-chain access management.
10+
* For each role, is extension implements an equivalent "aliased" role that is used for
11+
* restricting calls originating from other chains.
12+
*
13+
* For example, if a function `myFunction` is protected by `onlyRole(SOME_ROLE)`, and
14+
* if an address `x` has role `SOME_ROLE`, it would be able to call `myFunction` directly.
15+
* A wallet or contract at the same address on another chain would however not be able
16+
* to call this function. In order to do so, it would require to have the role
17+
* `_crossChainRoleAlias(SOME_ROLE)`.
18+
*
19+
* This aliasing is required to protect against multiple contracts living at the same
20+
* address on different chains but controlled by conflicting entities.
21+
*
22+
* _Available since v4.6._
23+
*/
24+
abstract contract AccessControlCrossChain is AccessControl, CrossChainEnabled {
25+
bytes32 public constant CROSSCHAIN_ALIAS = keccak256("CROSSCHAIN_ALIAS");
26+
27+
/**
28+
* @dev See {AccessControl-_checkRole}.
29+
*/
30+
function _checkRole(bytes32 role) internal view virtual override {
31+
if (_isCrossChain()) {
32+
_checkRole(_crossChainRoleAlias(role), _crossChainSender());
33+
} else {
34+
super._checkRole(role);
35+
}
36+
}
37+
38+
/**
39+
* @dev Returns the aliased role corresponding to `role`.
40+
*/
41+
function _crossChainRoleAlias(bytes32 role) internal pure virtual returns (bytes32) {
42+
return role ^ CROSSCHAIN_ALIAS;
43+
}
44+
}

contracts/access/README.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ This directory provides ways to restrict who can access the functions of a contr
1616

1717
{{AccessControl}}
1818

19+
{{AccessControlCrossChain}}
20+
1921
{{IAccessControlEnumerable}}
2022

2123
{{AccessControlEnumerable}}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.4;
4+
5+
import "./errors.sol";
6+
7+
/**
8+
* @dev Provides information for building cross-chain aware contracts. This
9+
* abstract contract provides accessors and modifiers to control the execution
10+
* flow when receiving cross-chain messages.
11+
*
12+
* Actual implementations of cross-chain aware contracts, which are based on
13+
* this abstraction, will have to inherit from a bridge-specific
14+
* specialization. Such specializations are provided under
15+
* `crosschain/<chain>/CrossChainEnabled<chain>.sol`.
16+
*
17+
* _Available since v4.6._
18+
*/
19+
abstract contract CrossChainEnabled {
20+
/**
21+
* @dev Throws if the current function call is not the result of a
22+
* cross-chain execution.
23+
*/
24+
modifier onlyCrossChain() {
25+
if (!_isCrossChain()) revert NotCrossChainCall();
26+
_;
27+
}
28+
29+
/**
30+
* @dev Throws if the current function call is not the result of a
31+
* cross-chain execution initiated by `account`.
32+
*/
33+
modifier onlyCrossChainSender(address expected) {
34+
address actual = _crossChainSender();
35+
if (expected != actual) revert InvalidCrossChainSender(actual, expected);
36+
_;
37+
}
38+
39+
/**
40+
* @dev Returns whether the current function call is the result of a
41+
* cross-chain message.
42+
*/
43+
function _isCrossChain() internal view virtual returns (bool);
44+
45+
/**
46+
* @dev Returns the address of the sender of the cross-chain message that
47+
* triggered the current function call.
48+
*
49+
* IMPORTANT: Should revert with `NotCrossChainCall` if the current function
50+
* call is not the result of a cross-chain message.
51+
*/
52+
function _crossChainSender() internal view virtual returns (address);
53+
}

contracts/crosschain/README.adoc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
= Cross Chain Awareness
2+
3+
[.readme-notice]
4+
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/crosschain
5+
6+
This directory provides building blocks to improve cross-chain awareness of smart contracts.
7+
8+
- {CrossChainEnabled} is an abstraction that contains accessors and modifiers to control the execution flow when receiving cross-chain messages.
9+
10+
== CrossChainEnabled specializations
11+
12+
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}.
13+
14+
{{CrossChainEnabledAMB}}
15+
16+
{{CrossChainEnabledArbitrumL1}}
17+
18+
{{CrossChainEnabledArbitrumL2}}
19+
20+
{{CrossChainEnabledOptimism}}
21+
22+
{{CrossChainEnabledPolygonChild}}
23+
24+
== Libraries for cross-chain
25+
26+
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.
27+
28+
{{LibAMB}}
29+
30+
{{LibArbitrumL1}}
31+
32+
{{LibArbitrumL2}}
33+
34+
{{LibOptimism}}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.4;
4+
5+
import "../CrossChainEnabled.sol";
6+
import "./LibAMB.sol";
7+
8+
/**
9+
* @dev [AMB](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge)
10+
* specialization or the {CrossChainEnabled} abstraction.
11+
*
12+
* As of february 2020, AMB bridges are available between the following chains:
13+
* - [ETH <> xDai](https://docs.tokenbridge.net/eth-xdai-amb-bridge/about-the-eth-xdai-amb)
14+
* - [ETH <> qDai](https://docs.tokenbridge.net/eth-qdai-bridge/about-the-eth-qdai-amb)
15+
* - [ETH <> ETC](https://docs.tokenbridge.net/eth-etc-amb-bridge/about-the-eth-etc-amb)
16+
* - [ETH <> BSC](https://docs.tokenbridge.net/eth-bsc-amb/about-the-eth-bsc-amb)
17+
* - [ETH <> POA](https://docs.tokenbridge.net/eth-poa-amb-bridge/about-the-eth-poa-amb)
18+
* - [BSC <> xDai](https://docs.tokenbridge.net/bsc-xdai-amb/about-the-bsc-xdai-amb)
19+
* - [POA <> xDai](https://docs.tokenbridge.net/poa-xdai-amb/about-the-poa-xdai-amb)
20+
* - [Rinkeby <> xDai](https://docs.tokenbridge.net/rinkeby-xdai-amb-bridge/about-the-rinkeby-xdai-amb)
21+
* - [Kovan <> Sokol](https://docs.tokenbridge.net/kovan-sokol-amb-bridge/about-the-kovan-sokol-amb)
22+
*
23+
* _Available since v4.6._
24+
*/
25+
contract CrossChainEnabledAMB is CrossChainEnabled {
26+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
27+
address private immutable _bridge;
28+
29+
/// @custom:oz-upgrades-unsafe-allow constructor
30+
constructor(address bridge) {
31+
_bridge = bridge;
32+
}
33+
34+
/**
35+
* @dev see {CrossChainEnabled-_isCrossChain}
36+
*/
37+
function _isCrossChain() internal view virtual override returns (bool) {
38+
return LibAMB.isCrossChain(_bridge);
39+
}
40+
41+
/**
42+
* @dev see {CrossChainEnabled-_crossChainSender}
43+
*/
44+
function _crossChainSender() internal view virtual override onlyCrossChain returns (address) {
45+
return LibAMB.crossChainSender(_bridge);
46+
}
47+
}

contracts/crosschain/amb/LibAMB.sol

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.4;
4+
5+
import {IAMB as AMB_Bridge} from "../../vendor/amb/IAMB.sol";
6+
import "../errors.sol";
7+
8+
/**
9+
* @dev Primitives for cross-chain aware contracts using the
10+
* [AMB](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge)
11+
* family of bridges.
12+
*/
13+
library LibAMB {
14+
/**
15+
* @dev Returns whether the current function call is the result of a
16+
* cross-chain message relayed by `bridge`.
17+
*/
18+
function isCrossChain(address bridge) internal view returns (bool) {
19+
return msg.sender == bridge;
20+
}
21+
22+
/**
23+
* @dev Returns the address of the sender that triggered the current
24+
* cross-chain message through `bridge`.
25+
*
26+
* NOTE: {isCrossChain} should be checked before trying to recover the
27+
* sender, as it will revert with `NotCrossChainCall` if the current
28+
* function call is not the result of a cross-chain message.
29+
*/
30+
function crossChainSender(address bridge) internal view returns (address) {
31+
if (!isCrossChain(bridge)) revert NotCrossChainCall();
32+
return AMB_Bridge(bridge).messageSender();
33+
}
34+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.4;
4+
5+
import "../CrossChainEnabled.sol";
6+
import "./LibArbitrumL1.sol";
7+
8+
/**
9+
* @dev [Arbitrum](https://arbitrum.io/) specialization or the
10+
* {CrossChainEnabled} abstraction the L1 side (mainnet).
11+
*
12+
* This version should only be deployed on L1 to process cross-chain messages
13+
* originating from L2. For the other side, use {CrossChainEnabledArbitrumL2}.
14+
*
15+
* The bridge contract is provided and maintained by the arbitrum team. You can
16+
* find the address of this contract on the rinkeby testnet in
17+
* [Arbitrum's developer documentation](https://developer.offchainlabs.com/docs/useful_addresses).
18+
*
19+
* _Available since v4.6._
20+
*/
21+
abstract contract CrossChainEnabledArbitrumL1 is CrossChainEnabled {
22+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
23+
address private immutable _bridge;
24+
25+
/// @custom:oz-upgrades-unsafe-allow constructor
26+
constructor(address bridge) {
27+
_bridge = bridge;
28+
}
29+
30+
/**
31+
* @dev see {CrossChainEnabled-_isCrossChain}
32+
*/
33+
function _isCrossChain() internal view virtual override returns (bool) {
34+
return LibArbitrumL1.isCrossChain(_bridge);
35+
}
36+
37+
/**
38+
* @dev see {CrossChainEnabled-_crossChainSender}
39+
*/
40+
function _crossChainSender() internal view virtual override onlyCrossChain returns (address) {
41+
return LibArbitrumL1.crossChainSender(_bridge);
42+
}
43+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.4;
4+
5+
import "../CrossChainEnabled.sol";
6+
import "./LibArbitrumL2.sol";
7+
8+
/**
9+
* @dev [Arbitrum](https://arbitrum.io/) specialization or the
10+
* {CrossChainEnabled} abstraction the L2 side (arbitrum).
11+
*
12+
* This version should only be deployed on L2 to process cross-chain messages
13+
* originating from L1. For the other side, use {CrossChainEnabledArbitrumL1}.
14+
*
15+
* Arbitrum L2 includes the `ArbSys` contract at a fixed address. Therefore,
16+
* this specialization of {CrossChainEnabled} does not include a constructor.
17+
*
18+
* _Available since v4.6._
19+
*/
20+
abstract contract CrossChainEnabledArbitrumL2 is CrossChainEnabled {
21+
/**
22+
* @dev see {CrossChainEnabled-_isCrossChain}
23+
*/
24+
function _isCrossChain() internal view virtual override returns (bool) {
25+
return LibArbitrumL2.isCrossChain(LibArbitrumL2.ARBSYS);
26+
}
27+
28+
/**
29+
* @dev see {CrossChainEnabled-_crossChainSender}
30+
*/
31+
function _crossChainSender() internal view virtual override onlyCrossChain returns (address) {
32+
return LibArbitrumL2.crossChainSender(LibArbitrumL2.ARBSYS);
33+
}
34+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.4;
4+
5+
import {IBridge as ArbitrumL1_Bridge} from "../../vendor/arbitrum/IBridge.sol";
6+
import {IInbox as ArbitrumL1_Inbox} from "../../vendor/arbitrum/IInbox.sol";
7+
import {IOutbox as ArbitrumL1_Outbox} from "../../vendor/arbitrum/IOutbox.sol";
8+
import "../errors.sol";
9+
10+
/**
11+
* @dev Primitives for cross-chain aware contracts for
12+
* [Arbitrum](https://arbitrum.io/).
13+
*
14+
* This version should only be used on L1 to process cross-chain messages
15+
* originating from L2. For the other side, use {LibArbitrumL2}.
16+
*/
17+
library LibArbitrumL1 {
18+
/**
19+
* @dev Returns whether the current function call is the result of a
20+
* cross-chain message relayed by the `bridge`.
21+
*/
22+
function isCrossChain(address bridge) internal view returns (bool) {
23+
return msg.sender == bridge;
24+
}
25+
26+
/**
27+
* @dev Returns the address of the sender that triggered the current
28+
* cross-chain message through the `bridge`.
29+
*
30+
* NOTE: {isCrossChain} should be checked before trying to recover the
31+
* sender, as it will revert with `NotCrossChainCall` if the current
32+
* function call is not the result of a cross-chain message.
33+
*/
34+
function crossChainSender(address bridge) internal view returns (address) {
35+
if (!isCrossChain(bridge)) revert NotCrossChainCall();
36+
37+
address sender = ArbitrumL1_Outbox(ArbitrumL1_Bridge(bridge).activeOutbox()).l2ToL1Sender();
38+
require(sender != address(0), "LibArbitrumL1: system messages without sender");
39+
40+
return sender;
41+
}
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.4;
4+
5+
import {IArbSys as ArbitrumL2_Bridge} from "../../vendor/arbitrum/IArbSys.sol";
6+
import "../errors.sol";
7+
8+
/**
9+
* @dev Primitives for cross-chain aware contracts for
10+
* [Arbitrum](https://arbitrum.io/).
11+
*
12+
* This version should only be used on L2 to process cross-chain messages
13+
* originating from L1. For the other side, use {LibArbitrumL1}.
14+
*/
15+
library LibArbitrumL2 {
16+
/**
17+
* @dev Returns whether the current function call is the result of a
18+
* cross-chain message relayed by `arbsys`.
19+
*/
20+
address public constant ARBSYS = 0x0000000000000000000000000000000000000064;
21+
22+
function isCrossChain(address arbsys) internal view returns (bool) {
23+
return ArbitrumL2_Bridge(arbsys).isTopLevelCall();
24+
}
25+
26+
/**
27+
* @dev Returns the address of the sender that triggered the current
28+
* cross-chain message through `arbsys`.
29+
*
30+
* NOTE: {isCrossChain} should be checked before trying to recover the
31+
* sender, as it will revert with `NotCrossChainCall` if the current
32+
* function call is not the result of a cross-chain message.
33+
*/
34+
function crossChainSender(address arbsys) internal view returns (address) {
35+
if (!isCrossChain(arbsys)) revert NotCrossChainCall();
36+
37+
return
38+
ArbitrumL2_Bridge(arbsys).wasMyCallersAddressAliased()
39+
? ArbitrumL2_Bridge(arbsys).myCallersAddressWithoutAliasing()
40+
: msg.sender;
41+
}
42+
}

0 commit comments

Comments
 (0)