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

Commit

Permalink
Develop (#413)
Browse files Browse the repository at this point in the history
* Anderson/remove deposit record (#382)

* remove DepositRecord in every handler,  modify event in Bridge, modify IDepositExecute interface
* modify test files based on removing deposit records
* add handlerResponse to deposit event and modify test files based on the event

* Rename chainID to domainID (#384)

* Add admin set deposit nonce (#385)

* Сombine vote and execute [Breaking] (#389)

* Remove resources from handler constructors (#393)

* Update dependencies and bump version (#394)

Set disableConfirmationListener: true to avoid spamming ganache during tests. trufflesuite/truffle#3522

* Remove fund functions (#412)

Co-authored-by: andersonlee725 <86676141+andersonlee725@users.noreply.github.com>
Co-authored-by: Oleksii Matiiasevych <oleksii@chainsafe.io>
  • Loading branch information
3 people authored Oct 27, 2021
1 parent ce60431 commit cbfaf9c
Show file tree
Hide file tree
Showing 50 changed files with 22,563 additions and 6,222 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ jobs:
node-version: [12.x]
steps:
- name: Checkout code
uses: actions/checkout@v2
- uses: actions/cache@v2.1.4
uses: actions/checkout@v2.3.4
- uses: actions/cache@v2.1.6
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
uses: actions/setup-node@v2.1.5
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
Expand All @@ -39,7 +39,7 @@ jobs:
node-version: [12.x]
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v2.3.4
- name: Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
Expand All @@ -51,4 +51,4 @@ jobs:
- name: Coverall
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
124 changes: 83 additions & 41 deletions contracts/Bridge.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

Expand All @@ -19,7 +20,7 @@ contract Bridge is Pausable, AccessControl, SafeMath {
// Limit relayers number because proposal can fit only so much votes
uint256 constant public MAX_RELAYERS = 200;

uint8 public _chainID;
uint8 public _domainID;
uint8 public _relayerThreshold;
uint128 public _fee;
uint40 public _expiry;
Expand All @@ -33,33 +34,39 @@ contract Bridge is Pausable, AccessControl, SafeMath {
uint40 _proposedBlock; // 1099511627775 maximum block
}

// destinationChainID => number of deposits
// destinationDomainID => number of deposits
mapping(uint8 => uint64) public _depositCounts;
// resourceID => handler address
mapping(bytes32 => address) public _resourceIDToHandlerAddress;
// destinationChainID + depositNonce => dataHash => Proposal
// destinationDomainID + depositNonce => dataHash => Proposal
mapping(uint72 => mapping(bytes32 => Proposal)) private _proposals;

event RelayerThresholdChanged(uint256 newThreshold);
event RelayerAdded(address relayer);
event RelayerRemoved(address relayer);
event Deposit(
uint8 destinationChainID,
uint8 destinationDomainID,
bytes32 resourceID,
uint64 depositNonce
uint64 depositNonce,
address indexed user,
bytes data,
bytes handlerResponse
);
event ProposalEvent(
uint8 originChainID,
uint8 originDomainID,
uint64 depositNonce,
ProposalStatus status,
bytes32 dataHash
);
event ProposalVote(
uint8 originChainID,
uint8 originDomainID,
uint64 depositNonce,
ProposalStatus status,
bytes32 dataHash
);
event FailedHandlerExecution(
bytes lowLevelData
);

bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");

Expand Down Expand Up @@ -102,12 +109,12 @@ contract Bridge is Pausable, AccessControl, SafeMath {
/**
@notice Initializes Bridge, creates and grants {msg.sender} the admin role,
creates and grants {initialRelayers} the relayer role.
@param chainID ID of chain the Bridge contract exists on.
@param domainID ID of chain the Bridge contract exists on.
@param initialRelayers Addresses that should be initially granted the relayer role.
@param initialRelayerThreshold Number of votes needed for a deposit proposal to be considered passed.
*/
constructor (uint8 chainID, address[] memory initialRelayers, uint256 initialRelayerThreshold, uint256 fee, uint256 expiry) public {
_chainID = chainID;
constructor (uint8 domainID, address[] memory initialRelayers, uint256 initialRelayerThreshold, uint256 fee, uint256 expiry) public {
_domainID = domainID;
_relayerThreshold = initialRelayerThreshold.toUint8();
_fee = fee.toUint128();
_expiry = expiry.toUint40();
Expand All @@ -122,7 +129,7 @@ contract Bridge is Pausable, AccessControl, SafeMath {
/**
@notice Returns true if {relayer} has voted on {destNonce} {dataHash} proposal.
@notice Naming left unchanged for backward compatibility.
@param destNonce destinationChainID + depositNonce of the proposal.
@param destNonce destinationDomainID + depositNonce of the proposal.
@param dataHash Hash of data to be provided when deposit proposal is executed.
@param relayer Address to check.
*/
Expand Down Expand Up @@ -249,9 +256,20 @@ contract Bridge is Pausable, AccessControl, SafeMath {
handler.setBurnable(tokenAddress);
}

/**
@notice Sets the nonce for the specific domainID.
@notice Only callable by an address that currently has the admin role.
@param domainID Domain ID for increasing nonce.
@param nonce The nonce value to be set.
*/
function adminSetDepositNonce(uint8 domainID, uint64 nonce) external onlyAdmin {
require(nonce > _depositCounts[domainID], "Does not allow decrements of the nonce");
_depositCounts[domainID] = nonce;
}

/**
@notice Returns a proposal.
@param originChainID Chain ID deposit originated from.
@param originDomainID Chain ID deposit originated from.
@param depositNonce ID of proposal generated by proposal's origin Bridge contract.
@param dataHash Hash of data to be provided when deposit proposal is executed.
@return Proposal which consists of:
Expand All @@ -260,8 +278,8 @@ contract Bridge is Pausable, AccessControl, SafeMath {
- _noVotes Number of votes against proposal.
- _status Current status of proposal.
*/
function getProposal(uint8 originChainID, uint64 depositNonce, bytes32 dataHash) external view returns (Proposal memory) {
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(originChainID);
function getProposal(uint8 originDomainID, uint64 depositNonce, bytes32 dataHash) external view returns (Proposal memory) {
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(originDomainID);
return _proposals[nonceAndID][dataHash];
}

Expand Down Expand Up @@ -303,43 +321,53 @@ contract Bridge is Pausable, AccessControl, SafeMath {
/**
@notice Initiates a transfer using a specified handler contract.
@notice Only callable when Bridge is not paused.
@param destinationChainID ID of chain deposit will be bridged to.
@param destinationDomainID ID of chain deposit will be bridged to.
@param resourceID ResourceID used to find address of handler to be used for deposit.
@param data Additional data to be passed to specified handler.
@notice Emits {Deposit} event.
@notice Emits {Deposit} event with all necessary parameters and a handler response.
- ERC20Handler: responds with an empty data.
- ERC721Handler: responds with the deposited token metadata acquired by calling a tokenURI method in the token contract.
- GenericHandler: responds with the raw bytes returned from the call to the target contract.
*/
function deposit(uint8 destinationChainID, bytes32 resourceID, bytes calldata data) external payable whenNotPaused {
function deposit(uint8 destinationDomainID, bytes32 resourceID, bytes calldata data) external payable whenNotPaused {
require(msg.value == _fee, "Incorrect fee supplied");

address handler = _resourceIDToHandlerAddress[resourceID];
require(handler != address(0), "resourceID not mapped to handler");

uint64 depositNonce = ++_depositCounts[destinationChainID];
uint64 depositNonce = ++_depositCounts[destinationDomainID];

IDepositExecute depositHandler = IDepositExecute(handler);
depositHandler.deposit(resourceID, destinationChainID, depositNonce, msg.sender, data);
bytes memory handlerResponse = depositHandler.deposit(resourceID, msg.sender, data);

emit Deposit(destinationChainID, resourceID, depositNonce);
emit Deposit(destinationDomainID, resourceID, depositNonce, msg.sender, data, handlerResponse);
}

/**
@notice When called, {msg.sender} will be marked as voting in favor of proposal.
@notice Only callable by relayers when Bridge is not paused.
@param chainID ID of chain deposit originated from.
@param domainID ID of chain deposit originated from.
@param depositNonce ID of deposited generated by origin Bridge contract.
@param dataHash Hash of data provided when deposit was made.
@param data Data originally provided when deposit was made.
@notice Proposal must not have already been passed or executed.
@notice {msg.sender} must not have already voted on proposal.
@notice Emits {ProposalEvent} event with status indicating the proposal status.
@notice Emits {ProposalVote} event.
*/
function voteProposal(uint8 chainID, uint64 depositNonce, bytes32 resourceID, bytes32 dataHash) external onlyRelayers whenNotPaused {

uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID);
function voteProposal(uint8 domainID, uint64 depositNonce, bytes32 resourceID, bytes calldata data) external onlyRelayers whenNotPaused {
address handler = _resourceIDToHandlerAddress[resourceID];
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(domainID);
bytes32 dataHash = keccak256(abi.encodePacked(handler, data));
Proposal memory proposal = _proposals[nonceAndID][dataHash];

require(_resourceIDToHandlerAddress[resourceID] != address(0), "no handler for resourceID");
require(uint(proposal._status) <= 1, "proposal already passed/executed/cancelled");

if (proposal._status == ProposalStatus.Passed) {
executeProposal(domainID, depositNonce, data, resourceID, true);
return;
}

require(uint(proposal._status) <= 1, "proposal already executed/cancelled");
require(!_hasVoted(proposal, msg.sender), "relayer already voted");

if (proposal._status == ProposalStatus.Inactive) {
Expand All @@ -350,42 +378,45 @@ contract Bridge is Pausable, AccessControl, SafeMath {
_proposedBlock : uint40(block.number) // Overflow is desired.
});

emit ProposalEvent(chainID, depositNonce, ProposalStatus.Active, dataHash);
emit ProposalEvent(domainID, depositNonce, ProposalStatus.Active, dataHash);
} else if (uint40(sub(block.number, proposal._proposedBlock)) > _expiry) {
// if the number of blocks that has passed since this proposal was
// submitted exceeds the expiry threshold set, cancel the proposal
proposal._status = ProposalStatus.Cancelled;

emit ProposalEvent(chainID, depositNonce, ProposalStatus.Cancelled, dataHash);
emit ProposalEvent(domainID, depositNonce, ProposalStatus.Cancelled, dataHash);
}

if (proposal._status != ProposalStatus.Cancelled) {
proposal._yesVotes = (proposal._yesVotes | _relayerBit(msg.sender)).toUint200();
proposal._yesVotesTotal++; // TODO: check if bit counting is cheaper.

emit ProposalVote(chainID, depositNonce, proposal._status, dataHash);
emit ProposalVote(domainID, depositNonce, proposal._status, dataHash);

// Finalize if _relayerThreshold has been reached
if (proposal._yesVotesTotal >= _relayerThreshold) {
proposal._status = ProposalStatus.Passed;

emit ProposalEvent(chainID, depositNonce, ProposalStatus.Passed, dataHash);
emit ProposalEvent(domainID, depositNonce, ProposalStatus.Passed, dataHash);
}
}
_proposals[nonceAndID][dataHash] = proposal;

if (proposal._status == ProposalStatus.Passed) {
executeProposal(domainID, depositNonce, data, resourceID, false);
}
}

/**
@notice Cancels a deposit proposal that has not been executed yet.
@notice Only callable by relayers when Bridge is not paused.
@param chainID ID of chain deposit originated from.
@param domainID ID of chain deposit originated from.
@param depositNonce ID of deposited generated by origin Bridge contract.
@param dataHash Hash of data originally provided when deposit was made.
@notice Proposal must be past expiry threshold.
@notice Emits {ProposalEvent} event with status {Cancelled}.
*/
function cancelProposal(uint8 chainID, uint64 depositNonce, bytes32 dataHash) public onlyAdminOrRelayer {
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID);
function cancelProposal(uint8 domainID, uint64 depositNonce, bytes32 dataHash) public onlyAdminOrRelayer {
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(domainID);
Proposal memory proposal = _proposals[nonceAndID][dataHash];
ProposalStatus currentStatus = proposal._status;

Expand All @@ -396,34 +427,45 @@ contract Bridge is Pausable, AccessControl, SafeMath {
proposal._status = ProposalStatus.Cancelled;
_proposals[nonceAndID][dataHash] = proposal;

emit ProposalEvent(chainID, depositNonce, ProposalStatus.Cancelled, dataHash);
emit ProposalEvent(domainID, depositNonce, ProposalStatus.Cancelled, dataHash);
}

/**
@notice Executes a deposit proposal that is considered passed using a specified handler contract.
@notice Only callable by relayers when Bridge is not paused.
@param chainID ID of chain deposit originated from.
@param domainID ID of chain deposit originated from.
@param resourceID ResourceID to be used when making deposits.
@param depositNonce ID of deposited generated by origin Bridge contract.
@param data Data originally provided when deposit was made.
@param revertOnFail Decision if the transaction should be reverted in case of handler's executeProposal is reverted or not.
@notice Proposal must have Passed status.
@notice Hash of {data} must equal proposal's {dataHash}.
@notice Emits {ProposalEvent} event with status {Executed}.
@notice Emits {FailedExecution} event with the failed reason.
*/
function executeProposal(uint8 chainID, uint64 depositNonce, bytes calldata data, bytes32 resourceID) external onlyRelayers whenNotPaused {
function executeProposal(uint8 domainID, uint64 depositNonce, bytes calldata data, bytes32 resourceID, bool revertOnFail) public onlyRelayers whenNotPaused {
address handler = _resourceIDToHandlerAddress[resourceID];
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID);
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(domainID);
bytes32 dataHash = keccak256(abi.encodePacked(handler, data));
Proposal storage proposal = _proposals[nonceAndID][dataHash];

require(proposal._status == ProposalStatus.Passed, "Proposal must have Passed status");

proposal._status = ProposalStatus.Executed;

IDepositExecute depositHandler = IDepositExecute(handler);
depositHandler.executeProposal(resourceID, data);

emit ProposalEvent(chainID, depositNonce, ProposalStatus.Executed, dataHash);
if (revertOnFail) {
depositHandler.executeProposal(resourceID, data);
} else {
try depositHandler.executeProposal(resourceID, data) {
} catch (bytes memory lowLevelData) {
proposal._status = ProposalStatus.Passed;
emit FailedHandlerExecution(lowLevelData);
return;
}
}

emit ProposalEvent(domainID, depositNonce, ProposalStatus.Executed, dataHash);
}

/**
Expand Down
1 change: 1 addition & 0 deletions contracts/CentrifugeAsset.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

Expand Down
12 changes: 1 addition & 11 deletions contracts/ERC20Safe.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";
Expand All @@ -13,17 +14,6 @@ import "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol";
contract ERC20Safe {
using SafeMath for uint256;

/**
@notice Used to transfer tokens into the safe to fund proposals.
@param tokenAddress Address of ERC20 to transfer.
@param owner Address of current token owner.
@param amount Amount of tokens to transfer.
*/
function fundERC20(address tokenAddress, address owner, uint256 amount) public {
IERC20 erc20 = IERC20(tokenAddress);
_safeTransferFrom(erc20, owner, address(this), amount);
}

/**
@notice Used to gain custody of deposited token.
@param tokenAddress Address of ERC20 to transfer.
Expand Down
1 change: 1 addition & 0 deletions contracts/ERC721MinterBurnerPauser.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.6.12;

// This is adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/presets/ERC721PresetMinterPauserAutoId.sol
Expand Down
12 changes: 1 addition & 11 deletions contracts/ERC721Safe.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";
Expand All @@ -12,17 +13,6 @@ import "./ERC721MinterBurnerPauser.sol";
contract ERC721Safe {
using SafeMath for uint256;

/**
@notice Used to transfer tokens into the safe to fund proposals.
@param tokenAddress Address of ERC721 to transfer.
@param owner Address of current token owner.
@param tokenID ID of token to transfer.
*/
function fundERC721(address tokenAddress, address owner, uint tokenID) public {
IERC721 erc721 = IERC721(tokenAddress);
erc721.transferFrom(owner, address(this), tokenID);
}

/**
@notice Used to gain custoday of deposited token.
@param tokenAddress Address of ERC721 to transfer.
Expand Down
1 change: 1 addition & 0 deletions contracts/Migrations.sol
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.6.12;

contract Migrations {
Expand Down
Loading

0 comments on commit cbfaf9c

Please sign in to comment.