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

Anderson/add admin set deposit nonce #385

Merged
merged 2 commits into from
Sep 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 43 additions & 32 deletions contracts/Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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,32 +33,32 @@ 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,
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
Expand Down Expand Up @@ -105,12 +105,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 @@ -125,7 +125,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 @@ -252,9 +252,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 @@ -263,8 +274,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 @@ -306,42 +317,42 @@ 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 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);
bytes memory handlerResponse = depositHandler.deposit(resourceID, msg.sender, data);

emit Deposit(destinationChainID, resourceID, depositNonce, msg.sender, data, handlerResponse);
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.
@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 {
function voteProposal(uint8 domainID, uint64 depositNonce, bytes32 resourceID, bytes32 dataHash) external onlyRelayers whenNotPaused {

uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID);
uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(domainID);
Proposal memory proposal = _proposals[nonceAndID][dataHash];

require(_resourceIDToHandlerAddress[resourceID] != address(0), "no handler for resourceID");
Expand All @@ -356,26 +367,26 @@ 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;
Expand All @@ -384,14 +395,14 @@ contract Bridge is Pausable, AccessControl, SafeMath {
/**
@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 @@ -402,23 +413,23 @@ 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.
@notice Proposal must have Passed status.
@notice Hash of {data} must equal proposal's {dataHash}.
@notice Emits {ProposalEvent} event with status {Executed}.
*/
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) external 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];

Expand All @@ -429,7 +440,7 @@ contract Bridge is Pausable, AccessControl, SafeMath {
IDepositExecute depositHandler = IDepositExecute(handler);
depositHandler.executeProposal(resourceID, data);

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

/**
Expand Down
6 changes: 3 additions & 3 deletions contracts/interfaces/IBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ pragma solidity 0.6.12;
*/
interface IBridge {
/**
@notice Exposing getter for {_chainID} instead of forcing the use of call.
@return uint8 The {_chainID} that is currently set for the Bridge contract.
@notice Exposing getter for {_domainID} instead of forcing the use of call.
@return uint8 The {_domainID} that is currently set for the Bridge contract.
*/
function _chainID() external returns (uint8);
function _domainID() external returns (uint8);
}
34 changes: 27 additions & 7 deletions test/contractBridge/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const CentrifugeAssetContract = artifacts.require("CentrifugeAsset");
// This test does NOT include all getter methods, just
// getters that should work with only the constructor called
contract('Bridge - [admin]', async accounts => {
const chainID = 1;
const domainID = 1;
const initialRelayers = accounts.slice(0, 3);
const initialRelayerThreshold = 2;

Expand All @@ -32,7 +32,7 @@ contract('Bridge - [admin]', async accounts => {
};

beforeEach(async () => {
BridgeInstance = await BridgeContract.new(chainID, initialRelayers, initialRelayerThreshold, 0, 100);
BridgeInstance = await BridgeContract.new(domainID, initialRelayers, initialRelayerThreshold, 0, 100);
ADMIN_ROLE = await BridgeInstance.DEFAULT_ADMIN_ROLE()
});

Expand Down Expand Up @@ -108,7 +108,7 @@ contract('Bridge - [admin]', async accounts => {

it('Should set a Resource ID for handler address', async () => {
const ERC20MintableInstance = await ERC20MintableContract.new("token", "TOK");
const resourceID = Helpers.createResourceID(ERC20MintableInstance.address, chainID);
const resourceID = Helpers.createResourceID(ERC20MintableInstance.address, domainID);
const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address, [], [], []);

assert.equal(await BridgeInstance._resourceIDToHandlerAddress.call(resourceID), Ethers.constants.AddressZero);
Expand All @@ -121,7 +121,7 @@ contract('Bridge - [admin]', async accounts => {

it('Should set a ERC20 Resource ID and contract address', async () => {
const ERC20MintableInstance = await ERC20MintableContract.new("token", "TOK");
const resourceID = Helpers.createResourceID(ERC20MintableInstance.address, chainID);
const resourceID = Helpers.createResourceID(ERC20MintableInstance.address, domainID);
const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address, [], [], []);

await TruffleAssert.passes(BridgeInstance.adminSetResource(
Expand All @@ -138,7 +138,7 @@ contract('Bridge - [admin]', async accounts => {

it('Should set a Generic Resource ID and contract address', async () => {
const CentrifugeAssetInstance = await CentrifugeAssetContract.new();
const resourceID = Helpers.createResourceID(CentrifugeAssetInstance.address, chainID);
const resourceID = Helpers.createResourceID(CentrifugeAssetInstance.address, domainID);
const GenericHandlerInstance = await GenericHandlerContract.new(BridgeInstance.address, [], [], [], [], []);

await TruffleAssert.passes(BridgeInstance.adminSetGenericResource(GenericHandlerInstance.address, resourceID, CentrifugeAssetInstance.address, '0x00000000', 0, '0x00000000'));
Expand All @@ -154,7 +154,7 @@ contract('Bridge - [admin]', async accounts => {

it('Should set ERC20MintableInstance.address as burnable', async () => {
const ERC20MintableInstance = await ERC20MintableContract.new("token", "TOK");
const resourceID = Helpers.createResourceID(ERC20MintableInstance.address, chainID);
const resourceID = Helpers.createResourceID(ERC20MintableInstance.address, domainID);
const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address, [resourceID], [ERC20MintableInstance.address], []);

await TruffleAssert.passes(BridgeInstance.adminSetBurnable(ERC20HandlerInstance.address, ERC20MintableInstance.address));
Expand Down Expand Up @@ -194,7 +194,7 @@ contract('Bridge - [admin]', async accounts => {
let handlerBalance;

const ERC20MintableInstance = await ERC20MintableContract.new("token", "TOK");
const resourceID = Helpers.createResourceID(ERC20MintableInstance.address, chainID);
const resourceID = Helpers.createResourceID(ERC20MintableInstance.address, domainID);
const ERC20HandlerInstance = await ERC20HandlerContract.new(BridgeInstance.address, [resourceID], [ERC20MintableInstance.address], []);

await ERC20MintableInstance.mint(tokenOwner, numTokens);
Expand All @@ -216,4 +216,24 @@ contract('Bridge - [admin]', async accounts => {
it('Should require admin role to withdraw funds', async () => {
await assertOnlyAdmin(BridgeInstance.adminWithdraw, someAddress, someAddress, someAddress, 0);
});

// Set nonce

it('Should set nonce', async () => {
const nonce = 3;
await BridgeInstance.adminSetDepositNonce(domainID, nonce);
const nonceAfterSet = await BridgeInstance._depositCounts.call(domainID);
assert.equal(nonceAfterSet, nonce);
});

it('Should require admin role to set nonce', async () => {
await assertOnlyAdmin(BridgeInstance.adminSetDepositNonce, 1, 3);
});

it('Should not allow for decrements of the nonce', async () => {
const currentNonce = 3;
await BridgeInstance.adminSetDepositNonce(domainID, currentNonce);
const newNonce = 2;
await TruffleAssert.reverts(BridgeInstance.adminSetDepositNonce(domainID, newNonce), "Does not allow decrements of the nonce");
});
});
Loading