Skip to content
This repository has been archived by the owner on May 9, 2024. It is now read-only.

Commit

Permalink
Merge da432bf into 3bcbce5
Browse files Browse the repository at this point in the history
  • Loading branch information
nmlinaric committed Jun 21, 2022
2 parents 3bcbce5 + da432bf commit 3be80cb
Show file tree
Hide file tree
Showing 4 changed files with 511 additions and 90 deletions.
42 changes: 41 additions & 1 deletion contracts/Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ contract Bridge is Pausable, AccessControl {
uint64 depositNonce,
bytes32 dataHash
);

event FailedHandlerExecution(
bytes lowLevelData
);
Expand Down Expand Up @@ -257,7 +258,7 @@ contract Bridge is Pausable, AccessControl {
function executeProposal(uint8 originDomainID, uint64 depositNonce, bytes calldata data, bytes32 resourceID, bytes calldata signature) public whenNotPaused {
require(isProposalExecuted(originDomainID, depositNonce) != true, "Deposit with provided nonce already executed");

address signer = keccak256(abi.encodePacked(originDomainID, _domainID, depositNonce, data, resourceID)).recover(signature);
address signer = keccak256(abi.encode(originDomainID, _domainID, depositNonce, data, resourceID)).recover(signature);
require(signer == _MPCAddress, "Invalid message signer");

address handler = _resourceIDToHandlerAddress[resourceID];
Expand All @@ -273,6 +274,45 @@ contract Bridge is Pausable, AccessControl {
emit ProposalExecution(originDomainID, depositNonce, dataHash);
}

/**
@notice Executes a batch of deposit proposals using a specified handler contract for each proposal (only if signature is signed by MPC).
@param originDomainIDs Array of IDs of chain deposit originated from.
@param resourceIDs Array of ResourceIDs to be used when making deposits.
@param depositNonces Array of IDs of deposit generated by origin Bridge contract.
@param data Array of data originally provided when deposit was made.
@param signature bytes memory signature for the whole batch composed of MPC key shares
@notice Emits {ProposalExecution} event for each proposal in the batch.
*/
function executeProposals(uint8[] memory originDomainIDs, uint64[] memory depositNonces, bytes[] calldata data, bytes32[] memory resourceIDs, bytes memory signature) public whenNotPaused {
require(originDomainIDs.length == depositNonces.length, "DomainID and deposit nonce arrays have to be same length");
require(originDomainIDs.length == data.length, "DomainID and data arrays have to be same length");
require(originDomainIDs.length == resourceIDs.length, "DomainID and resourceID arrays have to be same length");

address signer = keccak256(abi.encode(originDomainIDs,_domainID, depositNonces, data, resourceIDs)).recover(signature);
require(signer == _MPCAddress, "Invalid message signer");

for (uint256 i = 0; i < originDomainIDs.length; i++) {
if(isProposalExecuted(originDomainIDs[i], depositNonces[i])) {
continue;
}

address handler = _resourceIDToHandlerAddress[resourceIDs[i]];
bytes32 dataHash = keccak256(abi.encodePacked(handler, data[i]));

IDepositExecute depositHandler = IDepositExecute(handler);

usedNonces[originDomainIDs[i]][depositNonces[i] / 256] |= 1 << (depositNonces[i] % 256);

try depositHandler.executeProposal(resourceIDs[i], data[i]) {
} catch (bytes memory lowLevelData) {
emit FailedHandlerExecution(lowLevelData);
continue;
}

emit ProposalExecution(originDomainIDs[i], depositNonces[i], dataHash);
}
}

/**
@notice Once MPC address is set, this method can't be invoked anymore.
It's used to trigger the belonging process on the MPC side which also handles keygen function calls order.
Expand Down
307 changes: 307 additions & 0 deletions test/contractBridge/executeProposals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
/**
* Copyright 2020 ChainSafe Systems
* SPDX-License-Identifier: LGPL-3.0-only
*/

const TruffleAssert = require('truffle-assertions');
const Ethers = require('ethers');

const Helpers = require('../helpers');

const BridgeContract = artifacts.require("Bridge");
const ERC20MintableContract = artifacts.require("ERC20PresetMinterPauser");
const ERC20HandlerContract = artifacts.require("ERC20Handler");
const ERC721MintableContract = artifacts.require("ERC721MinterBurnerPauser");
const ERC721HandlerContract = artifacts.require("ERC721Handler");
const ERC1155MintableContract = artifacts.require("ERC1155PresetMinterPauser");
const ERC1155HandlerContract = artifacts.require("ERC1155Handler");

contract('Bridge - [execute proposal]', async (accounts) => {
const destinationDomainID = 1;
const originDomainIDs = [2,2,2];

const depositerAddress = accounts[1];
const recipientAddress = accounts[2];
const relayer1Address = accounts[3];

const tokenID = 1;
const erc721DepositMetadata = "0xf00d";
const initialTokenAmount = 100;
const depositAmount = 10;
const expectedDepositNonces = [1,2,3];
const feeData = '0x';

let BridgeInstance;
let ERC20MintableInstance;
let ERC20HandlerInstance;
let ERC721MintableInstance;
let ERC721HandlerInstance;
let ERC1155MintableInstance;
let ERC1155HandlerInstance;

let erc20ResourceID;
let erc721ResourceID;
let erc1155ResourceID;
let resourceIDs;
let erc20DepositData;
let erc20DepositProposalData;
let erc20DataHash;
let erc721DepositData;
let erc721DepositProposalData;
let erc721DataHash;
let erc1155DepositData;
let erc1155DepositProposalData;
let erc1155DataHash;
let proposalDataArray;

beforeEach(async () => {
await Promise.all([
BridgeContract.new(destinationDomainID).then(instance => BridgeInstance = instance),
ERC20MintableContract.new("ERC20token", "ERC20TOK").then(instance => ERC20MintableInstance = instance),
ERC721MintableContract.new("ERC721token", "ERC721TOK", "").then(instance => ERC721MintableInstance = instance),
ERC1155MintableContract.new("ERC1155TOK").then(instance => ERC1155MintableInstance = instance),
]);

erc20ResourceID = Helpers.createResourceID(ERC20MintableInstance.address, destinationDomainID),
erc721ResourceID = Helpers.createResourceID(ERC721MintableInstance.address, destinationDomainID),
erc1155ResourceID = Helpers.createResourceID(ERC1155MintableInstance.address, destinationDomainID);
resourceIDs = [erc20ResourceID, erc721ResourceID, erc1155ResourceID];
contractAddresses = [ERC20MintableInstance.address, ERC721MintableInstance.address, ERC1155MintableInstance.address];

ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address);
ERC721HandlerInstance = await ERC721HandlerContract.new(BridgeInstance.address);
ERC1155HandlerInstance = await ERC1155HandlerContract.new(BridgeInstance.address);

await Promise.all([
ERC20MintableInstance.mint(depositerAddress, initialTokenAmount),
BridgeInstance.adminSetResource(ERC20HandlerInstance.address, erc20ResourceID, ERC20MintableInstance.address),
ERC721MintableInstance.grantRole(await ERC721MintableInstance.MINTER_ROLE(), ERC721HandlerInstance.address),
ERC721MintableInstance.mint(depositerAddress, tokenID, ""),
BridgeInstance.adminSetResource(ERC721HandlerInstance.address, erc721ResourceID, ERC721MintableInstance.address),
BridgeInstance.adminSetResource(ERC1155HandlerInstance.address, erc1155ResourceID, ERC1155MintableInstance.address),
ERC1155MintableInstance.mintBatch(depositerAddress, [tokenID], [initialTokenAmount], "0x0"),
]);

await Promise.all([
ERC20MintableInstance.approve(ERC20HandlerInstance.address, depositAmount, { from: depositerAddress }),
ERC721MintableInstance.approve(ERC721HandlerInstance.address, tokenID, { from: depositerAddress }),
ERC1155MintableInstance.setApprovalForAll(ERC1155HandlerInstance.address, true, { from: depositerAddress })
]);

erc20DepositData = Helpers.createERCDepositData(depositAmount, 20, recipientAddress)
erc20DepositProposalData = Helpers.createERCDepositData(depositAmount, 20, recipientAddress)
erc20DataHash = Ethers.utils.keccak256(ERC20HandlerInstance.address + erc20DepositProposalData.substr(2));

erc721DepositData = Helpers.createERCDepositData(tokenID, 20, recipientAddress);
erc721DepositProposalData = Helpers.createERC721DepositProposalData(tokenID, 20, recipientAddress, erc721DepositMetadata.length, erc721DepositMetadata);
erc721DataHash = Ethers.utils.keccak256(ERC721HandlerInstance.address + erc721DepositProposalData.substr(2));

erc1155DepositData = Helpers.createERC1155DepositData([tokenID], [depositAmount]);
erc1155DepositProposalData = Helpers.createERC1155DepositProposalData([tokenID], [depositAmount], recipientAddress, "0x");
erc1155DataHash = Ethers.utils.keccak256(ERC1155HandlerInstance.address + erc1155DepositProposalData.substr(2));

proposalDataArray = [erc20DepositProposalData, erc721DepositProposalData, erc1155DepositProposalData];

// set MPC address to unpause the Bridge
await BridgeInstance.endKeygen(Helpers.mpcAddress);
});

it('should create and execute executeProposal successfully', async () => {
const proposalSignedData = await Helpers.signArrayOfDataWithMpc(originDomainIDs, destinationDomainID, expectedDepositNonces, proposalDataArray, resourceIDs);

// depositerAddress makes initial deposit of depositAmount
assert.isFalse(await BridgeInstance.paused());
await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[0],
erc20ResourceID,
erc20DepositData,
feeData,
{ from: depositerAddress }
));

await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[1],
erc721ResourceID,
erc721DepositData,
feeData,
{ from: depositerAddress }
));

await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[2],
erc1155ResourceID,
erc1155DepositData,
feeData,
{ from: depositerAddress }
));

