Skip to content

Commit

Permalink
Contracts Refactor (ChainSafe#149)
Browse files Browse the repository at this point in the history
* format

* resolve conflicts

* Init test for Safe constructor

* Init test for releaseErc method

* Init releaseNFT test

* Refactor name of variables. Assign magic numbers to named variables for clarity

* Update DepositCounts to public

* Remove unused validator variables

* Rename variables and replace magic numbers with variables for clarity. Add depositID test

* Rename variables and replace magic numbers with variables for clarity. Add depositId test

* Replace magic address with variable for clarity

* Rename tokenId to tokenID for consistency

* Remove abi dir

* Add truffle-assertions package to test events in EVM smart contracts

* Update path to ignore contracts dir to be only dir in root

* Add Bridge contract that's a refactored verison of emitter

* Add refactored version of Safe.sol

* Init Validator contract

* Inint ERC20Handler

* Init ERC20Handler and Validator interfaces

* Temporarily whitelist path to evm Bridge contract tests

* Init depositERC20 test

* Add support for Generic and ERC721 deposits

* Add carrot to solidity version to allow for minor upgrades

* Update ganache-cli and truffle packages to latest version

* Replace usage of ERC20 contract with IERC20. Add releaseERC20 method

* Remove ERC20Safe constructor call. Add withdrawERC20 method

* Remove executeDeposit. Add withdrawERC20

* Add ValidatorActionType, ThresholdType, and Vote enums. Add hasVoted, createValidatorProposal, voteValidatorProposal, createThresholdProposal, and voteThresholdProposal function signatures

* Init ERC721Safe, ERC721Handler, IDepositHandler, IERC721Handler

* Formatting of test message

* Add ValidatorActionType, ThresholdType, VoteStatus enums. Add createThresholdProposal and voteThresholdProposal function signatures

* Add createValidatorProposal

* Add hasVoted. Formatting and spelling correction

* Spelling

* Rename validatorAddress to proposedAddress. Add createValidatorThresholdProposal and voteValidatorThresholdProposal

* Add createValidatorThresholdProposal and voteValidatorThresholdProposal

* Fix type in voteValidatorThresholdProposal

* Add createValidatorThresholdProposal method

* Add voteValidatorThresholdProposal method

* Add getDepositCount, getGenericDepositRecord, getERC20DepositRecord, getERC721DepositRecord, getDepositProposal methods

* Add getValidatorThreshold and getCurrentValidatorThresholdProposal methods

* Add getValidatorThreshold, getCurrentValidatorThresholdProposal, getValidatorProposal, getValidatorThreshold, getCurrentValidatorThresholdProposal, getValidatorProposal methods

* Add comments

* Add ValidatorAdded, ValidatorRemoved, and ValidatorThresholdChanged events

* Add ValidatorThresholdChanged event

* Add ValidatorProposalCreated and ValidatorProposalVote events

* Add ValidatorThresholdProposalCreated and ValidatorThresholdProposalVote events

* Add Validator contract

* Formatting

* Remove old Erc20 and Erc721 handlers

* Change _depositCount to be incremented before assignment as depositID

* Add extra data parsing assembly from old Erc721 handler

* Additonal test to complete ERC20 deposit coverage

* Init test file

* Init test file

* Add initialValidatorThreshold to constructor

* Add _addValidator and _removeValidator. Add constructor with initialValidators and initialValidatorThreshold

* Init test file

* Rename test describe string to provide better context

* Init test file

* renamed gitattributes (ChainSafe#164)

* Rename bridge test dir to chainBridge to avoid name conflicts

* Update usage of decimals in assembly block to hexadecimal for consistency

* Update usage of decimals in assembly block to hexadecimal for consistency. Correct copy calldata hexadecimal

Co-authored-by: Gregory Markou <[email protected]>
Co-authored-by: Gregory Markou <[email protected]>
Co-authored-by: Stephanie <[email protected]>
  • Loading branch information
4 people authored Mar 9, 2020
1 parent ccc38b2 commit 19ad136
Show file tree
Hide file tree
Showing 28 changed files with 2,353 additions and 484 deletions.
File renamed without changes.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ node_modules/

centrifuge-chain/

contracts/
./contracts/
abi/
387 changes: 387 additions & 0 deletions on-chain/evm-contracts/contracts/Bridge.sol

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion on-chain/evm-contracts/contracts/BridgeAsset.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity 0.5.12;
pragma solidity ^0.5.12;
pragma experimental ABIEncoderV2;

contract BridgeAsset {
Expand Down
25 changes: 25 additions & 0 deletions on-chain/evm-contracts/contracts/ERC20Safe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
pragma solidity ^0.5.12;

import "./helpers/SafeMath.sol";
import "./erc/ERC20/IERC20.sol";

contract ERC20Safe {
using SafeMath for uint;

// ERC20 contract => amount of tokens owned by Safe
mapping(address => uint) public _balances;

function lockERC20(address tokenAddress, address owner, address recipient, uint amount) internal {
IERC20 erc20 = IERC20(tokenAddress);
erc20.transferFrom(owner, recipient, amount);

_balances[tokenAddress] = _balances[tokenAddress].add(amount);
}

function releaseERC20(address tokenAddress, address owner, address recipient, uint amount) internal {
IERC20 erc20 = IERC20(tokenAddress);
erc20.transferFrom(owner, recipient, amount);

_balances[tokenAddress] = _balances[tokenAddress].sub(amount);
}
}
25 changes: 25 additions & 0 deletions on-chain/evm-contracts/contracts/ERC721Safe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
pragma solidity ^0.5.12;

import "./helpers/SafeMath.sol";
import "./erc/ERC721/IERC721.sol";

contract ERC721Safe {
using SafeMath for uint;

// ERC721 contract => amount of tokens owned by Safe
mapping(address => uint) public _balances;

function lockERC721(address tokenAddress, address owner, address recipient, uint tokenID) internal {
IERC721 erc721 = IERC721(tokenAddress);
erc721.transferFrom(owner, recipient, tokenID);

_balances[tokenAddress] = _balances[tokenAddress].add(1);
}

function releaseERC721(address tokenAddress, address owner, address recipient, uint tokenID) internal {
IERC721 erc721 = IERC721(tokenAddress);
erc721.transferFrom(owner, recipient, tokenID);

_balances[tokenAddress] = _balances[tokenAddress].sub(1);
}
}
2 changes: 1 addition & 1 deletion on-chain/evm-contracts/contracts/Emitter.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity 0.5.12;
pragma solidity ^0.5.12;

import "./Safe.sol";

Expand Down
2 changes: 1 addition & 1 deletion on-chain/evm-contracts/contracts/Receiver.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity 0.5.12;
pragma solidity ^0.5.12;

import "./interfaces/IHandler.sol";

Expand Down
2 changes: 1 addition & 1 deletion on-chain/evm-contracts/contracts/Safe.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity 0.5.12;
pragma solidity ^0.5.12;

import "./helpers/SafeMath.sol";
import "./erc/ERC20/ERC20.sol";
Expand Down
210 changes: 210 additions & 0 deletions on-chain/evm-contracts/contracts/Validator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
pragma solidity ^0.5.12;

import "./interfaces/IValidator.sol";
import "./helpers/SafeMath.sol";

contract Validator is IValidator {
using SafeMath for uint;

uint public _validatorThreshold;
uint public _totalValidators;
ValidatorThresholdProposal private _currentValidatorThresholdProposal;
// ValidatorActionType and _validatorActionTypeStrings must be kept
// the same length and order to function properly
string[] _validatorActionTypeStrings = ["remove", "add"];
// VoteStatus and _voteStatusStrings must be kept
// the same length and order to function properly
string[] _voteStatusStrings = ["inactive", "active"];

struct ValidatorProposal {
address _proposedAddress;
ValidatorActionType _action;
mapping(address => bool) _votes;
uint _numYes;
uint _numNo;
VoteStatus _status;
}

struct ValidatorThresholdProposal {
uint _proposedValue;
mapping(address => bool) _votes;
uint _numYes;
uint _numNo;
VoteStatus _status;
}

// Validator Address => whether they are a Validator
mapping(address => bool) public _validators;
// Validator Address => ValidatorProposal
mapping(address => ValidatorProposal) public _validatorProposals;

event ValidatorProposalCreated(address indexed proposedAddress, ValidatorActionType indexed validatorActionType);
event ValidatorProposalVote(address indexed proposedAddress, Vote vote);
event ValidatorAdded(address indexed validatorAddress);
event ValidatorRemoved(address indexed validatorAddress);
event ValidatorThresholdProposalCreated(uint indexed proposedValue);
event ValidatorThresholdProposalVote(Vote vote);
event ValidatorThresholdChanged(uint indexed newThreshold);

modifier _onlyValidators() {
require(_validators[msg.sender], "sender is not a validator");
_;
}

constructor (address[] memory initialValidators, uint initialValidatorThreshold) public {
for (uint i; i < initialValidators.length; i++) {
_addValidator(initialValidators[i]);
}

_validatorThreshold = initialValidatorThreshold;
}

function isValidator(address validatorAddress) public returns (bool) {
return _validators[validatorAddress];
}

function getValidatorThreshold() public view returns (uint) {
return _validatorThreshold;
}

function getTotalValidators() public returns (uint) {
return _totalValidators;
}

function getCurrentValidatorThresholdProposal() public view returns (
uint, uint, uint, string memory) {
return (
_currentValidatorThresholdProposal._proposedValue,
_currentValidatorThresholdProposal._numYes,
_currentValidatorThresholdProposal._numNo,
_voteStatusStrings[uint(_currentValidatorThresholdProposal._status)]);
}

function getValidatorProposal(address proposedAddress) public view returns (
address, string memory, uint, uint, string memory) {
ValidatorProposal memory validatorProposal = _validatorProposals[proposedAddress];
return (
validatorProposal._proposedAddress,
_validatorActionTypeStrings[uint(validatorProposal._action)],
validatorProposal._numYes,
validatorProposal._numNo,
_voteStatusStrings[uint(validatorProposal._status)]);
}

function createValidatorProposal(address proposedAddress, ValidatorActionType action) public _onlyValidators {
require(uint(action) <= 1, "action out of the vote enum range");
require(action == ValidatorActionType.Remove && _validators[proposedAddress] == true, "address is not a validator");
require(action == ValidatorActionType.Add && _validators[proposedAddress] == false, "address is currently a validator");
require(_validatorProposals[proposedAddress]._status == VoteStatus.Inactive, "there is already an active proposal for this address");

_validatorProposals[proposedAddress] = ValidatorProposal({
_proposedAddress: proposedAddress,
_action: action,
_numYes: 1, // Creator always votes in favour
_numNo: 0,
_status: VoteStatus.Active
});

if (_validatorThreshold <= 1) {
_validatorProposals[proposedAddress]._status = VoteStatus.Inactive;
if (action == ValidatorActionType.Add) {
_addValidator(proposedAddress);
} else {
_removeValidator(proposedAddress);
}
}
// Record vote
_validatorProposals[proposedAddress]._votes[msg.sender] = true;
emit ValidatorProposalCreated(proposedAddress, action);
}

function voteValidatorProposal(address proposedAddress, Vote vote) public _onlyValidators {
require(_validatorProposals[proposedAddress]._status == VoteStatus.Active, "there is no active proposal for this address");
require(!_validatorProposals[proposedAddress]._votes[msg.sender], "validator has already voted");
require(uint(vote) <= 1, "vote out of the vote enum range");

// Cast vote
if (vote == Vote.Yes) {
_validatorProposals[proposedAddress]._numYes++;
} else {
_validatorProposals[proposedAddress]._numNo++;
}

// Record vote
_validatorProposals[proposedAddress]._votes[msg.sender] = true;
emit ValidatorProposalVote(proposedAddress, vote);

// Todo: Edge case if validator threshold changes?
// Todo: For a proposal to pass does the number of yes votes just need to be higher than the threshold, or does it also have to be greater than the number of no votes?
if (_validatorProposals[proposedAddress]._numYes >= _validatorThreshold) {
if (_validatorProposals[proposedAddress]._action == ValidatorActionType.Add) {
_addValidator(proposedAddress);
} else {
_removeValidator(proposedAddress);
}

_validatorProposals[proposedAddress]._status = VoteStatus.Inactive;
} else if (_totalValidators.sub(_validatorProposals[proposedAddress]._numNo) < _validatorThreshold) {
_validatorProposals[proposedAddress]._status = VoteStatus.Inactive;
}
}

function createValidatorThresholdProposal(uint proposedValue) public _onlyValidators {
require(_currentValidatorThresholdProposal._status == VoteStatus.Inactive, "a proposal is currently active");
require(proposedValue <= _totalValidators, "proposed value cannot be greater than the total number of validators");

_currentValidatorThresholdProposal = ValidatorThresholdProposal({
_proposedValue: proposedValue,
_numYes: 1, // Creator always votes in favour
_numNo: 0,
_status: VoteStatus.Active
});

if (_validatorThreshold <= 1) {
_validatorThreshold = _currentValidatorThresholdProposal._proposedValue;
_currentValidatorThresholdProposal._status = VoteStatus.Inactive;
emit ValidatorThresholdChanged(proposedValue);
}
// Record vote
_currentValidatorThresholdProposal._votes[msg.sender] = true;
emit ValidatorThresholdProposalCreated(proposedValue);
}

function voteValidatorThresholdProposal(Vote vote) public _onlyValidators {
require(_currentValidatorThresholdProposal._status == VoteStatus.Active, "no proposal is currently active");
require(!_currentValidatorThresholdProposal._votes[msg.sender], "validator has already voted");
require(uint(vote) <= 1, "vote out of the vote enum range");

// Cast vote
if (vote == Vote.Yes) {
_currentValidatorThresholdProposal._numYes++;
} else {
_currentValidatorThresholdProposal._numNo++;
}

_currentValidatorThresholdProposal._votes[msg.sender] = true;
emit ValidatorThresholdProposalVote(vote);

// Todo: Edge case if validator threshold changes?
// Todo: For a proposal to pass does the number of yes votes just need to be higher than the threshold, or does it also have to be greater than the number of no votes?
if (_currentValidatorThresholdProposal._numYes >= _validatorThreshold) {
_validatorThreshold = _currentValidatorThresholdProposal._proposedValue;
_currentValidatorThresholdProposal._status = VoteStatus.Inactive;
emit ValidatorThresholdChanged(_currentValidatorThresholdProposal._proposedValue);
} else if (_totalValidators.sub(_currentValidatorThresholdProposal._numNo) < _validatorThreshold) {
_currentValidatorThresholdProposal._status = VoteStatus.Inactive;
}
}

function _addValidator(address addr) internal {
_validators[addr] = true;
_totalValidators++;
emit ValidatorAdded(addr);
}

function _removeValidator(address addr) internal {
_validators[addr] = false;
_totalValidators--;
emit ValidatorRemoved(addr);
}
}
42 changes: 42 additions & 0 deletions on-chain/evm-contracts/contracts/handlers/ERC20Handler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
pragma solidity ^0.5.12;

import "../interfaces/IERC20Handler.sol";
import "../ERC20Safe.sol";
import "../erc/ERC20/ERC20Mintable.sol";
import "../interfaces/IDepositHandler.sol";

contract ERC20Handler is IERC20Handler, IDepositHandler, ERC20Safe {
address public _bridgeAddress;

modifier _onlyBridge() {
require(msg.sender == _bridgeAddress);
_;
}

constructor(address bridgeAddress) public {
_bridgeAddress = bridgeAddress;
}

function depositERC20(address tokenAddress, address owner, uint amount) public _onlyBridge {
lockERC20(tokenAddress, owner, address(this), amount);
}

function executeDeposit(bytes memory data) public {
address destinationChainTokenAddress;
address destinationRecipientAddress;
uint amount;

assembly {
destinationChainTokenAddress := mload(add(data, 0x20))
destinationRecipientAddress := mload(add(data, 0x40))
amount := mload(add(data, 0x60))
}

ERC20Mintable erc20 = ERC20Mintable(destinationChainTokenAddress);
erc20.mint(destinationRecipientAddress, amount);
}

function withdrawERC20(address tokenAddress, address recipient, uint amount) public _onlyBridge {
releaseERC20(tokenAddress, address(this), recipient, amount);
}
}
51 changes: 51 additions & 0 deletions on-chain/evm-contracts/contracts/handlers/ERC721Handler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
pragma solidity ^0.5.12;

import "../interfaces/IERC721Handler.sol";
import "../ERC721Safe.sol";
import "../interfaces/IDepositHandler.sol";
import "../erc/ERC721/ERC721Mintable.sol";

contract ERC721Handler is IERC721Handler, IDepositHandler, ERC721Safe {
address public _bridgeAddress;

modifier _onlyBridge() {
require(msg.sender == _bridgeAddress);
_;
}

constructor(address bridgeAddress) public {
_bridgeAddress = bridgeAddress;
}

function depositERC721(address tokenAddress, address owner, uint tokenID) public _onlyBridge {
lockERC721(tokenAddress, owner, address(this), tokenID);
}

function executeDeposit(bytes memory data) public {
address destinationChainTokenAddress;
address destinationRecipientAddress;
uint tokenID;
bytes memory extraData;

assembly {
destinationChainTokenAddress := mload(add(data, 0x20))
destinationRecipientAddress := mload(add(data, 0x40))
tokenID := mload(add(data, 0x60))
extraData := mload(0x40)
let lenextra := mload(add(0x80, data))
mstore(0x40, add(0x60, add(extraData, lenextra)))
calldatacopy(
extraData, // copy to extra
0xA0, // copy from calldata @ 0xA0
sub(calldatasize, 0xA0) // copy size (calldatasize - 0xA0)
)
}

ERC721Mintable erc721 = ERC721Mintable(destinationChainTokenAddress);
erc721.safeMint(destinationRecipientAddress, tokenID, extraData);
}

function withdrawERC721(address tokenAddress, address recipient, uint tokenID) public _onlyBridge {
releaseERC721(tokenAddress, address(this), recipient, tokenID);
}
}
Loading

0 comments on commit 19ad136

Please sign in to comment.