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 5 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
6 changes: 3 additions & 3 deletions contracts/access/AccessControlCrossChain.sol
Expand Up @@ -14,7 +14,7 @@ import "../crosschain/CrossChainEnabled.sol";
* 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
* `_aliasRole(SOME_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.
Expand All @@ -29,7 +29,7 @@ abstract contract AccessControlCrossChain is AccessControl, CrossChainEnabled {
*/
function _checkRole(bytes32 role) internal view virtual override {
if (_isCrossChain()) {
_checkRole(_aliasRole(role), _crossChainSender());
_checkRole(_crossChainRoleAlias(role), _crossChainSender());
} else {
super._checkRole(role);
}
Expand All @@ -38,7 +38,7 @@ abstract contract AccessControlCrossChain is AccessControl, CrossChainEnabled {
/**
* @dev Returns the aliased role corresponding to `role`.
*/
function _aliasRole(bytes32 role) internal pure virtual returns (bytes32) {
function _crossChainRoleAlias(bytes32 role) internal pure virtual returns (bytes32) {
return role ^ CROSSCHAIN_ALIAS;
}
}
12 changes: 6 additions & 6 deletions contracts/crosschain/CrossChainEnabled.sol
Expand Up @@ -16,7 +16,7 @@ pragma solidity ^0.8.0;
*/
abstract contract CrossChainEnabled {
error NotCrossChainCall();
error InvalidCrossChainSender(address sender, address expected);
error InvalidCrossChainSender(address actual, address expected);

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

Expand All @@ -47,8 +47,8 @@ abstract contract CrossChainEnabled {
* @dev Returns the address of the sender of the cross-chain message that
* triggered the current function call.
*
* NOTE: Should revert if the current function call is not the result of a
* cross-chain message.
* 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);
}
2 changes: 2 additions & 0 deletions contracts/crosschain/amb/CrossChainEnabledAMB.sol
Expand Up @@ -23,8 +23,10 @@ import "./LibAMB.sol";
* _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;
}
Expand Down
2 changes: 2 additions & 0 deletions contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol
Expand Up @@ -19,8 +19,10 @@ import "./LibArbitrumL1.sol";
* _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;
}
Expand Down
4 changes: 2 additions & 2 deletions contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol
Expand Up @@ -22,13 +22,13 @@ abstract contract CrossChainEnabledArbitrumL2 is CrossChainEnabled {
* @dev see {CrossChainEnabled-_isCrossChain}
*/
function _isCrossChain() internal view virtual override returns (bool) {
return LibArbitrumL2.isCrossChain(address(LibArbitrumL2.ARBSYS));
return LibArbitrumL2.isCrossChain(LibArbitrumL2.ARBSYS);
}

/**
* @dev see {CrossChainEnabled-_crossChainSender}
*/
function _crossChainSender() internal view virtual override onlyCrossChain returns (address) {
return LibArbitrumL2.crossChainSender(address(LibArbitrumL2.ARBSYS));
return LibArbitrumL2.crossChainSender(LibArbitrumL2.ARBSYS);
}
}
9 changes: 6 additions & 3 deletions contracts/crosschain/arbitrum/LibArbitrumL1.sol
Expand Up @@ -16,20 +16,23 @@ import {IOutbox as ArbitrumL1_Outbox} from "../../vendor/arbitrum/IOutbox.sol";
library LibArbitrumL1 {
/**
* @dev Returns whether the current function call is the result of a
* cross-chain message relayed by `bridge`.
* 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 `bridge`.
* cross-chain message through the bridge attached to `inbox`.
*
* NOTE: {isCrossChain} should be checked before trying to recover the
* sender.
*/
function crossChainSender(address inbox) internal view returns (address) {
return ArbitrumL1_Outbox(ArbitrumL1_Bridge(ArbitrumL1_Inbox(inbox).bridge()).activeOutbox()).l2ToL1Sender();
address sender = ArbitrumL1_Outbox(ArbitrumL1_Bridge(ArbitrumL1_Inbox(inbox).bridge()).activeOutbox())
.l2ToL1Sender();
require(sender != address(0), "LibArbitrumL1: system messages without sender");
return sender;
}
}
16 changes: 8 additions & 8 deletions contracts/crosschain/arbitrum/LibArbitrumL2.sol
Expand Up @@ -14,25 +14,25 @@ import {IArbSys as ArbitrumL2_Bridge} from "../../vendor/arbitrum/IArbSys.sol";
library LibArbitrumL2 {
/**
* @dev Returns whether the current function call is the result of a
* cross-chain message relayed by `bridge`.
* cross-chain message relayed by `arbsys`.
*/
ArbitrumL2_Bridge public constant ARBSYS = ArbitrumL2_Bridge(0x0000000000000000000000000000000000000064);
address public constant ARBSYS = 0x0000000000000000000000000000000000000064;

function isCrossChain(address bridge) internal view returns (bool) {
return ArbitrumL2_Bridge(bridge).isTopLevelCall();
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 `bridge`.
* cross-chain message through `arbsys`.
*
* NOTE: {isCrossChain} should be checked before trying to recover the
* sender.
*/
function crossChainSender(address bridge) internal view returns (address) {
function crossChainSender(address arbsys) internal view returns (address) {
return
ArbitrumL2_Bridge(bridge).wasMyCallersAddressAliased()
? ArbitrumL2_Bridge(bridge).myCallersAddressWithoutAliasing()
ArbitrumL2_Bridge(arbsys).wasMyCallersAddressAliased()
? ArbitrumL2_Bridge(arbsys).myCallersAddressWithoutAliasing()
: msg.sender;
}
}
14 changes: 8 additions & 6 deletions contracts/crosschain/optimism/CrossChainEnabledOptimism.sol
Expand Up @@ -9,30 +9,32 @@ import "./LibOptimism.sol";
* @dev [Optimism](https://www.optimism.io/) specialization or the
* {CrossChainEnabled} abstraction.
*
* The bridge (`CrossDomainMessenger`) contract is provided and maintained by
* The messenger (`CrossDomainMessenger`) contract is provided and maintained by
* the optimism team. You can find the address of this contract on mainnet and
* kovan in the [deployments section of Optimism monorepo](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments).
*
* _Available since v4.6._
*/
abstract contract CrossChainEnabledOptimism is CrossChainEnabled {
address private immutable _bridge;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable _messenger;

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

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

/**
* @dev see {CrossChainEnabled-_crossChainSender}
*/
function _crossChainSender() internal view virtual override onlyCrossChain returns (address) {
return LibOptimism.crossChainSender(_bridge);
return LibOptimism.crossChainSender(_messenger);
}
}
17 changes: 9 additions & 8 deletions contracts/crosschain/optimism/LibOptimism.sol
Expand Up @@ -5,26 +5,27 @@ pragma solidity ^0.8.0;
import {ICrossDomainMessenger as Optimism_Bridge} from "../../vendor/optimism/ICrossDomainMessenger.sol";

/**
* @dev Primitives for cross-chain aware contracts for
* [Optimism](https://www.optimism.io/).
* @dev Primitives for cross-chain aware contracts for [Optimism](https://www.optimism.io/).
* See the [documentation](https://community.optimism.io/docs/developers/bridge/messaging/#accessing-msg-sender)
* for the functionality used here.
*/
library LibOptimism {
Amxx marked this conversation as resolved.
Show resolved Hide resolved
/**
* @dev Returns whether the current function call is the result of a
* cross-chain message relayed by `bridge`.
* cross-chain message relayed by `messenger`.
*/
function isCrossChain(address bridge) internal view returns (bool) {
return msg.sender == bridge;
function isCrossChain(address messenger) internal view returns (bool) {
return msg.sender == messenger;
}

/**
* @dev Returns the address of the sender that triggered the current
* cross-chain message through `bridge`.
* cross-chain message through `messenger`.
*
* NOTE: {isCrossChain} should be checked before trying to recover the
* sender.
*/
function crossChainSender(address bridge) internal view returns (address) {
return Optimism_Bridge(bridge).xDomainMessageSender();
function crossChainSender(address messenger) internal view returns (address) {
return Optimism_Bridge(messenger).xDomainMessageSender();
}
}
Expand Up @@ -23,9 +23,11 @@ address constant DEFAULT_SENDER = 0x000000000000000000000000000000000000dEaD;
* _Available since v4.6._
*/
abstract contract CrossChainEnabledPolygonChild is IFxMessageProcessor, CrossChainEnabled, ReentrancyGuard {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable _fxChild;
address private _sender = DEFAULT_SENDER;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address fxChild) {
_fxChild = fxChild;
}
Expand Down
4 changes: 2 additions & 2 deletions contracts/mocks/AccessControlCrossChainMock.sol
Expand Up @@ -16,7 +16,7 @@ contract AccessControlCrossChainMock is AccessControlCrossChain, CrossChainEnabl

function senderProtected(bytes32 roleId) public onlyRole(roleId) {}

function aliasRole(bytes32 role) public pure virtual returns (bytes32) {
return _aliasRole(role);
function crossChainRoleAlias(bytes32 role) public pure virtual returns (bytes32) {
return _crossChainRoleAlias(role);
}
}
8 changes: 4 additions & 4 deletions test/access/AccessControlCrossChain.test.js
Expand Up @@ -5,7 +5,7 @@ const {
shouldBehaveLikeAccessControl,
} = require('./AccessControl.behavior.js');

const aliasRole = (role) => web3.utils.leftPad(
const crossChainRoleAlias = (role) => web3.utils.leftPad(
web3.utils.toHex(web3.utils.toBN(role).xor(web3.utils.toBN(web3.utils.soliditySha3('CROSSCHAIN_ALIAS')))),
64,
);
Expand All @@ -26,11 +26,11 @@ contract('AccessControl', function (accounts) {
describe('CrossChain enabled', function () {
beforeEach(async function () {
await this.accessControl.grantRole(ROLE, accounts[0], { from: accounts[0] });
await this.accessControl.grantRole(aliasRole(ROLE), accounts[1], { from: accounts[0] });
await this.accessControl.grantRole(crossChainRoleAlias(ROLE), accounts[1], { from: accounts[0] });
});

it('check alliassing', async function () {
expect(await this.accessControl.aliasRole(ROLE)).to.be.bignumber.equal(aliasRole(ROLE));
expect(await this.accessControl.crossChainRoleAlias(ROLE)).to.be.bignumber.equal(crossChainRoleAlias(ROLE));
});

it('Crosschain calls not authorized to non-aliased addresses', async function () {
Expand All @@ -41,7 +41,7 @@ contract('AccessControl', function (accounts) {
'senderProtected',
[ ROLE ],
),
`AccessControl: account ${accounts[0].toLowerCase()} is missing role ${aliasRole(ROLE)}`,
`AccessControl: account ${accounts[0].toLowerCase()} is missing role ${crossChainRoleAlias(ROLE)}`,
);
});

Expand Down
25 changes: 19 additions & 6 deletions test/crosschain/CrossChainEnabled.test.js
@@ -1,6 +1,18 @@
const { expectRevert } = require('@openzeppelin/test-helpers');
const { expect } = require('chai');
const CrossChainHelper = require('../helpers/crosschain');

/** Revert handler that supports custom errors. */
async function expectRevert (promise, reason) {
try {
await promise;
expect.fail('Expected promise to throw but it didn\'t');
} catch (error) {
if (reason) {
expect(error.message).to.include(reason);
}
}
}

function randomAddress () {
return web3.utils.toChecksumAddress(web3.utils.randomHex(20));
}
Expand All @@ -13,20 +25,21 @@ const CrossChainEnabledPolygonChildMock = artifacts.require('CrossChainEnabledPo

function shouldBehaveLikeReceiver (sender = randomAddress()) {
it('should reject same-chain calls', async function () {
await expectRevert.unspecified(
await expectRevert(
this.receiver.crossChainRestricted(),
); // TODO: check custom error
'NotCrossChainCall()',
);
});

it('should restrict to cross-chain call from a invalid sender', async function () {
await expectRevert.unspecified(CrossChainHelper.call(
await expectRevert(CrossChainHelper.call(
sender,
this.receiver,
'crossChainOwnerRestricted()',
)); // TODO: check custom error
));
Amxx marked this conversation as resolved.
Show resolved Hide resolved
});

it('should grant access to cross-chain call from a the owner', async function () {
it('should grant access to cross-chain call from the owner', async function () {
await CrossChainHelper.call(
await this.receiver.owner(),
this.receiver,
Expand Down