const executeTx = await BridgeInstance.executeProposals(
originDomainIDs,
expectedDepositNonces,
proposalDataArray,
resourceIDs,
proposalSignedData,
{ from: relayer1Address }
);

await TruffleAssert.passes(executeTx);

// check that deposit nonces had been marked as used in bitmap
expectedDepositNonces.forEach(
async (_,index) => {
assert.isTrue(await BridgeInstance.isProposalExecuted(originDomainIDs[0], expectedDepositNonces[index]));
});

// check that tokens are transferred to recipient address
const recipientERC20Balance = await ERC20MintableInstance.balanceOf(recipientAddress);
assert.strictEqual(recipientERC20Balance.toNumber(), depositAmount);

const recipientERC721Balance = await ERC721MintableInstance.balanceOf(recipientAddress);
assert.strictEqual(recipientERC721Balance.toNumber(), 1);

const recipientERC1155Balance = await ERC1155MintableInstance.balanceOf(recipientAddress, destinationDomainID);
assert.strictEqual(recipientERC1155Balance.toNumber(), depositAmount);
});

it('should skip executing proposal if deposit nonce is already used', async () => {
const proposalSignedData = await Helpers.signArrayOfDataWithMpc(originDomainIDs, destinationDomainID, expectedDepositNonces, proposalDataArray, resourceIDs);

// depositerAddress makes initial deposit of depositAmount
assert.isFalse(await BridgeInstance.paused());
await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[0],
erc20ResourceID,
erc20DepositData,
feeData,
{ from: depositerAddress }
));

