Add utilities for CrossChain messaging (#3183)
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
This commit is contained in:
59
test/access/AccessControlCrossChain.test.js
Normal file
59
test/access/AccessControlCrossChain.test.js
Normal file
@ -0,0 +1,59 @@
|
||||
const { expectRevert } = require('@openzeppelin/test-helpers');
|
||||
const { BridgeHelper } = require('../helpers/crosschain');
|
||||
|
||||
const {
|
||||
shouldBehaveLikeAccessControl,
|
||||
} = require('./AccessControl.behavior.js');
|
||||
|
||||
const crossChainRoleAlias = (role) => web3.utils.leftPad(
|
||||
web3.utils.toHex(web3.utils.toBN(role).xor(web3.utils.toBN(web3.utils.soliditySha3('CROSSCHAIN_ALIAS')))),
|
||||
64,
|
||||
);
|
||||
|
||||
const AccessControlCrossChainMock = artifacts.require('AccessControlCrossChainMock');
|
||||
|
||||
const ROLE = web3.utils.soliditySha3('ROLE');
|
||||
|
||||
contract('AccessControl', function (accounts) {
|
||||
before(async function () {
|
||||
this.bridge = await BridgeHelper.deploy();
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
this.accessControl = await AccessControlCrossChainMock.new({ from: accounts[0] });
|
||||
});
|
||||
|
||||
shouldBehaveLikeAccessControl('AccessControl', ...accounts);
|
||||
|
||||
describe('CrossChain enabled', function () {
|
||||
beforeEach(async function () {
|
||||
await this.accessControl.grantRole(ROLE, accounts[0], { from: accounts[0] });
|
||||
await this.accessControl.grantRole(crossChainRoleAlias(ROLE), accounts[1], { from: accounts[0] });
|
||||
});
|
||||
|
||||
it('check alliassing', async function () {
|
||||
expect(await this.accessControl.crossChainRoleAlias(ROLE)).to.be.bignumber.equal(crossChainRoleAlias(ROLE));
|
||||
});
|
||||
|
||||
it('Crosschain calls not authorized to non-aliased addresses', async function () {
|
||||
await expectRevert(
|
||||
this.bridge.call(
|
||||
accounts[0],
|
||||
this.accessControl,
|
||||
'senderProtected',
|
||||
[ ROLE ],
|
||||
),
|
||||
`AccessControl: account ${accounts[0].toLowerCase()} is missing role ${crossChainRoleAlias(ROLE)}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('Crosschain calls not authorized to non-aliased addresses', async function () {
|
||||
await this.bridge.call(
|
||||
accounts[1],
|
||||
this.accessControl,
|
||||
'senderProtected',
|
||||
[ ROLE ],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
83
test/crosschain/CrossChainEnabled.test.js
Normal file
83
test/crosschain/CrossChainEnabled.test.js
Normal file
@ -0,0 +1,83 @@
|
||||
const { BridgeHelper } = require('../helpers/crosschain');
|
||||
const { expectRevertCustomError } = require('../helpers/customError');
|
||||
|
||||
function randomAddress () {
|
||||
return web3.utils.toChecksumAddress(web3.utils.randomHex(20));
|
||||
}
|
||||
|
||||
const CrossChainEnabledAMBMock = artifacts.require('CrossChainEnabledAMBMock');
|
||||
const CrossChainEnabledArbitrumL1Mock = artifacts.require('CrossChainEnabledArbitrumL1Mock');
|
||||
const CrossChainEnabledArbitrumL2Mock = artifacts.require('CrossChainEnabledArbitrumL2Mock');
|
||||
const CrossChainEnabledOptimismMock = artifacts.require('CrossChainEnabledOptimismMock');
|
||||
const CrossChainEnabledPolygonChildMock = artifacts.require('CrossChainEnabledPolygonChildMock');
|
||||
|
||||
function shouldBehaveLikeReceiver (sender = randomAddress()) {
|
||||
it('should reject same-chain calls', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.receiver.crossChainRestricted(),
|
||||
'NotCrossChainCall()',
|
||||
);
|
||||
});
|
||||
|
||||
it('should restrict to cross-chain call from a invalid sender', async function () {
|
||||
await expectRevertCustomError(
|
||||
this.bridge.call(sender, this.receiver, 'crossChainOwnerRestricted()'),
|
||||
`InvalidCrossChainSender("${sender}", "${await this.receiver.owner()}")`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should grant access to cross-chain call from the owner', async function () {
|
||||
await this.bridge.call(
|
||||
await this.receiver.owner(),
|
||||
this.receiver,
|
||||
'crossChainOwnerRestricted()',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
contract('CrossChainEnabled', function () {
|
||||
describe('AMB', function () {
|
||||
beforeEach(async function () {
|
||||
this.bridge = await BridgeHelper.deploy('AMB');
|
||||
this.receiver = await CrossChainEnabledAMBMock.new(this.bridge.address);
|
||||
});
|
||||
|
||||
shouldBehaveLikeReceiver();
|
||||
});
|
||||
|
||||
describe('Arbitrum-L1', function () {
|
||||
beforeEach(async function () {
|
||||
this.bridge = await BridgeHelper.deploy('Arbitrum-L1');
|
||||
this.receiver = await CrossChainEnabledArbitrumL1Mock.new(this.bridge.address);
|
||||
});
|
||||
|
||||
shouldBehaveLikeReceiver();
|
||||
});
|
||||
|
||||
describe('Arbitrum-L2', function () {
|
||||
beforeEach(async function () {
|
||||
this.bridge = await BridgeHelper.deploy('Arbitrum-L2');
|
||||
this.receiver = await CrossChainEnabledArbitrumL2Mock.new(this.bridge.address);
|
||||
});
|
||||
|
||||
shouldBehaveLikeReceiver();
|
||||
});
|
||||
|
||||
describe('Optimism', function () {
|
||||
beforeEach(async function () {
|
||||
this.bridge = await BridgeHelper.deploy('Optimism');
|
||||
this.receiver = await CrossChainEnabledOptimismMock.new(this.bridge.address);
|
||||
});
|
||||
|
||||
shouldBehaveLikeReceiver();
|
||||
});
|
||||
|
||||
describe('Polygon-Child', function () {
|
||||
beforeEach(async function () {
|
||||
this.bridge = await BridgeHelper.deploy('Polygon-Child');
|
||||
this.receiver = await CrossChainEnabledPolygonChildMock.new(this.bridge.address);
|
||||
});
|
||||
|
||||
shouldBehaveLikeReceiver();
|
||||
});
|
||||
});
|
||||
63
test/helpers/crosschain.js
Normal file
63
test/helpers/crosschain.js
Normal file
@ -0,0 +1,63 @@
|
||||
const { promisify } = require('util');
|
||||
|
||||
const BridgeAMBMock = artifacts.require('BridgeAMBMock');
|
||||
const BridgeArbitrumL1Mock = artifacts.require('BridgeArbitrumL1Mock');
|
||||
const BridgeArbitrumL2Mock = artifacts.require('BridgeArbitrumL2Mock');
|
||||
const BridgeOptimismMock = artifacts.require('BridgeOptimismMock');
|
||||
const BridgePolygonChildMock = artifacts.require('BridgePolygonChildMock');
|
||||
|
||||
class BridgeHelper {
|
||||
static async deploy (type) {
|
||||
return new BridgeHelper(await deployBridge(type));
|
||||
}
|
||||
|
||||
constructor (bridge) {
|
||||
this.bridge = bridge;
|
||||
this.address = bridge.address;
|
||||
}
|
||||
|
||||
call (from, target, selector = undefined, args = []) {
|
||||
return this.bridge.relayAs(
|
||||
target.address || target,
|
||||
selector
|
||||
? target.contract.methods[selector](...args).encodeABI()
|
||||
: '0x',
|
||||
from,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function deployBridge (type = 'Arbitrum-L2') {
|
||||
switch (type) {
|
||||
case 'AMB':
|
||||
return BridgeAMBMock.new();
|
||||
|
||||
case 'Arbitrum-L1':
|
||||
return BridgeArbitrumL1Mock.new();
|
||||
|
||||
case 'Arbitrum-L2': {
|
||||
const instance = await BridgeArbitrumL2Mock.new();
|
||||
const code = await web3.eth.getCode(instance.address);
|
||||
await promisify(web3.currentProvider.send.bind(web3.currentProvider))({
|
||||
jsonrpc: '2.0',
|
||||
method: 'hardhat_setCode',
|
||||
params: [ '0x0000000000000000000000000000000000000064', code ],
|
||||
id: new Date().getTime(),
|
||||
});
|
||||
return BridgeArbitrumL2Mock.at('0x0000000000000000000000000000000000000064');
|
||||
}
|
||||
|
||||
case 'Optimism':
|
||||
return BridgeOptimismMock.new();
|
||||
|
||||
case 'Polygon-Child':
|
||||
return BridgePolygonChildMock.new();
|
||||
|
||||
default:
|
||||
throw new Error(`CrossChain: ${type} is not supported`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BridgeHelper,
|
||||
};
|
||||
24
test/helpers/customError.js
Normal file
24
test/helpers/customError.js
Normal file
@ -0,0 +1,24 @@
|
||||
const { config } = require('hardhat');
|
||||
|
||||
const optimizationsEnabled = config.solidity.compilers.some(c => c.settings.optimizer.enabled);
|
||||
|
||||
/** Revert handler that supports custom errors. */
|
||||
async function expectRevertCustomError (promise, reason) {
|
||||
try {
|
||||
await promise;
|
||||
expect.fail('Expected promise to throw but it didn\'t');
|
||||
} catch (revert) {
|
||||
if (reason) {
|
||||
if (optimizationsEnabled) {
|
||||
// Optimizations currently mess with Hardhat's decoding of custom errors
|
||||
expect(revert.message).to.include.oneOf([reason, 'unrecognized return data or custom error']);
|
||||
} else {
|
||||
expect(revert.message).to.include(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
expectRevertCustomError,
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
const { expectEvent } = require('@openzeppelin/test-helpers');
|
||||
const { expect } = require('chai');
|
||||
const { expectRevertCustomError } = require('../../helpers/customError');
|
||||
|
||||
const Bytes32DequeMock = artifacts.require('Bytes32DequeMock');
|
||||
|
||||
@ -10,18 +10,6 @@ async function getContent (deque) {
|
||||
return values;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract('DoubleEndedQueue', function (accounts) {
|
||||
const bytesA = '0xdeadbeef'.padEnd(66, '0');
|
||||
const bytesB = '0x0123456789'.padEnd(66, '0');
|
||||
@ -39,10 +27,10 @@ contract('DoubleEndedQueue', function (accounts) {
|
||||
});
|
||||
|
||||
it('reverts on accesses', async function () {
|
||||
await expectRevert(this.deque.popBack(), 'Empty()');
|
||||
await expectRevert(this.deque.popFront(), 'Empty()');
|
||||
await expectRevert(this.deque.back(), 'Empty()');
|
||||
await expectRevert(this.deque.front(), 'Empty()');
|
||||
await expectRevertCustomError(this.deque.popBack(), 'Empty()');
|
||||
await expectRevertCustomError(this.deque.popFront(), 'Empty()');
|
||||
await expectRevertCustomError(this.deque.back(), 'Empty()');
|
||||
await expectRevertCustomError(this.deque.front(), 'Empty()');
|
||||
});
|
||||
});
|
||||
|
||||
@ -63,7 +51,7 @@ contract('DoubleEndedQueue', function (accounts) {
|
||||
});
|
||||
|
||||
it('out of bounds access', async function () {
|
||||
await expectRevert(this.deque.at(this.content.length), 'OutOfBounds()');
|
||||
await expectRevertCustomError(this.deque.at(this.content.length), 'OutOfBounds()');
|
||||
});
|
||||
|
||||
describe('push', function () {
|
||||
|
||||
Reference in New Issue
Block a user