await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[1],
erc721ResourceID,
erc721DepositData,
feeData,
{ from: depositerAddress }
));

await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[2],
erc1155ResourceID,
erc1155DepositData,
feeData,
{ from: depositerAddress }
));

const executeTx = await BridgeInstance.executeProposals(
originDomainIDs,
expectedDepositNonces,
proposalDataArray,
resourceIDs,
proposalSignedData,
{ from: relayer1Address }
);


await TruffleAssert.passes(executeTx);

// check that deposit nonces had been marked as used in bitmap
expectedDepositNonces.forEach(
async (_,index) => {
assert.isTrue(await BridgeInstance.isProposalExecuted(originDomainIDs[0], expectedDepositNonces[index]));
});

// check that tokens are transferred to recipient address
const recipientERC20Balance = await ERC20MintableInstance.balanceOf(recipientAddress);
assert.strictEqual(recipientERC20Balance.toNumber(), depositAmount);

const recipientERC721Balance = await ERC721MintableInstance.balanceOf(recipientAddress);
assert.strictEqual(recipientERC721Balance.toNumber(), 1);

const recipientERC1155Balance = await ERC1155MintableInstance.balanceOf(recipientAddress, destinationDomainID);
assert.strictEqual(recipientERC1155Balance.toNumber(), depositAmount);


const skipExecuteTx = await BridgeInstance.executeProposals(
originDomainIDs,
expectedDepositNonces,
proposalDataArray,
resourceIDs,
proposalSignedData,
{ from: relayer1Address }
);

// check that no ProposalExecution events are emitted
assert.equal(skipExecuteTx.logs.length, 0);
});

it('executeProposal event should be emitted with expected values', async () => {
const proposalSignedData = await Helpers.signArrayOfDataWithMpc(originDomainIDs, destinationDomainID, expectedDepositNonces, proposalDataArray, resourceIDs);

// depositerAddress makes initial deposit of depositAmount
assert.isFalse(await BridgeInstance.paused());
await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[0],
erc20ResourceID,
erc20DepositData,
feeData,
{ from: depositerAddress }
));

await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[1],
erc721ResourceID,
erc721DepositData,
feeData,
{ from: depositerAddress }
));

await TruffleAssert.passes(BridgeInstance.deposit(
originDomainIDs[2],
erc1155ResourceID,
erc1155DepositData,
feeData,
{ from: depositerAddress }
));

const executeTx = await BridgeInstance.executeProposals(
originDomainIDs,
expectedDepositNonces,
proposalDataArray,
resourceIDs,
proposalSignedData,
{ from: relayer1Address }
);

TruffleAssert.eventEmitted(executeTx, 'ProposalExecution', (event) => {
return event.originDomainID.toNumber() === originDomainIDs[0] &&
event.depositNonce.toNumber() === expectedDepositNonces[0] &&
event.dataHash === erc20DataHash
});

// check that ProposalExecution has been emitted with expected values for ERC721
assert.equal(executeTx.logs[1].args.originDomainID, originDomainIDs[1]);
assert.equal(executeTx.logs[1].args.depositNonce, expectedDepositNonces[1]);
assert.equal(executeTx.logs[1].args.dataHash, erc721DataHash);

// check that ProposalExecution has been emitted with expected values for ERC1155
assert.equal(executeTx.logs[2].args.originDomainID, originDomainIDs[2]);
assert.equal(executeTx.logs[2].args.depositNonce, expectedDepositNonces[2]);
assert.equal(executeTx.logs[2].args.dataHash, erc1155DataHash);

// check that deposit nonces had been marked as used in bitmap
expectedDepositNonces.forEach(
async (_,index) => {
assert.isTrue(await BridgeInstance.isProposalExecuted(originDomainIDs[0], expectedDepositNonces[index]));
});

// check that tokens are transferred to recipient address
const recipientERC20Balance = await ERC20MintableInstance.balanceOf(recipientAddress);
assert.strictEqual(recipientERC20Balance.toNumber(), depositAmount);

const recipientERC721Balance = await ERC721MintableInstance.balanceOf(recipientAddress);
assert.strictEqual(recipientERC721Balance.toNumber(), 1);

const recipientERC1155Balance = await ERC1155MintableInstance.balanceOf(recipientAddress, destinationDomainID);
assert.strictEqual(recipientERC1155Balance.toNumber(), depositAmount);
});
});

0 comments on commit 3be80cb

Please sign in to comment.