From b0344021b4b2903409cf35afc9dab9a441778f10 Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Sat, 3 Mar 2018 00:38:42 +0100 Subject: [PATCH 1/8] 1. Add ERC677 support 2. Add governance for validators 3. Make Home and Foreign Bridges operated from ValidatorsContract --- .editorconfig | 15 ++ contracts/BridgeDeploymentAddressStorage.sol | 10 ++ contracts/BridgeValidators.sol | 92 +++++------ contracts/ERC677.sol | 10 ++ contracts/ERC677Receiver.sol | 6 + contracts/ForeignBridge.sol | 152 ++++++++++-------- contracts/HomeBridge.sol | 105 +++--------- contracts/IBridgeValidators.sol | 7 + ...n.sol => IBurnableMintableERC677Token.sol} | 6 +- contracts/POA20.sol | 57 +++++-- contracts/Validatable.sol | 17 ++ contracts/libraries/Helpers.sol | 43 +++-- contracts/libraries/Message.sol | 20 ++- migrations/1_initial_migration.js | 4 +- migrations/2_bridge_deployment.js | 18 +++ truffle.js | 16 +- 16 files changed, 354 insertions(+), 224 deletions(-) create mode 100644 .editorconfig create mode 100644 contracts/BridgeDeploymentAddressStorage.sol create mode 100644 contracts/ERC677.sol create mode 100644 contracts/ERC677Receiver.sol create mode 100644 contracts/IBridgeValidators.sol rename contracts/{IBurnableMintableERC827Token.sol => IBurnableMintableERC677Token.sol} (52%) create mode 100644 contracts/Validatable.sol create mode 100644 migrations/2_bridge_deployment.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..5d196c7c9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: http://EditorConfig.org + +root = true + +[*] +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[*.ts] +indent_style = tab + +[{package.json,.travis.yml}] +indent_style = space +indent_size = 4 diff --git a/contracts/BridgeDeploymentAddressStorage.sol b/contracts/BridgeDeploymentAddressStorage.sol new file mode 100644 index 000000000..aa90c7a1f --- /dev/null +++ b/contracts/BridgeDeploymentAddressStorage.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.4.19; + + +contract BridgeDeploymentAddressStorage { + uint256 public deployedAtBlock; + + function BridgeDeploymentAddressStorage() public { + deployedAtBlock = block.number; + } +} diff --git a/contracts/BridgeValidators.sol b/contracts/BridgeValidators.sol index 00a2b23d9..255b483cc 100644 --- a/contracts/BridgeValidators.sol +++ b/contracts/BridgeValidators.sol @@ -1,52 +1,54 @@ pragma solidity ^0.4.18; -import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; +import "zeppelin-solidity/contracts/ownership/Ownable.sol"; +import "./IBridgeValidators.sol"; -contract BridgeValidators is Ownable { - // Event created on validator gets added - event ValidatorAdded (address validator); - event ValidatorRemoved (address validator); - uint8 requiredValidators = 0; - uint256 public validatorCount = 0; +contract BridgeValidators is Ownable, IBridgeValidators { - mapping (address => bool) public validators; + // Event created on validator gets added + event ValidatorAdded (address validator); + event ValidatorRemoved (address validator); + uint8 requiredValidators = 0; + uint256 public validatorCount = 0; - function BridgeValidators(uint8 _requiredValidators,address[] _initialValidators) public { - require(_requiredValidators != 0); - require(_initialValidators.length >= _requiredValidators); - setRequiredValidators(_requiredValidators); - for (uint i = 0; i < _initialValidators.length; i++) { - require(!isValidator(_initialValidators[i]) && _initialValidators[i] != address(0)); - addValidator(_initialValidators[i]); + mapping (address => bool) public validators; + + function BridgeValidators(uint8 _requiredValidators, address[] _initialValidators) public Ownable() { + require(_requiredValidators != 0); + require(_initialValidators.length >= _requiredValidators); + validatorCount = _initialValidators.length; + for (uint i = 0; i < _initialValidators.length; i++) { + require(!isValidator(_initialValidators[i]) && _initialValidators[i] != address(0)); + addValidator(_initialValidators[i]); + } + setRequiredValidators(_requiredValidators); + } + + function addValidator(address _validator) public onlyOwner { + assert(validators[_validator] != true); + validatorCount++; + validators[_validator] = true; + ValidatorAdded(_validator); + } + + function removeValidator(address _validator) public onlyOwner { + require(validatorCount > requiredValidators); + validators[_validator] = false; + validatorCount--; + ValidatorRemoved(_validator); + } + + function setRequiredValidators(uint8 _requiredValidators) public onlyOwner { + require(validatorCount >= _requiredValidators); + requiredValidators = _requiredValidators; + } + + function isValidator(address _validator) public view returns(bool) { + return validators[_validator] == true; + } + + function requiredSignatures() public view returns(uint8) { + return requiredValidators; } - validatorCount = _initialValidators.length; - } - - function addValidator(address _validator) public onlyOwner { - assert(validators[_validator] != true); - validatorCount++; - validators[_validator] = true; - ValidatorAdded(_validator); - } - - function removeValidator(address _validator) public onlyOwner { - require(validatorCount > requiredValidators); - validators[_validator] = false; - validatorCount--; - ValidatorRemoved(_validator); - } - - function setRequiredValidators(uint8 _requiredValidators) public onlyOwner { - require(validatorCount >= _requiredValidators); - requiredValidators = _requiredValidators; - } - - function isValidator(address _validator) public view returns(bool) { - return (validators[_validator] == true); - } - - function onlyValidator(address _validator) public view returns(bool) { - return validators[_validator] == true; - } -} \ No newline at end of file +} diff --git a/contracts/ERC677.sol b/contracts/ERC677.sol new file mode 100644 index 000000000..daead733a --- /dev/null +++ b/contracts/ERC677.sol @@ -0,0 +1,10 @@ +pragma solidity 0.4.19; +import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; + + +contract ERC677 is ERC20 { + event Transfer(address indexed from, address indexed to, uint value, bytes data); + + function transferAndCall(address, uint, bytes) returns (bool); + +} diff --git a/contracts/ERC677Receiver.sol b/contracts/ERC677Receiver.sol new file mode 100644 index 000000000..56fabc5cd --- /dev/null +++ b/contracts/ERC677Receiver.sol @@ -0,0 +1,6 @@ +pragma solidity ^0.4.19; + + +contract ERC677Receiver { + function onTokenTransfer(address _from, uint _value, bytes _data) external returns(bool); +} diff --git a/contracts/ForeignBridge.sol b/contracts/ForeignBridge.sol index f16ed6dac..dd29cd7c3 100644 --- a/contracts/ForeignBridge.sol +++ b/contracts/ForeignBridge.sol @@ -2,74 +2,88 @@ pragma solidity 0.4.19; import "./libraries/Helpers.sol"; import "./libraries/Message.sol"; import "./libraries/MessageSigning.sol"; -import "./BridgeValidators.sol"; - -contract ForeignBridge { +import "./IBridgeValidators.sol"; +import "./Validatable.sol"; +import "./BridgeDeploymentAddressStorage.sol"; +import "./IBurnableMintableERC677Token.sol"; +import "./ERC677Receiver.sol"; + +contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressStorage { + uint256 public gasLimitDepositRelay; + uint256 public gasLimitWithdrawConfirm; + uint256 homeGasPrice = 1000000000 wei; + mapping (bytes32 => bytes) messages; + mapping (bytes32 => bytes) signatures; + mapping (bytes32 => bool) messages_signed; + mapping (bytes32 => uint) num_messages_signed; mapping (bytes32 => bool) deposits_signed; - mapping (bytes32 => uint8) num_deposits_signed; - mapping (bytes32 => bool) mintRequestsDone; - mapping (bytes32 => uint8) withdrawRequests; + mapping (bytes32 => uint) num_deposits_signed; - /// Pending signatures and authorities who confirmed them - BridgeValidators public validatorsContract; + mapping (bytes32 => bool) tokenAddressApprovalSigns; + mapping (address => uint256) public numTokenAddressApprovalSigns; - /// Token contract which the bridge has full ownership to burn and mint - address public token; + IBurnableMintableERC677Token public erc677token; /// triggered when relay of deposit from HomeBridge is complete event Deposit(address recipient, uint value); /// Event created on money withdraw. - event Withdraw(address recipient, uint value); + event Withdraw(address recipient, uint256 value, uint256 homeGasPrice); /// Collected signatures which should be relayed to home chain. event CollectedSignatures(address authorityResponsibleForRelay, bytes32 messageHash); + event TokenAddress(address token); + + event GasConsumptionLimitsUpdated(uint256 gasLimitDepositRelay, uint256 gasLimitWithdrawConfirm); + function ForeignBridge( - address _token, - address _validatorsContract - ) public - { - require(_token != address(0) && _validatorsContract != address(0)); - validatorsContract = BridgeValidators(_validatorsContract); - token = _token; + address _validatorContract, + address _erc677token + ) public Validatable(_validatorContract) { + erc677token = IBurnableMintableERC677Token(_erc677token); } + function setGasLimitDepositRelay(uint256 gas) onlyValidator { + gasLimitDepositRelay = gas; - /// Used to deposit money to the contract. - /// - /// deposit recipient (bytes20) - /// deposit value (uint) - /// mainnet transaction hash (bytes32) // to avoid transaction duplication - function deposit(address recipient, uint256 value, bytes32 transactionHash) public { - require(validatorsContract.onlyValidator(msg.sender)); + GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm); + } + + function setGasLimitWithdrawConfirm(uint256 gas) onlyValidator { + gasLimitWithdrawConfirm = gas; + + GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm); + } + + function deposit(address recipient, uint value, bytes32 transactionHash) public onlyValidator { + require(erc677token != address(0x0)); + + // Protection from misbehaing authority bytes32 hash_msg = keccak256(recipient, value, transactionHash); bytes32 hash_sender = keccak256(msg.sender, hash_msg); - // Prevents Duplicated deposits + // Duplicated deposits require(!deposits_signed[hash_sender]); deposits_signed[hash_sender] = true; - uint8 signed = num_deposits_signed[hash_msg] + 1; + uint signed = num_deposits_signed[hash_msg] + 1; num_deposits_signed[hash_msg] = signed; - if (signed == requiredValidators) { - // token.mint(recipient, value); + if (signed == validatorContract.requiredSignatures()) { + // If the bridge contract does not own enough tokens to transfer + // it will couse funds lock on the home side of the bridge + erc677token.mint(recipient, value); Deposit(recipient, value); } } - /// Transfer `value` from `msg.sender`s local balance (on `foreign` chain) to `recipient` on `home` chain. - /// - /// immediately decreases `msg.sender`s local balance. - /// emits a `Withdraw` event which will be picked up by the bridge authorities. - /// bridge authorities will then sign off (by calling `submitSignature`) on a message containing `value`, - /// `recipient` and the `hash` of the transaction on `foreign` containing the `Withdraw` event. - /// once `requiredSignatures` are collected a `CollectedSignatures` event will be emitted. - /// an authority will pick up `CollectedSignatures` an call `HomeBridge.withdraw` - /// which transfers `value - relayCost` to `recipient` completing the transfer. - function transferHomeViaRelay(address recipient, uint256 value) public { - Withdraw(recipient, value); + function onTokenTransfer(address _from, uint256 _value, bytes _data) external returns(bool) { + require(erc677token != address(0x0)); + require(msg.sender == address(erc677token)); + erc677token.burn(_value); + Withdraw(_from, _value, homeGasPrice); + return true; } /// Should be used as sync tool @@ -80,34 +94,46 @@ contract ForeignBridge { /// withdrawal recipient (bytes20) /// withdrawal value (uint) /// foreign transaction hash (bytes32) // to avoid transaction duplication - function submitSignature(bytes signature, bytes message) public { - require(validatorsContract.onlyValidator(msg.sender)); - // Validate submited signatures - require(MessageSigning.recoverAddressFromSignedMessage(signature, message) == msg.sender); + function submitSignature(bytes signature, bytes message) public onlyValidator { + // ensure that `signature` is really `message` signed by `msg.sender` + require(msg.sender == MessageSigning.recoverAddressFromSignedMessage(signature, message)); + + require(message.length == 116); + bytes32 hash = keccak256(message); + bytes32 hash_sender = keccak256(msg.sender, hash); + + uint signed = num_messages_signed[hash_sender] + 1; + + if (signed > 1) { + // Duplicated signatures + require(!messages_signed[hash_sender]); + } + else { + // check if it will really reduce gas usage in case of the second transaction + // with the same hash + messages[hash] = message; + } + messages_signed[hash_sender] = true; - // Valid withdraw message must have 84 bytes - require(message.length == 84); - var hash = keccak256(message); + bytes32 sign_idx = keccak256(hash, (signed-1)); + signatures[sign_idx] = signature; - // Duplicated signatures - // require(!Helpers.addressArrayContains(signatures[hash].signed, msg.sender)); - // signatures[hash].message = message; - // signatures[hash].signed.push(msg.sender); - // signatures[hash].signatures.push(signature); + num_messages_signed[hash_sender] = signed; // TODO: this may cause troubles if requiredSignatures len is changed - // if (signatures[hash].signed.length == requiredSignatures) { + if (signed == validatorContract.requiredSignatures()) { CollectedSignatures(msg.sender, hash); - // } + } } - /// Get signature - // function signature(bytes32 hash, uint index) public view returns (bytes) { - // return signatures[hash].signatures[index]; - // } + function signature(bytes32 hash, uint index) public view returns (bytes) { + bytes32 sign_idx = keccak256(hash, index); + return signatures[sign_idx]; + } + + /// Get message + function message(bytes32 hash) public view returns (bytes) { + return messages[hash]; + } - // /// Get message - // function message(bytes32 hash) public view returns (bytes) { - // return signatures[hash].message; - // } -} \ No newline at end of file +} diff --git a/contracts/HomeBridge.sol b/contracts/HomeBridge.sol index dedf48d3c..b861bdef8 100644 --- a/contracts/HomeBridge.sol +++ b/contracts/HomeBridge.sol @@ -1,63 +1,25 @@ -pragma solidity 0.4.19; -import "./libraries/Helpers.sol"; +pragma solidity ^0.4.19; +import "./libraries/SafeMath.sol"; import "./libraries/Message.sol"; import "./libraries/MessageSigning.sol"; -import "./libraries/SafeMath.sol"; +import "./IBridgeValidators.sol"; +import "./Validatable.sol"; +import "./BridgeDeploymentAddressStorage.sol"; -contract HomeBridge { - /// Number of authorities signatures required to withdraw the money. - /// - /// Must be lesser than number of authorities. - using SafeMath for uint256; - uint256 public requiredSignatures; - /// The gas cost of calling `HomeBridge.withdraw`. - /// - /// Is subtracted from `value` on withdraw. - /// recipient pays the relaying authority for withdraw. - /// this shuts down attacks that exhaust authorities funds on home chain. +contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { + using SafeMath for uint256; + uint256 public gasLimitWithdrawRelay; uint256 public estimatedGasCostOfWithdraw; + mapping (bytes32 => bool) withdraws; - /// Contract authorities. - address[] public authorities; - - /// Used foreign transaction hashes. - mapping (bytes32 => bool) public withdraws; - - /// Event created on money deposit. + event GasConsumptionLimitsUpdated(uint256 gas); event Deposit (address recipient, uint256 value); - - /// Event created on money withdraw. event Withdraw (address recipient, uint256 value); - /// Multisig authority validation - modifier allAuthorities(uint8[] v, bytes32[] r, bytes32[] s, bytes message) { - var hash = MessageSigning.hashMessage(message); - var used = new address[](requiredSignatures); - - require(requiredSignatures <= v.length); - - for (uint256 i = 0; i < requiredSignatures; i++) { - var a = ecrecover(hash, v[i], r[i], s[i]); - require(Helpers.addressArrayContains(authorities, a)); - require(!Helpers.addressArrayContains(used, a)); - used[i] = a; - } - _; - } - - /// Constructor. - function HomeBridge( - uint256 requiredSignaturesParam, - address[] authoritiesParam, - uint256 estimatedGasCostOfWithdrawParam - ) public - { - require(requiredSignaturesParam != 0); - require(requiredSignaturesParam <= authoritiesParam.length); - requiredSignatures = requiredSignaturesParam; - authorities = authoritiesParam; - estimatedGasCostOfWithdraw = estimatedGasCostOfWithdrawParam; + function HomeBridge ( + address _validatorContract + ) public Validatable(_validatorContract) { } /// Should be used to deposit money. @@ -65,47 +27,28 @@ contract HomeBridge { Deposit(msg.sender, msg.value); } - /// to be called by authorities to check - /// whether they withdraw message should be relayed or whether it - /// is too low to cover the cost of calling withdraw and can be ignored - function isMessageValueSufficientToCoverRelay(bytes message) public view returns (bool) { - return Message.getValue(message) > getWithdrawRelayCost(); + function setGasLimitWithdrawRelay(uint256 _gas) public onlyValidator { + gasLimitWithdrawRelay = _gas; + GasConsumptionLimitsUpdated(gasLimitWithdrawRelay); } - /// an upper bound to the cost of relaying a withdraw by calling HomeBridge.withdraw - function getWithdrawRelayCost() public view returns (uint256) { - return estimatedGasCostOfWithdraw.mul(tx.gasprice); - } + function withdraw(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) public { + require(message.length == 116); + // require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract)); - /// Used to withdraw money from the contract. - /// - /// message contains: - /// withdrawal recipient (bytes20) - /// withdrawal value (uint) - /// foreign transaction hash (bytes32) // to avoid transaction duplication - /// - /// NOTE that anyone can call withdraw provided they have the message and required signatures! - function withdraw(uint8[] v, bytes32[] r, bytes32[] s, bytes message) public allAuthorities(v, r, s, message) { - require(message.length == 84); address recipient = Message.getRecipient(message); - uint value = Message.getValue(message); + uint256 value = Message.getValue(message); bytes32 hash = Message.getTransactionHash(message); - - // The following two statements guard against reentry into this function. - // Duplicated withdraw or reentry. + uint256 homeGasPrice = Message.getHomeGasPrice(message); + require((recipient == msg.sender) || (tx.gasprice == homeGasPrice)); require(!withdraws[hash]); // Order of operations below is critical to avoid TheDAO-like re-entry bug withdraws[hash] = true; - // this fails if `value` is not even enough to cover the relay cost. - // Authorities simply IGNORE withdraws where `value` can’t relay cost. - // Think of it as `value` getting burned entirely on the relay with no value left to pay out the recipient. - require(isMessageValueSufficientToCoverRelay(message)); - - uint estimatedWeiCostOfWithdraw = getWithdrawRelayCost(); + uint256 estimatedWeiCostOfWithdraw = estimatedGasCostOfWithdraw.mul(homeGasPrice); // charge recipient for relay cost - uint valueRemainingAfterSubtractingCost = value - estimatedWeiCostOfWithdraw; + uint256 valueRemainingAfterSubtractingCost = value.sub(estimatedWeiCostOfWithdraw); // pay out recipient recipient.transfer(valueRemainingAfterSubtractingCost); diff --git a/contracts/IBridgeValidators.sol b/contracts/IBridgeValidators.sol new file mode 100644 index 000000000..b90ee7af3 --- /dev/null +++ b/contracts/IBridgeValidators.sol @@ -0,0 +1,7 @@ +pragma solidity ^0.4.19; + + +interface IBridgeValidators { + function isValidator(address _validator) public view returns(bool); + function requiredSignatures() public view returns(uint8); +} diff --git a/contracts/IBurnableMintableERC827Token.sol b/contracts/IBurnableMintableERC677Token.sol similarity index 52% rename from contracts/IBurnableMintableERC827Token.sol rename to contracts/IBurnableMintableERC677Token.sol index 13e19b40d..7dc306ad1 100644 --- a/contracts/IBurnableMintableERC827Token.sol +++ b/contracts/IBurnableMintableERC677Token.sol @@ -1,8 +1,8 @@ pragma solidity 0.4.19; -import "zeppelin-solidity/contracts/token/ERC827/ERC827.sol"; +import "./ERC677.sol"; -contract IBurnableMintableERC827Token is ERC827 { +contract IBurnableMintableERC677Token is ERC677 { function mint(address, uint256) public returns (bool); function burn(uint256 _value) public; -} \ No newline at end of file +} diff --git a/contracts/POA20.sol b/contracts/POA20.sol index 164f270a6..7848060f3 100644 --- a/contracts/POA20.sol +++ b/contracts/POA20.sol @@ -1,21 +1,54 @@ -pragma solidity 0.4.19; -import "zeppelin-solidity/contracts/token/ERC827/ERC827Token.sol"; +pragma solidity ^0.4.19; + import "zeppelin-solidity/contracts/token/ERC20/PausableToken.sol"; import "zeppelin-solidity/contracts/token/ERC20/BurnableToken.sol"; import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; import "zeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol"; -import "./IBurnableMintableERC827Token.sol"; +import "./IBurnableMintableERC677Token.sol"; +import "./ERC677Receiver.sol"; + + +contract POA20 is + IBurnableMintableERC677Token, + DetailedERC20, + BurnableToken, + MintableToken, + PausableToken { + function POA20( + string _name, + string _symbol, + uint8 _decimals) + public DetailedERC20(_name, _symbol, _decimals) {} + modifier validRecipient(address _recipient) { + require(_recipient != address(0) && _recipient != address(this)); + _; + } + + function transferAndCall(address _to, uint _value, bytes _data) + public validRecipient(_to) returns (bool) + { + super.transfer(_to, _value); + Transfer(msg.sender, _to, _value, _data); + if (isContract(_to)) { + contractFallback(_to, _value, _data); + } + return true; + } -contract POA20 is IBurnableMintableERC827Token, DetailedERC20, BurnableToken, MintableToken, PausableToken, ERC827Token { - function POA20(string _name, string _symbol, uint8 _decimals) public DetailedERC20(_name, _symbol, _decimals) { + function contractFallback(address _to, uint _value, bytes _data) + private + { + ERC677Receiver receiver = ERC677Receiver(_to); + receiver.onTokenTransfer(msg.sender, _value, _data); } - /** - * @dev Burns a specific amount of tokens. - * @param _value The amount of token to be burned. - **/ - function burn(uint256 _value) public onlyOwner { - super.burn(_value); + function isContract(address _addr) + private + returns (bool hasCode) + { + uint length; + assembly { length := extcodesize(_addr) } + return length > 0; } -} \ No newline at end of file +} diff --git a/contracts/Validatable.sol b/contracts/Validatable.sol new file mode 100644 index 000000000..915e3fbf7 --- /dev/null +++ b/contracts/Validatable.sol @@ -0,0 +1,17 @@ +pragma solidity ^0.4.19; +import "./IBridgeValidators.sol"; + + +contract Validatable { + IBridgeValidators public validatorContract; + + modifier onlyValidator() { + require(validatorContract.isValidator(msg.sender)); + _; + } + + function Validatable(address _validatorContract) public { + require(_validatorContract != address(0)); + validatorContract = IBridgeValidators(_validatorContract); + } +} diff --git a/contracts/libraries/Helpers.sol b/contracts/libraries/Helpers.sol index 25ac80c65..d3c1e99cf 100644 --- a/contracts/libraries/Helpers.sol +++ b/contracts/libraries/Helpers.sol @@ -1,11 +1,10 @@ pragma solidity 0.4.19; +import "../IBridgeValidators.sol"; +import "./MessageSigning.sol"; -/// general helpers. -/// `internal` so they get compiled into contracts using them. library Helpers { - /// returns whether `array` contains `value`. function addressArrayContains(address[] array, address value) internal pure returns (bool) { - for (uint i = 0; i < array.length; i++) { + for (uint256 i = 0; i < array.length; i++) { if (array[i] == value) { return true; } @@ -13,12 +12,10 @@ library Helpers { return false; } - // returns the digits of `inputValue` as a string. - // example: `uintToString(12345678)` returns `"12345678"` - function uintToString(uint inputValue) internal pure returns (string) { + function uintToString(uint256 inputValue) internal pure returns (string) { // figure out the length of the resulting string - uint length = 0; - uint currentValue = inputValue; + uint256 length = 0; + uint256 currentValue = inputValue; do { length++; currentValue /= 10; @@ -26,7 +23,7 @@ library Helpers { // allocate enough memory bytes memory result = new bytes(length); // construct the string backwards - uint i = length - 1; + uint256 i = length - 1; currentValue = inputValue; do { result[i--] = byte(48 + currentValue % 10); @@ -34,4 +31,28 @@ library Helpers { } while (currentValue != 0); return string(result); } -} \ No newline at end of file + + function hasEnoughValidSignatures( + bytes _message, + uint8[] _vs, + bytes32[] _rs, + bytes32[] _ss, + IBridgeValidators _validatorContract) internal view returns (bool) { + uint8 _requiredSignatures = _validatorContract.requiredSignatures(); + require(_vs.length < _requiredSignatures); + bytes32 hash = MessageSigning.hashMessage(_message); + address[] memory encounteredAddresses = new address[](_requiredSignatures); + + for (uint8 i = 0; i < _requiredSignatures; i++) { + address recoveredAddress = ecrecover(hash, _vs[i], _rs[i], _ss[i]); + // only signatures by addresses in `addresses` are allowed + require(_validatorContract.isValidator(recoveredAddress)); + // duplicate signatures are not allowed + if (addressArrayContains(encounteredAddresses, recoveredAddress)) { + return false; + } + encounteredAddresses[i] = recoveredAddress; + } + return true; + } +} diff --git a/contracts/libraries/Message.sol b/contracts/libraries/Message.sol index 87e1e97b3..53d1223a1 100644 --- a/contracts/libraries/Message.sol +++ b/contracts/libraries/Message.sol @@ -3,10 +3,11 @@ pragma solidity 0.4.19; library Message { // layout of message :: bytes: - // offset 0: 32 bytes :: uint (little endian) - message length + // offset 0: 32 bytes :: uint256 - message length // offset 32: 20 bytes :: address - recipient address - // offset 52: 32 bytes :: uint (little endian) - value + // offset 52: 32 bytes :: uint256 - value // offset 84: 32 bytes :: bytes32 - transaction hash + // offset 116: 32 bytes :: uint256 - home gas price // bytes 1 to 32 are 0 because message length is stored as little endian. // mload always reads 32 bytes. @@ -28,8 +29,8 @@ library Message { return recipient; } - function getValue(bytes message) internal pure returns (uint) { - uint value; + function getValue(bytes message) internal pure returns (uint256) { + uint256 value; // solium-disable-next-line security/no-inline-assembly assembly { value := mload(add(message, 52)) @@ -45,4 +46,13 @@ library Message { } return hash; } -} \ No newline at end of file + + function getHomeGasPrice(bytes message) internal pure returns (uint256) { + uint256 gasPrice; + // solium-disable-next-line security/no-inline-assembly + assembly { + gasPrice := mload(add(message, 116)) + } + return gasPrice; + } +} diff --git a/migrations/1_initial_migration.js b/migrations/1_initial_migration.js index 4d5f3f9b0..95103d288 100644 --- a/migrations/1_initial_migration.js +++ b/migrations/1_initial_migration.js @@ -1,5 +1,5 @@ var Migrations = artifacts.require("./Migrations.sol"); -module.exports = function(deployer) { - deployer.deploy(Migrations); +module.exports = async function(deployer) { + await deployer.deploy(Migrations); }; diff --git a/migrations/2_bridge_deployment.js b/migrations/2_bridge_deployment.js new file mode 100644 index 000000000..538060aeb --- /dev/null +++ b/migrations/2_bridge_deployment.js @@ -0,0 +1,18 @@ +const POA20 = artifacts.require("./POA20.sol"); +const BridgeValidators = artifacts.require("./BridgeValidators.sol"); +const HomeBridge = artifacts.require("./HomeBridge.sol"); +const ForeignBridge = artifacts.require("./ForeignBridge.sol"); + +module.exports = async function(deployer, network, accounts) { + await deployer.deploy(POA20, "POA ERC20 on Foundation", "POA20", 18) + const erc677token = await POA20.deployed() + await deployer.deploy(BridgeValidators, '1', [accounts[0]]); + const validatorContract = await BridgeValidators.deployed(); + await deployer.deploy(HomeBridge, validatorContract.address); + await deployer.deploy(ForeignBridge, validatorContract.address, erc677token.address); + const foreignBridge = await ForeignBridge.deployed(); + + await erc677token.transferOwnership(foreignBridge.address) + console.log('all is done') + +}; diff --git a/truffle.js b/truffle.js index a6330d6d5..a1ff07dca 100644 --- a/truffle.js +++ b/truffle.js @@ -1,4 +1,16 @@ module.exports = { - // See - // to customize your Truffle configuration! + networks: { + development: { + host: "localhost", + port: 7545, + network_id: "*" // Match any network id + }, + parity: { + host: "localhost", + port: "8591", + network_id: "*", + gas: 4700000, + gasPrice: 1000000000 + } + } }; From 78977401b2176ed0faa4041923d7ed183d94d2d9 Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Sat, 3 Mar 2018 01:16:38 +0100 Subject: [PATCH 2/8] Provide flat contracts Fixes #2 --- flats/BridgeValidators_flat.sol | 101 +++++++ flats/ForeignBridge_flat.sol | 409 ++++++++++++++++++++++++++ flats/HomeBridge_flat.sol | 282 ++++++++++++++++++ flats/POA20_flat.sol | 491 ++++++++++++++++++++++++++++++++ 4 files changed, 1283 insertions(+) create mode 100644 flats/BridgeValidators_flat.sol create mode 100644 flats/ForeignBridge_flat.sol create mode 100644 flats/HomeBridge_flat.sol create mode 100644 flats/POA20_flat.sol diff --git a/flats/BridgeValidators_flat.sol b/flats/BridgeValidators_flat.sol new file mode 100644 index 000000000..174ff3683 --- /dev/null +++ b/flats/BridgeValidators_flat.sol @@ -0,0 +1,101 @@ +pragma solidity 0.4.20; + +// File: contracts/IBridgeValidators.sol + +interface IBridgeValidators { + function isValidator(address _validator) public view returns(bool); + function requiredSignatures() public view returns(uint8); +} + +// File: zeppelin-solidity/contracts/ownership/Ownable.sol + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address public owner; + + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + function Ownable() public { + owner = msg.sender; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) public onlyOwner { + require(newOwner != address(0)); + OwnershipTransferred(owner, newOwner); + owner = newOwner; + } + +} + +// File: contracts/BridgeValidators.sol + +contract BridgeValidators is Ownable, IBridgeValidators { + + // Event created on validator gets added + event ValidatorAdded (address validator); + event ValidatorRemoved (address validator); + uint8 requiredValidators = 0; + uint256 public validatorCount = 0; + + mapping (address => bool) public validators; + + function BridgeValidators(uint8 _requiredValidators, address[] _initialValidators) public Ownable() { + require(_requiredValidators != 0); + require(_initialValidators.length >= _requiredValidators); + validatorCount = _initialValidators.length; + for (uint i = 0; i < _initialValidators.length; i++) { + require(!isValidator(_initialValidators[i]) && _initialValidators[i] != address(0)); + addValidator(_initialValidators[i]); + } + setRequiredValidators(_requiredValidators); + } + + function addValidator(address _validator) public onlyOwner { + assert(validators[_validator] != true); + validatorCount++; + validators[_validator] = true; + ValidatorAdded(_validator); + } + + function removeValidator(address _validator) public onlyOwner { + require(validatorCount > requiredValidators); + validators[_validator] = false; + validatorCount--; + ValidatorRemoved(_validator); + } + + function setRequiredValidators(uint8 _requiredValidators) public onlyOwner { + require(validatorCount >= _requiredValidators); + requiredValidators = _requiredValidators; + } + + function isValidator(address _validator) public view returns(bool) { + return validators[_validator] == true; + } + + function requiredSignatures() public view returns(uint8) { + return requiredValidators; + } +} diff --git a/flats/ForeignBridge_flat.sol b/flats/ForeignBridge_flat.sol new file mode 100644 index 000000000..59330be81 --- /dev/null +++ b/flats/ForeignBridge_flat.sol @@ -0,0 +1,409 @@ +pragma solidity 0.4.20; + +// File: contracts/BridgeDeploymentAddressStorage.sol + +contract BridgeDeploymentAddressStorage { + uint256 public deployedAtBlock; + + function BridgeDeploymentAddressStorage() public { + deployedAtBlock = block.number; + } +} + +// File: contracts/ERC677Receiver.sol + +contract ERC677Receiver { + function onTokenTransfer(address _from, uint _value, bytes _data) external returns(bool); +} + +// File: contracts/IBridgeValidators.sol + +interface IBridgeValidators { + function isValidator(address _validator) public view returns(bool); + function requiredSignatures() public view returns(uint8); +} + +// File: zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + function totalSupply() public view returns (uint256); + function balanceOf(address who) public view returns (uint256); + function transfer(address to, uint256 value) public returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); +} + +// File: zeppelin-solidity/contracts/token/ERC20/ERC20.sol + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20 is ERC20Basic { + function allowance(address owner, address spender) public view returns (uint256); + function transferFrom(address from, address to, uint256 value) public returns (bool); + function approve(address spender, uint256 value) public returns (bool); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: contracts/ERC677.sol + +contract ERC677 is ERC20 { + event Transfer(address indexed from, address indexed to, uint value, bytes data); + + function transferAndCall(address, uint, bytes) returns (bool); + +} + +// File: contracts/IBurnableMintableERC677Token.sol + +contract IBurnableMintableERC677Token is ERC677 { + function mint(address, uint256) public returns (bool); + function burn(uint256 _value) public; +} + +// File: contracts/Validatable.sol + +contract Validatable { + IBridgeValidators public validatorContract; + + modifier onlyValidator() { + require(validatorContract.isValidator(msg.sender)); + _; + } + + function Validatable(address _validatorContract) public { + require(_validatorContract != address(0)); + validatorContract = IBridgeValidators(_validatorContract); + } +} + +// File: contracts/libraries/MessageSigning.sol + +// import "./Helpers.sol"; +library Helpers { + function addressArrayContains(address[] array, address value) internal pure returns (bool) { + for (uint256 i = 0; i < array.length; i++) { + if (array[i] == value) { + return true; + } + } + return false; + } + + function uintToString(uint256 inputValue) internal pure returns (string) { + // figure out the length of the resulting string + uint256 length = 0; + uint256 currentValue = inputValue; + do { + length++; + currentValue /= 10; + } while (currentValue != 0); + // allocate enough memory + bytes memory result = new bytes(length); + // construct the string backwards + uint256 i = length - 1; + currentValue = inputValue; + do { + result[i--] = byte(48 + currentValue % 10); + currentValue /= 10; + } while (currentValue != 0); + return string(result); + } + + function hasEnoughValidSignatures( + bytes _message, + uint8[] _vs, + bytes32[] _rs, + bytes32[] _ss, + IBridgeValidators _validatorContract) internal view returns (bool) { + uint8 _requiredSignatures = _validatorContract.requiredSignatures(); + require(_vs.length < _requiredSignatures); + bytes32 hash = MessageSigning.hashMessage(_message); + address[] memory encounteredAddresses = new address[](_requiredSignatures); + + for (uint8 i = 0; i < _requiredSignatures; i++) { + address recoveredAddress = ecrecover(hash, _vs[i], _rs[i], _ss[i]); + // only signatures by addresses in `addresses` are allowed + require(_validatorContract.isValidator(recoveredAddress)); + // duplicate signatures are not allowed + if (addressArrayContains(encounteredAddresses, recoveredAddress)) { + return false; + } + encounteredAddresses[i] = recoveredAddress; + } + return true; + } +} + +library MessageSigning { + function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) { + require(signature.length == 65); + bytes32 r; + bytes32 s; + bytes1 v; + // solium-disable-next-line security/no-inline-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := mload(add(signature, 0x60)) + } + return ecrecover(hashMessage(message), uint8(v), r, s); + } + + function hashMessage(bytes message) internal pure returns (bytes32) { + bytes memory prefix = "\x19Ethereum Signed Message:\n"; + return keccak256(prefix, Helpers.uintToString(message.length), message); + } +} + +// File: contracts/libraries/Helpers.sol + +library Helpers { + function addressArrayContains(address[] array, address value) internal pure returns (bool) { + for (uint256 i = 0; i < array.length; i++) { + if (array[i] == value) { + return true; + } + } + return false; + } + + function uintToString(uint256 inputValue) internal pure returns (string) { + // figure out the length of the resulting string + uint256 length = 0; + uint256 currentValue = inputValue; + do { + length++; + currentValue /= 10; + } while (currentValue != 0); + // allocate enough memory + bytes memory result = new bytes(length); + // construct the string backwards + uint256 i = length - 1; + currentValue = inputValue; + do { + result[i--] = byte(48 + currentValue % 10); + currentValue /= 10; + } while (currentValue != 0); + return string(result); + } + + function hasEnoughValidSignatures( + bytes _message, + uint8[] _vs, + bytes32[] _rs, + bytes32[] _ss, + IBridgeValidators _validatorContract) internal view returns (bool) { + uint8 _requiredSignatures = _validatorContract.requiredSignatures(); + require(_vs.length < _requiredSignatures); + bytes32 hash = MessageSigning.hashMessage(_message); + address[] memory encounteredAddresses = new address[](_requiredSignatures); + + for (uint8 i = 0; i < _requiredSignatures; i++) { + address recoveredAddress = ecrecover(hash, _vs[i], _rs[i], _ss[i]); + // only signatures by addresses in `addresses` are allowed + require(_validatorContract.isValidator(recoveredAddress)); + // duplicate signatures are not allowed + if (addressArrayContains(encounteredAddresses, recoveredAddress)) { + return false; + } + encounteredAddresses[i] = recoveredAddress; + } + return true; + } +} + +// File: contracts/libraries/Message.sol + +library Message { + // layout of message :: bytes: + // offset 0: 32 bytes :: uint256 - message length + // offset 32: 20 bytes :: address - recipient address + // offset 52: 32 bytes :: uint256 - value + // offset 84: 32 bytes :: bytes32 - transaction hash + // offset 116: 32 bytes :: uint256 - home gas price + + // bytes 1 to 32 are 0 because message length is stored as little endian. + // mload always reads 32 bytes. + // so we can and have to start reading recipient at offset 20 instead of 32. + // if we were to read at 32 the address would contain part of value and be corrupted. + // when reading from offset 20 mload will read 12 zero bytes followed + // by the 20 recipient address bytes and correctly convert it into an address. + // this saves some storage/gas over the alternative solution + // which is padding address to 32 bytes and reading recipient at offset 32. + // for more details see discussion in: + // https://github.com/paritytech/parity-bridge/issues/61 + + function getRecipient(bytes message) internal pure returns (address) { + address recipient; + // solium-disable-next-line security/no-inline-assembly + assembly { + recipient := mload(add(message, 20)) + } + return recipient; + } + + function getValue(bytes message) internal pure returns (uint256) { + uint256 value; + // solium-disable-next-line security/no-inline-assembly + assembly { + value := mload(add(message, 52)) + } + return value; + } + + function getTransactionHash(bytes message) internal pure returns (bytes32) { + bytes32 hash; + // solium-disable-next-line security/no-inline-assembly + assembly { + hash := mload(add(message, 84)) + } + return hash; + } + + function getHomeGasPrice(bytes message) internal pure returns (uint256) { + uint256 gasPrice; + // solium-disable-next-line security/no-inline-assembly + assembly { + gasPrice := mload(add(message, 116)) + } + return gasPrice; + } +} + +// File: contracts/ForeignBridge.sol + +contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressStorage { + uint256 public gasLimitDepositRelay; + uint256 public gasLimitWithdrawConfirm; + uint256 homeGasPrice = 1000000000 wei; + mapping (bytes32 => bytes) messages; + mapping (bytes32 => bytes) signatures; + mapping (bytes32 => bool) messages_signed; + mapping (bytes32 => uint) num_messages_signed; + mapping (bytes32 => bool) deposits_signed; + mapping (bytes32 => uint) num_deposits_signed; + + mapping (bytes32 => bool) tokenAddressApprovalSigns; + mapping (address => uint256) public numTokenAddressApprovalSigns; + + IBurnableMintableERC677Token public erc677token; + + /// triggered when relay of deposit from HomeBridge is complete + event Deposit(address recipient, uint value); + + /// Event created on money withdraw. + event Withdraw(address recipient, uint256 value, uint256 homeGasPrice); + + /// Collected signatures which should be relayed to home chain. + event CollectedSignatures(address authorityResponsibleForRelay, bytes32 messageHash); + + event TokenAddress(address token); + + event GasConsumptionLimitsUpdated(uint256 gasLimitDepositRelay, uint256 gasLimitWithdrawConfirm); + + function ForeignBridge( + address _validatorContract, + address _erc677token + ) public Validatable(_validatorContract) { + erc677token = IBurnableMintableERC677Token(_erc677token); + } + + function setGasLimitDepositRelay(uint256 gas) onlyValidator { + gasLimitDepositRelay = gas; + + GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm); + } + + function setGasLimitWithdrawConfirm(uint256 gas) onlyValidator { + gasLimitWithdrawConfirm = gas; + + GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm); + } + + function deposit(address recipient, uint value, bytes32 transactionHash) public onlyValidator { + require(erc677token != address(0x0)); + + // Protection from misbehaing authority + bytes32 hash_msg = keccak256(recipient, value, transactionHash); + bytes32 hash_sender = keccak256(msg.sender, hash_msg); + + // Duplicated deposits + require(!deposits_signed[hash_sender]); + deposits_signed[hash_sender] = true; + + uint signed = num_deposits_signed[hash_msg] + 1; + num_deposits_signed[hash_msg] = signed; + + if (signed == validatorContract.requiredSignatures()) { + // If the bridge contract does not own enough tokens to transfer + // it will couse funds lock on the home side of the bridge + erc677token.mint(recipient, value); + Deposit(recipient, value); + } + } + + function onTokenTransfer(address _from, uint256 _value, bytes _data) external returns(bool) { + require(erc677token != address(0x0)); + require(msg.sender == address(erc677token)); + erc677token.burn(_value); + Withdraw(_from, _value, homeGasPrice); + return true; + } + + /// Should be used as sync tool + /// + /// Message is a message that should be relayed to main chain once authorities sign it. + /// + /// for withdraw message contains: + /// withdrawal recipient (bytes20) + /// withdrawal value (uint) + /// foreign transaction hash (bytes32) // to avoid transaction duplication + function submitSignature(bytes signature, bytes message) public onlyValidator { + // ensure that `signature` is really `message` signed by `msg.sender` + require(msg.sender == MessageSigning.recoverAddressFromSignedMessage(signature, message)); + + require(message.length == 116); + bytes32 hash = keccak256(message); + bytes32 hash_sender = keccak256(msg.sender, hash); + + uint signed = num_messages_signed[hash_sender] + 1; + + if (signed > 1) { + // Duplicated signatures + require(!messages_signed[hash_sender]); + } + else { + // check if it will really reduce gas usage in case of the second transaction + // with the same hash + messages[hash] = message; + } + messages_signed[hash_sender] = true; + + bytes32 sign_idx = keccak256(hash, (signed-1)); + signatures[sign_idx] = signature; + + num_messages_signed[hash_sender] = signed; + + // TODO: this may cause troubles if requiredSignatures len is changed + if (signed == validatorContract.requiredSignatures()) { + CollectedSignatures(msg.sender, hash); + } + } + + function signature(bytes32 hash, uint index) public view returns (bytes) { + bytes32 sign_idx = keccak256(hash, index); + return signatures[sign_idx]; + } + + /// Get message + function message(bytes32 hash) public view returns (bytes) { + return messages[hash]; + } + +} diff --git a/flats/HomeBridge_flat.sol b/flats/HomeBridge_flat.sol new file mode 100644 index 000000000..9c7713a5e --- /dev/null +++ b/flats/HomeBridge_flat.sol @@ -0,0 +1,282 @@ +pragma solidity 0.4.20; + +// File: contracts/BridgeDeploymentAddressStorage.sol + +contract BridgeDeploymentAddressStorage { + uint256 public deployedAtBlock; + + function BridgeDeploymentAddressStorage() public { + deployedAtBlock = block.number; + } +} + +// File: contracts/IBridgeValidators.sol + +interface IBridgeValidators { + function isValidator(address _validator) public view returns(bool); + function requiredSignatures() public view returns(uint8); +} + +// File: contracts/Validatable.sol + +contract Validatable { + IBridgeValidators public validatorContract; + + modifier onlyValidator() { + require(validatorContract.isValidator(msg.sender)); + _; + } + + function Validatable(address _validatorContract) public { + require(_validatorContract != address(0)); + validatorContract = IBridgeValidators(_validatorContract); + } +} + +// File: contracts/libraries/Message.sol + +library Message { + // layout of message :: bytes: + // offset 0: 32 bytes :: uint256 - message length + // offset 32: 20 bytes :: address - recipient address + // offset 52: 32 bytes :: uint256 - value + // offset 84: 32 bytes :: bytes32 - transaction hash + // offset 116: 32 bytes :: uint256 - home gas price + + // bytes 1 to 32 are 0 because message length is stored as little endian. + // mload always reads 32 bytes. + // so we can and have to start reading recipient at offset 20 instead of 32. + // if we were to read at 32 the address would contain part of value and be corrupted. + // when reading from offset 20 mload will read 12 zero bytes followed + // by the 20 recipient address bytes and correctly convert it into an address. + // this saves some storage/gas over the alternative solution + // which is padding address to 32 bytes and reading recipient at offset 32. + // for more details see discussion in: + // https://github.com/paritytech/parity-bridge/issues/61 + + function getRecipient(bytes message) internal pure returns (address) { + address recipient; + // solium-disable-next-line security/no-inline-assembly + assembly { + recipient := mload(add(message, 20)) + } + return recipient; + } + + function getValue(bytes message) internal pure returns (uint256) { + uint256 value; + // solium-disable-next-line security/no-inline-assembly + assembly { + value := mload(add(message, 52)) + } + return value; + } + + function getTransactionHash(bytes message) internal pure returns (bytes32) { + bytes32 hash; + // solium-disable-next-line security/no-inline-assembly + assembly { + hash := mload(add(message, 84)) + } + return hash; + } + + function getHomeGasPrice(bytes message) internal pure returns (uint256) { + uint256 gasPrice; + // solium-disable-next-line security/no-inline-assembly + assembly { + gasPrice := mload(add(message, 116)) + } + return gasPrice; + } +} + +// File: contracts/libraries/MessageSigning.sol + +// import "./Helpers.sol"; +library Helpers { + function addressArrayContains(address[] array, address value) internal pure returns (bool) { + for (uint256 i = 0; i < array.length; i++) { + if (array[i] == value) { + return true; + } + } + return false; + } + + function uintToString(uint256 inputValue) internal pure returns (string) { + // figure out the length of the resulting string + uint256 length = 0; + uint256 currentValue = inputValue; + do { + length++; + currentValue /= 10; + } while (currentValue != 0); + // allocate enough memory + bytes memory result = new bytes(length); + // construct the string backwards + uint256 i = length - 1; + currentValue = inputValue; + do { + result[i--] = byte(48 + currentValue % 10); + currentValue /= 10; + } while (currentValue != 0); + return string(result); + } + + function hasEnoughValidSignatures( + bytes _message, + uint8[] _vs, + bytes32[] _rs, + bytes32[] _ss, + IBridgeValidators _validatorContract) internal view returns (bool) { + uint8 _requiredSignatures = _validatorContract.requiredSignatures(); + require(_vs.length < _requiredSignatures); + bytes32 hash = MessageSigning.hashMessage(_message); + address[] memory encounteredAddresses = new address[](_requiredSignatures); + + for (uint8 i = 0; i < _requiredSignatures; i++) { + address recoveredAddress = ecrecover(hash, _vs[i], _rs[i], _ss[i]); + // only signatures by addresses in `addresses` are allowed + require(_validatorContract.isValidator(recoveredAddress)); + // duplicate signatures are not allowed + if (addressArrayContains(encounteredAddresses, recoveredAddress)) { + return false; + } + encounteredAddresses[i] = recoveredAddress; + } + return true; + } +} + +library MessageSigning { + function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) { + require(signature.length == 65); + bytes32 r; + bytes32 s; + bytes1 v; + // solium-disable-next-line security/no-inline-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := mload(add(signature, 0x60)) + } + return ecrecover(hashMessage(message), uint8(v), r, s); + } + + function hashMessage(bytes message) internal pure returns (bytes32) { + bytes memory prefix = "\x19Ethereum Signed Message:\n"; + return keccak256(prefix, Helpers.uintToString(message.length), message); + } +} + +// File: contracts/libraries/SafeMath.sol + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} + +// File: contracts/HomeBridge.sol + +// import "./libraries/Helpers.sol"; + + + + + + + +contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { + using SafeMath for uint256; + uint256 public gasLimitWithdrawRelay; + uint256 public estimatedGasCostOfWithdraw; + mapping (bytes32 => bool) withdraws; + + event GasConsumptionLimitsUpdated(uint256 gas); + event Deposit (address recipient, uint256 value); + event Withdraw (address recipient, uint256 value); + + function HomeBridge ( + address _validatorContract + ) public Validatable(_validatorContract) { + } + + /// Should be used to deposit money. + function () public payable { + Deposit(msg.sender, msg.value); + } + + function setGasLimitWithdrawRelay(uint256 _gas) public onlyValidator { + gasLimitWithdrawRelay = _gas; + GasConsumptionLimitsUpdated(gasLimitWithdrawRelay); + } + + function withdraw(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) public { + require(message.length == 116); + // require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract)); + + address recipient = Message.getRecipient(message); + uint256 value = Message.getValue(message); + bytes32 hash = Message.getTransactionHash(message); + uint256 homeGasPrice = Message.getHomeGasPrice(message); + require((recipient == msg.sender) || (tx.gasprice == homeGasPrice)); + require(!withdraws[hash]); + // Order of operations below is critical to avoid TheDAO-like re-entry bug + withdraws[hash] = true; + + uint256 estimatedWeiCostOfWithdraw = estimatedGasCostOfWithdraw.mul(homeGasPrice); + + // charge recipient for relay cost + uint256 valueRemainingAfterSubtractingCost = value.sub(estimatedWeiCostOfWithdraw); + + // pay out recipient + recipient.transfer(valueRemainingAfterSubtractingCost); + + // refund relay cost to relaying authority + msg.sender.transfer(estimatedWeiCostOfWithdraw); + + Withdraw(recipient, valueRemainingAfterSubtractingCost); + } +} diff --git a/flats/POA20_flat.sol b/flats/POA20_flat.sol new file mode 100644 index 000000000..d29123dc7 --- /dev/null +++ b/flats/POA20_flat.sol @@ -0,0 +1,491 @@ +pragma solidity 0.4.20; + +// File: contracts/ERC677Receiver.sol + +contract ERC677Receiver { + function onTokenTransfer(address _from, uint _value, bytes _data) external returns(bool); +} + +// File: zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + function totalSupply() public view returns (uint256); + function balanceOf(address who) public view returns (uint256); + function transfer(address to, uint256 value) public returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); +} + +// File: zeppelin-solidity/contracts/token/ERC20/ERC20.sol + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20 is ERC20Basic { + function allowance(address owner, address spender) public view returns (uint256); + function transferFrom(address from, address to, uint256 value) public returns (bool); + function approve(address spender, uint256 value) public returns (bool); + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +// File: contracts/ERC677.sol + +contract ERC677 is ERC20 { + event Transfer(address indexed from, address indexed to, uint value, bytes data); + + function transferAndCall(address, uint, bytes) returns (bool); + +} + +// File: contracts/IBurnableMintableERC677Token.sol + +contract IBurnableMintableERC677Token is ERC677 { + function mint(address, uint256) public returns (bool); + function burn(uint256 _value) public; +} + +// File: zeppelin-solidity/contracts/math/SafeMath.sol + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} + +// File: zeppelin-solidity/contracts/token/ERC20/BasicToken.sol + +/** + * @title Basic token + * @dev Basic version of StandardToken, with no allowances. + */ +contract BasicToken is ERC20Basic { + using SafeMath for uint256; + + mapping(address => uint256) balances; + + uint256 totalSupply_; + + /** + * @dev total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return totalSupply_; + } + + /** + * @dev transfer token for a specified address + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + */ + function transfer(address _to, uint256 _value) public returns (bool) { + require(_to != address(0)); + require(_value <= balances[msg.sender]); + + // SafeMath.sub will throw if there is not enough balance. + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param _owner The address to query the the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address _owner) public view returns (uint256 balance) { + return balances[_owner]; + } + +} + +// File: zeppelin-solidity/contracts/token/ERC20/BurnableToken.sol + +/** + * @title Burnable Token + * @dev Token that can be irreversibly burned (destroyed). + */ +contract BurnableToken is BasicToken { + + event Burn(address indexed burner, uint256 value); + + /** + * @dev Burns a specific amount of tokens. + * @param _value The amount of token to be burned. + */ + function burn(uint256 _value) public { + require(_value <= balances[msg.sender]); + // no need to require value <= totalSupply, since that would imply the + // sender's balance is greater than the totalSupply, which *should* be an assertion failure + + address burner = msg.sender; + balances[burner] = balances[burner].sub(_value); + totalSupply_ = totalSupply_.sub(_value); + Burn(burner, _value); + } +} + +// File: zeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol + +contract DetailedERC20 is ERC20 { + string public name; + string public symbol; + uint8 public decimals; + + function DetailedERC20(string _name, string _symbol, uint8 _decimals) public { + name = _name; + symbol = _symbol; + decimals = _decimals; + } +} + +// File: zeppelin-solidity/contracts/ownership/Ownable.sol + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address public owner; + + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + function Ownable() public { + owner = msg.sender; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param newOwner The address to transfer ownership to. + */ + function transferOwnership(address newOwner) public onlyOwner { + require(newOwner != address(0)); + OwnershipTransferred(owner, newOwner); + owner = newOwner; + } + +} + +// File: zeppelin-solidity/contracts/token/ERC20/StandardToken.sol + +/** + * @title Standard ERC20 token + * + * @dev Implementation of the basic standard token. + * @dev https://github.com/ethereum/EIPs/issues/20 + * @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + */ +contract StandardToken is ERC20, BasicToken { + + mapping (address => mapping (address => uint256)) internal allowed; + + + /** + * @dev Transfer tokens from one address to another + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + */ + function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { + require(_to != address(0)); + require(_value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + Transfer(_from, _to, _value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param _spender The address which will spend the funds. + * @param _value The amount of tokens to be spent. + */ + function approve(address _spender, uint256 _value) public returns (bool) { + allowed[msg.sender][_spender] = _value; + Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param _owner address The address which owns the funds. + * @param _spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance(address _owner, address _spender) public view returns (uint256) { + return allowed[_owner][_spender]; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * + * approve should be called when allowed[_spender] == 0. To increment + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _addedValue The amount of tokens to increase the allowance by. + */ + function increaseApproval(address _spender, uint _addedValue) public returns (bool) { + allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); + Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * + * approve should be called when allowed[_spender] == 0. To decrement + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _subtractedValue The amount of tokens to decrease the allowance by. + */ + function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) { + uint oldValue = allowed[msg.sender][_spender]; + if (_subtractedValue > oldValue) { + allowed[msg.sender][_spender] = 0; + } else { + allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); + } + Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + +} + +// File: zeppelin-solidity/contracts/token/ERC20/MintableToken.sol + +/** + * @title Mintable token + * @dev Simple ERC20 Token example, with mintable token creation + * @dev Issue: * https://github.com/OpenZeppelin/zeppelin-solidity/issues/120 + * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol + */ +contract MintableToken is StandardToken, Ownable { + event Mint(address indexed to, uint256 amount); + event MintFinished(); + + bool public mintingFinished = false; + + + modifier canMint() { + require(!mintingFinished); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint(address _to, uint256 _amount) onlyOwner canMint public returns (bool) { + totalSupply_ = totalSupply_.add(_amount); + balances[_to] = balances[_to].add(_amount); + Mint(_to, _amount); + Transfer(address(0), _to, _amount); + return true; + } + + /** + * @dev Function to stop minting new tokens. + * @return True if the operation was successful. + */ + function finishMinting() onlyOwner canMint public returns (bool) { + mintingFinished = true; + MintFinished(); + return true; + } +} + +// File: zeppelin-solidity/contracts/lifecycle/Pausable.sol + +/** + * @title Pausable + * @dev Base contract which allows children to implement an emergency stop mechanism. + */ +contract Pausable is Ownable { + event Pause(); + event Unpause(); + + bool public paused = false; + + + /** + * @dev Modifier to make a function callable only when the contract is not paused. + */ + modifier whenNotPaused() { + require(!paused); + _; + } + + /** + * @dev Modifier to make a function callable only when the contract is paused. + */ + modifier whenPaused() { + require(paused); + _; + } + + /** + * @dev called by the owner to pause, triggers stopped state + */ + function pause() onlyOwner whenNotPaused public { + paused = true; + Pause(); + } + + /** + * @dev called by the owner to unpause, returns to normal state + */ + function unpause() onlyOwner whenPaused public { + paused = false; + Unpause(); + } +} + +// File: zeppelin-solidity/contracts/token/ERC20/PausableToken.sol + +/** + * @title Pausable token + * @dev StandardToken modified with pausable transfers. + **/ +contract PausableToken is StandardToken, Pausable { + + function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) { + return super.transfer(_to, _value); + } + + function transferFrom(address _from, address _to, uint256 _value) public whenNotPaused returns (bool) { + return super.transferFrom(_from, _to, _value); + } + + function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) { + return super.approve(_spender, _value); + } + + function increaseApproval(address _spender, uint _addedValue) public whenNotPaused returns (bool success) { + return super.increaseApproval(_spender, _addedValue); + } + + function decreaseApproval(address _spender, uint _subtractedValue) public whenNotPaused returns (bool success) { + return super.decreaseApproval(_spender, _subtractedValue); + } +} + +// File: contracts/POA20.sol + +contract POA20 is + IBurnableMintableERC677Token, + DetailedERC20, + BurnableToken, + MintableToken, + PausableToken { + function POA20( + string _name, + string _symbol, + uint8 _decimals) + public DetailedERC20(_name, _symbol, _decimals) {} + + modifier validRecipient(address _recipient) { + require(_recipient != address(0) && _recipient != address(this)); + _; + } + + function transferAndCall(address _to, uint _value, bytes _data) + public validRecipient(_to) returns (bool) + { + super.transfer(_to, _value); + Transfer(msg.sender, _to, _value, _data); + if (isContract(_to)) { + contractFallback(_to, _value, _data); + } + return true; + } + + function contractFallback(address _to, uint _value, bytes _data) + private + { + ERC677Receiver receiver = ERC677Receiver(_to); + receiver.onTokenTransfer(msg.sender, _value, _data); + } + + function isContract(address _addr) + private + returns (bool hasCode) + { + uint length; + assembly { length := extcodesize(_addr) } + return length > 0; + } +} From cb33ddbb3b2d10c7c3eb7ce50b440c4f7a6344e5 Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Sat, 3 Mar 2018 14:20:05 +0100 Subject: [PATCH 3/8] Fix #9. Extend tracebility of deposits transactions --- contracts/ForeignBridge.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/ForeignBridge.sol b/contracts/ForeignBridge.sol index dd29cd7c3..0bb9296d8 100644 --- a/contracts/ForeignBridge.sol +++ b/contracts/ForeignBridge.sol @@ -37,6 +37,9 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt event GasConsumptionLimitsUpdated(uint256 gasLimitDepositRelay, uint256 gasLimitWithdrawConfirm); + event SignedForDeposit(address indexed signer, bytes32 message); + event SignedForWithdraw(address indexed signer, bytes32 message); + function ForeignBridge( address _validatorContract, address _erc677token @@ -76,6 +79,7 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt erc677token.mint(recipient, value); Deposit(recipient, value); } + SignedForDeposit(msg.sender, transactionHash); } function onTokenTransfer(address _from, uint256 _value, bytes _data) external returns(bool) { @@ -124,6 +128,7 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt if (signed == validatorContract.requiredSignatures()) { CollectedSignatures(msg.sender, hash); } + SignedForWithdraw(msg.sender, hash); } function signature(bytes32 hash, uint index) public view returns (bytes) { From 54e34c578d333db380f017247453d96e22dd5220 Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Sun, 11 Mar 2018 12:37:37 -0700 Subject: [PATCH 4/8] Apply comments enhancements --- contracts/BridgeValidators.sol | 4 + contracts/ERC677.sol | 2 +- contracts/ForeignBridge.sol | 24 +- contracts/HomeBridge.sol | 5 +- contracts/IBridgeValidators.sol | 1 + contracts/Validatable.sol | 5 + contracts/libraries/Helpers.sol | 24 +- contracts/libraries/MessageSigning.sol | 23 -- flats/BridgeValidators_flat.sol | 7 +- flats/ForeignBridge_flat.sol | 95 ++---- flats/HomeBridge_flat.sol | 139 ++++---- flats/POA20_flat.sol | 4 +- flatten.sh | 7 + migrations/2_bridge_deployment.js | 7 +- package-lock.json | 427 +++++++++++++++++++++++++ package.json | 10 +- 16 files changed, 593 insertions(+), 191 deletions(-) delete mode 100644 contracts/libraries/MessageSigning.sol create mode 100755 flatten.sh diff --git a/contracts/BridgeValidators.sol b/contracts/BridgeValidators.sol index 255b483cc..c656bb08b 100644 --- a/contracts/BridgeValidators.sol +++ b/contracts/BridgeValidators.sol @@ -51,4 +51,8 @@ contract BridgeValidators is Ownable, IBridgeValidators { function requiredSignatures() public view returns(uint8) { return requiredValidators; } + + function currentOwner() public view returns(address) { + return owner; + } } diff --git a/contracts/ERC677.sol b/contracts/ERC677.sol index daead733a..f5e996b00 100644 --- a/contracts/ERC677.sol +++ b/contracts/ERC677.sol @@ -5,6 +5,6 @@ import "zeppelin-solidity/contracts/token/ERC20/ERC20.sol"; contract ERC677 is ERC20 { event Transfer(address indexed from, address indexed to, uint value, bytes data); - function transferAndCall(address, uint, bytes) returns (bool); + function transferAndCall(address, uint, bytes) public returns (bool); } diff --git a/contracts/ForeignBridge.sol b/contracts/ForeignBridge.sol index 0bb9296d8..fd1f406d0 100644 --- a/contracts/ForeignBridge.sol +++ b/contracts/ForeignBridge.sol @@ -1,7 +1,6 @@ pragma solidity 0.4.19; import "./libraries/Helpers.sol"; import "./libraries/Message.sol"; -import "./libraries/MessageSigning.sol"; import "./IBridgeValidators.sol"; import "./Validatable.sol"; import "./BridgeDeploymentAddressStorage.sol"; @@ -15,12 +14,9 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt mapping (bytes32 => bytes) messages; mapping (bytes32 => bytes) signatures; mapping (bytes32 => bool) messages_signed; - mapping (bytes32 => uint) num_messages_signed; + mapping (bytes32 => uint256) num_messages_signed; mapping (bytes32 => bool) deposits_signed; - mapping (bytes32 => uint) num_deposits_signed; - - mapping (bytes32 => bool) tokenAddressApprovalSigns; - mapping (address => uint256) public numTokenAddressApprovalSigns; + mapping (bytes32 => uint256) num_deposits_signed; IBurnableMintableERC677Token public erc677token; @@ -33,8 +29,6 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt /// Collected signatures which should be relayed to home chain. event CollectedSignatures(address authorityResponsibleForRelay, bytes32 messageHash); - event TokenAddress(address token); - event GasConsumptionLimitsUpdated(uint256 gasLimitDepositRelay, uint256 gasLimitWithdrawConfirm); event SignedForDeposit(address indexed signer, bytes32 message); @@ -47,13 +41,13 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt erc677token = IBurnableMintableERC677Token(_erc677token); } - function setGasLimitDepositRelay(uint256 gas) onlyValidator { - gasLimitDepositRelay = gas; + function setGasLimitDepositRelay(uint256 _gas) public onlyOwner { + gasLimitDepositRelay = _gas; GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm); } - function setGasLimitWithdrawConfirm(uint256 gas) onlyValidator { + function setGasLimitWithdrawConfirm(uint256 gas) public onlyOwner { gasLimitWithdrawConfirm = gas; GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm); @@ -70,16 +64,18 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt require(!deposits_signed[hash_sender]); deposits_signed[hash_sender] = true; - uint signed = num_deposits_signed[hash_msg] + 1; + uint256 signed = num_deposits_signed[hash_msg] + 1; num_deposits_signed[hash_msg] = signed; + SignedForDeposit(msg.sender, transactionHash); + if (signed == validatorContract.requiredSignatures()) { // If the bridge contract does not own enough tokens to transfer // it will couse funds lock on the home side of the bridge erc677token.mint(recipient, value); Deposit(recipient, value); } - SignedForDeposit(msg.sender, transactionHash); + } function onTokenTransfer(address _from, uint256 _value, bytes _data) external returns(bool) { @@ -125,10 +121,10 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt num_messages_signed[hash_sender] = signed; // TODO: this may cause troubles if requiredSignatures len is changed + SignedForWithdraw(msg.sender, hash); if (signed == validatorContract.requiredSignatures()) { CollectedSignatures(msg.sender, hash); } - SignedForWithdraw(msg.sender, hash); } function signature(bytes32 hash, uint index) public view returns (bytes) { diff --git a/contracts/HomeBridge.sol b/contracts/HomeBridge.sol index b861bdef8..18690a9d3 100644 --- a/contracts/HomeBridge.sol +++ b/contracts/HomeBridge.sol @@ -1,7 +1,7 @@ pragma solidity ^0.4.19; import "./libraries/SafeMath.sol"; +import "./libraries/Helpers.sol"; import "./libraries/Message.sol"; -import "./libraries/MessageSigning.sol"; import "./IBridgeValidators.sol"; import "./Validatable.sol"; import "./BridgeDeploymentAddressStorage.sol"; @@ -27,11 +27,12 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { Deposit(msg.sender, msg.value); } - function setGasLimitWithdrawRelay(uint256 _gas) public onlyValidator { + function setGasLimitWithdrawRelay(uint256 _gas) public onlyOwner { gasLimitWithdrawRelay = _gas; GasConsumptionLimitsUpdated(gasLimitWithdrawRelay); } + function withdraw(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) public { require(message.length == 116); // require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract)); diff --git a/contracts/IBridgeValidators.sol b/contracts/IBridgeValidators.sol index b90ee7af3..a05ccfb08 100644 --- a/contracts/IBridgeValidators.sol +++ b/contracts/IBridgeValidators.sol @@ -4,4 +4,5 @@ pragma solidity ^0.4.19; interface IBridgeValidators { function isValidator(address _validator) public view returns(bool); function requiredSignatures() public view returns(uint8); + function currentOwner() public view returns(address); } diff --git a/contracts/Validatable.sol b/contracts/Validatable.sol index 915e3fbf7..93233fce5 100644 --- a/contracts/Validatable.sol +++ b/contracts/Validatable.sol @@ -10,6 +10,11 @@ contract Validatable { _; } + modifier onlyOwner() { + require(validatorContract.currentOwner() == msg.sender); + _; + } + function Validatable(address _validatorContract) public { require(_validatorContract != address(0)); validatorContract = IBridgeValidators(_validatorContract); diff --git a/contracts/libraries/Helpers.sol b/contracts/libraries/Helpers.sol index d3c1e99cf..7e3612b1c 100644 --- a/contracts/libraries/Helpers.sol +++ b/contracts/libraries/Helpers.sol @@ -1,6 +1,6 @@ pragma solidity 0.4.19; import "../IBridgeValidators.sol"; -import "./MessageSigning.sol"; + library Helpers { function addressArrayContains(address[] array, address value) internal pure returns (bool) { @@ -56,3 +56,25 @@ library Helpers { return true; } } + + +library MessageSigning { + function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) { + require(signature.length == 65); + bytes32 r; + bytes32 s; + bytes1 v; + // solium-disable-next-line security/no-inline-assembly + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := mload(add(signature, 0x60)) + } + return ecrecover(hashMessage(message), uint8(v), r, s); + } + + function hashMessage(bytes message) internal pure returns (bytes32) { + bytes memory prefix = "\x19Ethereum Signed Message:\n"; + return keccak256(prefix, Helpers.uintToString(message.length), message); + } +} diff --git a/contracts/libraries/MessageSigning.sol b/contracts/libraries/MessageSigning.sol deleted file mode 100644 index 42861c535..000000000 --- a/contracts/libraries/MessageSigning.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity 0.4.19; -import "./Helpers.sol"; - -library MessageSigning { - function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) { - require(signature.length == 65); - bytes32 r; - bytes32 s; - bytes1 v; - // solium-disable-next-line security/no-inline-assembly - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := mload(add(signature, 0x60)) - } - return ecrecover(hashMessage(message), uint8(v), r, s); - } - - function hashMessage(bytes message) internal pure returns (bytes32) { - bytes memory prefix = "\x19Ethereum Signed Message:\n"; - return keccak256(prefix, Helpers.uintToString(message.length), message); - } -} \ No newline at end of file diff --git a/flats/BridgeValidators_flat.sol b/flats/BridgeValidators_flat.sol index 174ff3683..347232257 100644 --- a/flats/BridgeValidators_flat.sol +++ b/flats/BridgeValidators_flat.sol @@ -1,10 +1,11 @@ -pragma solidity 0.4.20; +pragma solidity ^0.4.19; // File: contracts/IBridgeValidators.sol interface IBridgeValidators { function isValidator(address _validator) public view returns(bool); function requiredSignatures() public view returns(uint8); + function currentOwner() public view returns(address); } // File: zeppelin-solidity/contracts/ownership/Ownable.sol @@ -98,4 +99,8 @@ contract BridgeValidators is Ownable, IBridgeValidators { function requiredSignatures() public view returns(uint8) { return requiredValidators; } + + function currentOwner() public view returns(address) { + return owner; + } } diff --git a/flats/ForeignBridge_flat.sol b/flats/ForeignBridge_flat.sol index 59330be81..79af834b5 100644 --- a/flats/ForeignBridge_flat.sol +++ b/flats/ForeignBridge_flat.sol @@ -1,4 +1,4 @@ -pragma solidity 0.4.20; +pragma solidity 0.4.19; // File: contracts/BridgeDeploymentAddressStorage.sol @@ -21,6 +21,7 @@ contract ERC677Receiver { interface IBridgeValidators { function isValidator(address _validator) public view returns(bool); function requiredSignatures() public view returns(uint8); + function currentOwner() public view returns(address); } // File: zeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol @@ -55,7 +56,7 @@ contract ERC20 is ERC20Basic { contract ERC677 is ERC20 { event Transfer(address indexed from, address indexed to, uint value, bytes data); - function transferAndCall(address, uint, bytes) returns (bool); + function transferAndCall(address, uint, bytes) public returns (bool); } @@ -76,15 +77,19 @@ contract Validatable { _; } + modifier onlyOwner() { + require(validatorContract.currentOwner() == msg.sender); + _; + } + function Validatable(address _validatorContract) public { require(_validatorContract != address(0)); validatorContract = IBridgeValidators(_validatorContract); } } -// File: contracts/libraries/MessageSigning.sol +// File: contracts/libraries/Helpers.sol -// import "./Helpers.sol"; library Helpers { function addressArrayContains(address[] array, address value) internal pure returns (bool) { for (uint256 i = 0; i < array.length; i++) { @@ -140,6 +145,7 @@ library Helpers { } } + library MessageSigning { function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) { require(signature.length == 65); @@ -161,63 +167,6 @@ library MessageSigning { } } -// File: contracts/libraries/Helpers.sol - -library Helpers { - function addressArrayContains(address[] array, address value) internal pure returns (bool) { - for (uint256 i = 0; i < array.length; i++) { - if (array[i] == value) { - return true; - } - } - return false; - } - - function uintToString(uint256 inputValue) internal pure returns (string) { - // figure out the length of the resulting string - uint256 length = 0; - uint256 currentValue = inputValue; - do { - length++; - currentValue /= 10; - } while (currentValue != 0); - // allocate enough memory - bytes memory result = new bytes(length); - // construct the string backwards - uint256 i = length - 1; - currentValue = inputValue; - do { - result[i--] = byte(48 + currentValue % 10); - currentValue /= 10; - } while (currentValue != 0); - return string(result); - } - - function hasEnoughValidSignatures( - bytes _message, - uint8[] _vs, - bytes32[] _rs, - bytes32[] _ss, - IBridgeValidators _validatorContract) internal view returns (bool) { - uint8 _requiredSignatures = _validatorContract.requiredSignatures(); - require(_vs.length < _requiredSignatures); - bytes32 hash = MessageSigning.hashMessage(_message); - address[] memory encounteredAddresses = new address[](_requiredSignatures); - - for (uint8 i = 0; i < _requiredSignatures; i++) { - address recoveredAddress = ecrecover(hash, _vs[i], _rs[i], _ss[i]); - // only signatures by addresses in `addresses` are allowed - require(_validatorContract.isValidator(recoveredAddress)); - // duplicate signatures are not allowed - if (addressArrayContains(encounteredAddresses, recoveredAddress)) { - return false; - } - encounteredAddresses[i] = recoveredAddress; - } - return true; - } -} - // File: contracts/libraries/Message.sol library Message { @@ -285,12 +234,9 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt mapping (bytes32 => bytes) messages; mapping (bytes32 => bytes) signatures; mapping (bytes32 => bool) messages_signed; - mapping (bytes32 => uint) num_messages_signed; + mapping (bytes32 => uint256) num_messages_signed; mapping (bytes32 => bool) deposits_signed; - mapping (bytes32 => uint) num_deposits_signed; - - mapping (bytes32 => bool) tokenAddressApprovalSigns; - mapping (address => uint256) public numTokenAddressApprovalSigns; + mapping (bytes32 => uint256) num_deposits_signed; IBurnableMintableERC677Token public erc677token; @@ -303,10 +249,11 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt /// Collected signatures which should be relayed to home chain. event CollectedSignatures(address authorityResponsibleForRelay, bytes32 messageHash); - event TokenAddress(address token); - event GasConsumptionLimitsUpdated(uint256 gasLimitDepositRelay, uint256 gasLimitWithdrawConfirm); + event SignedForDeposit(address indexed signer, bytes32 message); + event SignedForWithdraw(address indexed signer, bytes32 message); + function ForeignBridge( address _validatorContract, address _erc677token @@ -314,13 +261,13 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt erc677token = IBurnableMintableERC677Token(_erc677token); } - function setGasLimitDepositRelay(uint256 gas) onlyValidator { - gasLimitDepositRelay = gas; + function setGasLimitDepositRelay(uint256 _gas) public onlyOwner { + gasLimitDepositRelay = _gas; GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm); } - function setGasLimitWithdrawConfirm(uint256 gas) onlyValidator { + function setGasLimitWithdrawConfirm(uint256 gas) public onlyOwner { gasLimitWithdrawConfirm = gas; GasConsumptionLimitsUpdated(gasLimitDepositRelay, gasLimitWithdrawConfirm); @@ -337,15 +284,18 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt require(!deposits_signed[hash_sender]); deposits_signed[hash_sender] = true; - uint signed = num_deposits_signed[hash_msg] + 1; + uint256 signed = num_deposits_signed[hash_msg] + 1; num_deposits_signed[hash_msg] = signed; + SignedForDeposit(msg.sender, transactionHash); + if (signed == validatorContract.requiredSignatures()) { // If the bridge contract does not own enough tokens to transfer // it will couse funds lock on the home side of the bridge erc677token.mint(recipient, value); Deposit(recipient, value); } + } function onTokenTransfer(address _from, uint256 _value, bytes _data) external returns(bool) { @@ -391,6 +341,7 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt num_messages_signed[hash_sender] = signed; // TODO: this may cause troubles if requiredSignatures len is changed + SignedForWithdraw(msg.sender, hash); if (signed == validatorContract.requiredSignatures()) { CollectedSignatures(msg.sender, hash); } diff --git a/flats/HomeBridge_flat.sol b/flats/HomeBridge_flat.sol index 9c7713a5e..4c7479e96 100644 --- a/flats/HomeBridge_flat.sol +++ b/flats/HomeBridge_flat.sol @@ -1,4 +1,4 @@ -pragma solidity 0.4.20; +pragma solidity 0.4.19; // File: contracts/BridgeDeploymentAddressStorage.sol @@ -15,6 +15,7 @@ contract BridgeDeploymentAddressStorage { interface IBridgeValidators { function isValidator(address _validator) public view returns(bool); function requiredSignatures() public view returns(uint8); + function currentOwner() public view returns(address); } // File: contracts/Validatable.sol @@ -27,73 +28,19 @@ contract Validatable { _; } + modifier onlyOwner() { + require(validatorContract.currentOwner() == msg.sender); + _; + } + function Validatable(address _validatorContract) public { require(_validatorContract != address(0)); validatorContract = IBridgeValidators(_validatorContract); } } -// File: contracts/libraries/Message.sol - -library Message { - // layout of message :: bytes: - // offset 0: 32 bytes :: uint256 - message length - // offset 32: 20 bytes :: address - recipient address - // offset 52: 32 bytes :: uint256 - value - // offset 84: 32 bytes :: bytes32 - transaction hash - // offset 116: 32 bytes :: uint256 - home gas price - - // bytes 1 to 32 are 0 because message length is stored as little endian. - // mload always reads 32 bytes. - // so we can and have to start reading recipient at offset 20 instead of 32. - // if we were to read at 32 the address would contain part of value and be corrupted. - // when reading from offset 20 mload will read 12 zero bytes followed - // by the 20 recipient address bytes and correctly convert it into an address. - // this saves some storage/gas over the alternative solution - // which is padding address to 32 bytes and reading recipient at offset 32. - // for more details see discussion in: - // https://github.com/paritytech/parity-bridge/issues/61 - - function getRecipient(bytes message) internal pure returns (address) { - address recipient; - // solium-disable-next-line security/no-inline-assembly - assembly { - recipient := mload(add(message, 20)) - } - return recipient; - } - - function getValue(bytes message) internal pure returns (uint256) { - uint256 value; - // solium-disable-next-line security/no-inline-assembly - assembly { - value := mload(add(message, 52)) - } - return value; - } - - function getTransactionHash(bytes message) internal pure returns (bytes32) { - bytes32 hash; - // solium-disable-next-line security/no-inline-assembly - assembly { - hash := mload(add(message, 84)) - } - return hash; - } - - function getHomeGasPrice(bytes message) internal pure returns (uint256) { - uint256 gasPrice; - // solium-disable-next-line security/no-inline-assembly - assembly { - gasPrice := mload(add(message, 116)) - } - return gasPrice; - } -} - -// File: contracts/libraries/MessageSigning.sol +// File: contracts/libraries/Helpers.sol -// import "./Helpers.sol"; library Helpers { function addressArrayContains(address[] array, address value) internal pure returns (bool) { for (uint256 i = 0; i < array.length; i++) { @@ -149,6 +96,7 @@ library Helpers { } } + library MessageSigning { function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) { require(signature.length == 65); @@ -170,6 +118,64 @@ library MessageSigning { } } +// File: contracts/libraries/Message.sol + +library Message { + // layout of message :: bytes: + // offset 0: 32 bytes :: uint256 - message length + // offset 32: 20 bytes :: address - recipient address + // offset 52: 32 bytes :: uint256 - value + // offset 84: 32 bytes :: bytes32 - transaction hash + // offset 116: 32 bytes :: uint256 - home gas price + + // bytes 1 to 32 are 0 because message length is stored as little endian. + // mload always reads 32 bytes. + // so we can and have to start reading recipient at offset 20 instead of 32. + // if we were to read at 32 the address would contain part of value and be corrupted. + // when reading from offset 20 mload will read 12 zero bytes followed + // by the 20 recipient address bytes and correctly convert it into an address. + // this saves some storage/gas over the alternative solution + // which is padding address to 32 bytes and reading recipient at offset 32. + // for more details see discussion in: + // https://github.com/paritytech/parity-bridge/issues/61 + + function getRecipient(bytes message) internal pure returns (address) { + address recipient; + // solium-disable-next-line security/no-inline-assembly + assembly { + recipient := mload(add(message, 20)) + } + return recipient; + } + + function getValue(bytes message) internal pure returns (uint256) { + uint256 value; + // solium-disable-next-line security/no-inline-assembly + assembly { + value := mload(add(message, 52)) + } + return value; + } + + function getTransactionHash(bytes message) internal pure returns (bytes32) { + bytes32 hash; + // solium-disable-next-line security/no-inline-assembly + assembly { + hash := mload(add(message, 84)) + } + return hash; + } + + function getHomeGasPrice(bytes message) internal pure returns (uint256) { + uint256 gasPrice; + // solium-disable-next-line security/no-inline-assembly + assembly { + gasPrice := mload(add(message, 116)) + } + return gasPrice; + } +} + // File: contracts/libraries/SafeMath.sol /** @@ -220,14 +226,6 @@ library SafeMath { // File: contracts/HomeBridge.sol -// import "./libraries/Helpers.sol"; - - - - - - - contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { using SafeMath for uint256; uint256 public gasLimitWithdrawRelay; @@ -248,11 +246,12 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { Deposit(msg.sender, msg.value); } - function setGasLimitWithdrawRelay(uint256 _gas) public onlyValidator { + function setGasLimitWithdrawRelay(uint256 _gas) public onlyOwner { gasLimitWithdrawRelay = _gas; GasConsumptionLimitsUpdated(gasLimitWithdrawRelay); } + function withdraw(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) public { require(message.length == 116); // require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract)); diff --git a/flats/POA20_flat.sol b/flats/POA20_flat.sol index d29123dc7..7e3103dac 100644 --- a/flats/POA20_flat.sol +++ b/flats/POA20_flat.sol @@ -1,4 +1,4 @@ -pragma solidity 0.4.20; +pragma solidity 0.4.19; // File: contracts/ERC677Receiver.sol @@ -38,7 +38,7 @@ contract ERC20 is ERC20Basic { contract ERC677 is ERC20 { event Transfer(address indexed from, address indexed to, uint value, bytes data); - function transferAndCall(address, uint, bytes) returns (bool); + function transferAndCall(address, uint, bytes) public returns (bool); } diff --git a/flatten.sh b/flatten.sh new file mode 100755 index 000000000..b600b0114 --- /dev/null +++ b/flatten.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +rm -rf flats/* +./node_modules/.bin/truffle-flattener contracts/ForeignBridge.sol > flats/ForeignBridge_flat.sol +./node_modules/.bin/truffle-flattener contracts/BridgeValidators.sol > flats/BridgeValidators_flat.sol +./node_modules/.bin/truffle-flattener contracts/HomeBridge.sol > flats/HomeBridge_flat.sol +./node_modules/.bin/truffle-flattener contracts/POA20.sol > flats/POA20_flat.sol diff --git a/migrations/2_bridge_deployment.js b/migrations/2_bridge_deployment.js index 538060aeb..c044c3c90 100644 --- a/migrations/2_bridge_deployment.js +++ b/migrations/2_bridge_deployment.js @@ -4,11 +4,16 @@ const HomeBridge = artifacts.require("./HomeBridge.sol"); const ForeignBridge = artifacts.require("./ForeignBridge.sol"); module.exports = async function(deployer, network, accounts) { + let validators = ["0xb8988b690910913c97a090c3a6f80fad8b3a4683"] + console.log('deploying token') await deployer.deploy(POA20, "POA ERC20 on Foundation", "POA20", 18) const erc677token = await POA20.deployed() - await deployer.deploy(BridgeValidators, '1', [accounts[0]]); + console.log('deploying validators') + await deployer.deploy(BridgeValidators, '1', validators); const validatorContract = await BridgeValidators.deployed(); + console.log('deploying home') await deployer.deploy(HomeBridge, validatorContract.address); + console.log('deploying ForeignBridge') await deployer.deploy(ForeignBridge, validatorContract.address, erc677token.address); const foreignBridge = await ForeignBridge.deployed(); diff --git a/package-lock.json b/package-lock.json index 353dcaf94..618948595 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,16 +4,41 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "bignumber.js": { + "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", + "dev": true + }, "bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", @@ -53,6 +78,12 @@ "wrap-ansi": "2.1.0" } }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -71,6 +102,12 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "crypto-js": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", + "integrity": "sha1-cV8HC/YBTyrpkqmLOSkli3E/CNU=", + "dev": true + }, "debug": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", @@ -117,6 +154,18 @@ "number-to-bn": "1.7.0" } }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", @@ -241,11 +290,41 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, "js-sha3": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz", "integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=" }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "json3": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", @@ -287,6 +366,30 @@ "strip-bom": "2.0.0" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "dev": true + }, "lodash._baseassign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", @@ -351,6 +454,12 @@ "lodash.isarray": "3.0.4" } }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -447,6 +556,30 @@ "lcid": "1.0.0" } }, + "p-limit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -478,6 +611,12 @@ "pinkie-promise": "2.0.1" } }, + "pegjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", + "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -548,6 +687,12 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, "solc": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.19.tgz", @@ -560,6 +705,96 @@ "yargs": "4.8.1" } }, + "solidity-parser": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/solidity-parser/-/solidity-parser-0.4.0.tgz", + "integrity": "sha1-o0PxPac8kWgyeQNGgOgMSR3jQPo=", + "dev": true, + "requires": { + "mocha": "2.5.3", + "pegjs": "0.10.0", + "yargs": "4.8.1" + }, + "dependencies": { + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimatch": "0.3.0" + } + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + } + } + }, "spdx-correct": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", @@ -620,6 +855,12 @@ "has-flag": "1.0.0" } }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "dev": true + }, "truffle": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.0.6.tgz", @@ -630,6 +871,167 @@ "solc": "0.4.19" } }, + "truffle-blockchain-utils": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/truffle-blockchain-utils/-/truffle-blockchain-utils-0.0.4.tgz", + "integrity": "sha512-wgRrhwqh0aea08Hz28hUV4tuF2uTVQH/e9kBou+WK04cqrutB5cxQVQ6HGjeZLltxBYOFvhrGOOq4l3WJFnPEA==", + "dev": true + }, + "truffle-config": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/truffle-config/-/truffle-config-1.0.4.tgz", + "integrity": "sha512-E8pvJNAIjs7LNsjkYeS2dgoOnLoSBrTwb1xF5lJwfvZmGMFpKvVL1sa5jpFxozpf/WkRn/rfxy8zTdb3pq16jA==", + "dev": true, + "requires": { + "find-up": "2.1.0", + "lodash": "4.17.5", + "original-require": "1.0.1", + "truffle-error": "0.0.2", + "truffle-provider": "0.0.4" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + } + } + }, + "truffle-contract": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/truffle-contract/-/truffle-contract-3.0.4.tgz", + "integrity": "sha512-/1LCtJFf5Jvm5Rv88T0d/rZSKvaiW/yO1SHXLGJgKzLsiG1F/2spFs4HrI1mRxP00opfrYXloEmLtkVV/kcndQ==", + "dev": true, + "requires": { + "ethjs-abi": "0.1.8", + "truffle-blockchain-utils": "0.0.4", + "truffle-contract-schema": "2.0.0", + "truffle-error": "0.0.2", + "web3": "0.20.5" + }, + "dependencies": { + "ethjs-abi": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.1.8.tgz", + "integrity": "sha1-zSiFg+1ijN+tr4re+juh28vKbBg=", + "dev": true, + "requires": { + "bn.js": "4.11.6", + "js-sha3": "0.5.5", + "number-to-bn": "1.7.0" + } + } + } + }, + "truffle-contract-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/truffle-contract-schema/-/truffle-contract-schema-2.0.0.tgz", + "integrity": "sha512-nLlspmu1GKDaluWksBwitHi/7Z3IpRjmBYeO9N+T1nVJD2V4IWJaptCKP1NqnPiJA+FChB7+F7pI6Br51/FtXQ==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "crypto-js": "3.1.9-1", + "debug": "3.1.0" + }, + "dependencies": { + "crypto-js": { + "version": "3.1.9-1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", + "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "truffle-error": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/truffle-error/-/truffle-error-0.0.2.tgz", + "integrity": "sha1-AbGJt4UFVmrhaJwjnHyi3RIc/kw=", + "dev": true + }, + "truffle-expect": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/truffle-expect/-/truffle-expect-0.0.3.tgz", + "integrity": "sha1-m3XO80O9WW5+XbyHj18bLjGKlEw=", + "dev": true + }, + "truffle-flattener": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/truffle-flattener/-/truffle-flattener-1.2.3.tgz", + "integrity": "sha512-DisthKMI1qH+Xbw/S84CLPGeXz/kMkVUxBpz8Elj2kI4paVjEFEFwrq0juQHwxr2w/046r2tUT5usCE38Bw6Qw==", + "dev": true, + "requires": { + "find-up": "2.1.0", + "semver": "5.5.0", + "solidity-parser": "0.4.0", + "truffle-config": "1.0.4", + "truffle-resolver": "4.0.2", + "tsort": "0.0.1" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + } + } + }, + "truffle-provider": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/truffle-provider/-/truffle-provider-0.0.4.tgz", + "integrity": "sha512-yVxxjocxnJcFspQ0T4Rjq/1wvvm3iLxidb6oa1EAX5LsnSQLPG8wAM5+JLlJ4FDBsqJdZLGOq1RR5Ln/w7x5JA==", + "dev": true, + "requires": { + "truffle-error": "0.0.2", + "web3": "0.20.5" + } + }, + "truffle-provisioner": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/truffle-provisioner/-/truffle-provisioner-0.1.0.tgz", + "integrity": "sha1-Ap5SScEBUwBzhTXgT97ZMaU8T2I=", + "dev": true + }, + "truffle-resolver": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/truffle-resolver/-/truffle-resolver-4.0.2.tgz", + "integrity": "sha512-HKRd45HSfAqb9/BCOgYq4zkyl2lF40MvPDIGhyoPXFj5/9PSFzclyTkkMOdb+Rnm7oC1vY4cE1/k453lgf81Kw==", + "dev": true, + "requires": { + "async": "2.6.0", + "truffle-contract": "3.0.4", + "truffle-expect": "0.0.3", + "truffle-provisioner": "0.1.0" + } + }, + "tsort": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", + "integrity": "sha1-4igPXoF/i/QnVlf9D5rr1E9aJ4Y=", + "dev": true + }, + "utf8": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", + "integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY=", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", @@ -639,6 +1041,19 @@ "spdx-expression-parse": "1.0.4" } }, + "web3": { + "version": "0.20.5", + "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.5.tgz", + "integrity": "sha1-xQSNNfe/TixMKAzlH7u8lRKQsWU=", + "dev": true, + "requires": { + "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", + "crypto-js": "3.1.8", + "utf8": "2.1.2", + "xhr2": "0.1.4", + "xmlhttprequest": "1.8.0" + } + }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", @@ -663,6 +1078,18 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "xhr2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.1.4.tgz", + "integrity": "sha1-f4dliEdxbbUCYyOBL4GMras4el8=", + "dev": true + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "dev": true + }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", diff --git a/package.json b/package.json index d85949f3f..1b1e91507 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,17 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "truffle test", + "deploy": "truffle migrate --reset --network $NETWORK", + "flatten": "bash flatten.sh" }, "author": "", "license": "ISC", - "scripts": { - "test": "truffle test" - }, "dependencies": { "truffle": "^4.0.6", "zeppelin-solidity": "^1.6.0" + }, + "devDependencies": { + "truffle-flattener": "^1.2.3" } } From a3e884c6a6a1fb40338ecdd74b86e99a55237e44 Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Sun, 11 Mar 2018 13:12:49 -0700 Subject: [PATCH 5/8] Add daily Limits --- README.md | 15 +++++++ contracts/ForeignBridge.sol | 27 ++++++++++- contracts/HomeBridge.sol | 27 ++++++++++- flats/BridgeValidators_flat.sol | 1 + flats/ForeignBridge_flat.sol | 74 ++++++++++++++++++++++++++++++- flats/HomeBridge_flat.sol | 27 ++++++++++- migrations/2_bridge_deployment.js | 6 ++- 7 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..391d121ef --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Dependencies +```bash +npm install +``` + +# To Deploy +Check `truffle.js` for networks and their ports +```bash +NETWORK=sokol npm run deploy +``` + +# To Flatten +```bash +npm run flatten +``` diff --git a/contracts/ForeignBridge.sol b/contracts/ForeignBridge.sol index fd1f406d0..30a44a180 100644 --- a/contracts/ForeignBridge.sol +++ b/contracts/ForeignBridge.sol @@ -1,4 +1,5 @@ pragma solidity 0.4.19; +import "./libraries/SafeMath.sol"; import "./libraries/Helpers.sol"; import "./libraries/Message.sol"; import "./IBridgeValidators.sol"; @@ -8,15 +9,18 @@ import "./IBurnableMintableERC677Token.sol"; import "./ERC677Receiver.sol"; contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressStorage { + using SafeMath for uint256; uint256 public gasLimitDepositRelay; uint256 public gasLimitWithdrawConfirm; uint256 homeGasPrice = 1000000000 wei; + uint256 public foreignDailyLimit; mapping (bytes32 => bytes) messages; mapping (bytes32 => bytes) signatures; mapping (bytes32 => bool) messages_signed; mapping (bytes32 => uint256) num_messages_signed; mapping (bytes32 => bool) deposits_signed; mapping (bytes32 => uint256) num_deposits_signed; + mapping (uint256 => uint256) totalSpentPerDay; IBurnableMintableERC677Token public erc677token; @@ -33,12 +37,16 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt event SignedForDeposit(address indexed signer, bytes32 message); event SignedForWithdraw(address indexed signer, bytes32 message); + event DailyLimit(uint256 newLimit); function ForeignBridge( address _validatorContract, - address _erc677token + address _erc677token, + uint256 _foreignDailyLimit ) public Validatable(_validatorContract) { + require(_foreignDailyLimit > 0); erc677token = IBurnableMintableERC677Token(_erc677token); + foreignDailyLimit = _foreignDailyLimit; } function setGasLimitDepositRelay(uint256 _gas) public onlyOwner { @@ -81,6 +89,8 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt function onTokenTransfer(address _from, uint256 _value, bytes _data) external returns(bool) { require(erc677token != address(0x0)); require(msg.sender == address(erc677token)); + require(withinLimit(_value)); + totalSpentPerDay[getCurrentDay()] = totalSpentPerDay[getCurrentDay()].add(_value); erc677token.burn(_value); Withdraw(_from, _value, homeGasPrice); return true; @@ -137,4 +147,19 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt return messages[hash]; } + function getCurrentDay() public view returns(uint256) { + return now / 1 days; + } + + function setDailyLimit(uint256 _foreignDailyLimit) public onlyOwner { + require(_foreignDailyLimit > 0); + foreignDailyLimit = _foreignDailyLimit; + DailyLimit(foreignDailyLimit); + } + + function withinLimit(uint256 _amount) public view returns(bool) { + uint256 nextLimit = totalSpentPerDay[getCurrentDay()].add(_amount); + return foreignDailyLimit >= nextLimit; + } + } diff --git a/contracts/HomeBridge.sol b/contracts/HomeBridge.sol index 18690a9d3..df9e957d5 100644 --- a/contracts/HomeBridge.sol +++ b/contracts/HomeBridge.sol @@ -11,19 +11,29 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { using SafeMath for uint256; uint256 public gasLimitWithdrawRelay; uint256 public estimatedGasCostOfWithdraw; + uint256 public homeDailyLimit; + mapping (uint256 => uint256) totalSpentPerDay; mapping (bytes32 => bool) withdraws; event GasConsumptionLimitsUpdated(uint256 gas); event Deposit (address recipient, uint256 value); event Withdraw (address recipient, uint256 value); + event DailyLimit(uint256 newLimit); function HomeBridge ( - address _validatorContract + address _validatorContract, + uint256 _homeDailyLimit ) public Validatable(_validatorContract) { + require(_homeDailyLimit > 0); + homeDailyLimit = _homeDailyLimit; + DailyLimit(homeDailyLimit); } /// Should be used to deposit money. function () public payable { + require(msg.value > 0); + require(withinLimit(msg.value)); + totalSpentPerDay[getCurrentDay()] = totalSpentPerDay[getCurrentDay()].add(msg.value); Deposit(msg.sender, msg.value); } @@ -59,4 +69,19 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { Withdraw(recipient, valueRemainingAfterSubtractingCost); } + + function setDailyLimit(uint256 _homeDailyLimit) public onlyOwner { + require(_homeDailyLimit > 0); + homeDailyLimit = _homeDailyLimit; + DailyLimit(homeDailyLimit); + } + + function getCurrentDay() public view returns(uint256) { + return now / 1 days; + } + + function withinLimit(uint256 _amount) public view returns(bool) { + uint256 nextLimit = totalSpentPerDay[getCurrentDay()].add(_amount); + return homeDailyLimit >= nextLimit; + } } diff --git a/flats/BridgeValidators_flat.sol b/flats/BridgeValidators_flat.sol index 347232257..d46b7550b 100644 --- a/flats/BridgeValidators_flat.sol +++ b/flats/BridgeValidators_flat.sol @@ -103,4 +103,5 @@ contract BridgeValidators is Ownable, IBridgeValidators { function currentOwner() public view returns(address) { return owner; } + } diff --git a/flats/ForeignBridge_flat.sol b/flats/ForeignBridge_flat.sol index 79af834b5..f17260e1a 100644 --- a/flats/ForeignBridge_flat.sol +++ b/flats/ForeignBridge_flat.sol @@ -225,18 +225,69 @@ library Message { } } +// File: contracts/libraries/SafeMath.sol + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + uint256 c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return c; + } + + /** + * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c >= a); + return c; + } +} + // File: contracts/ForeignBridge.sol contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressStorage { + using SafeMath for uint256; uint256 public gasLimitDepositRelay; uint256 public gasLimitWithdrawConfirm; uint256 homeGasPrice = 1000000000 wei; + uint256 public foreignDailyLimit; mapping (bytes32 => bytes) messages; mapping (bytes32 => bytes) signatures; mapping (bytes32 => bool) messages_signed; mapping (bytes32 => uint256) num_messages_signed; mapping (bytes32 => bool) deposits_signed; mapping (bytes32 => uint256) num_deposits_signed; + mapping (uint256 => uint256) totalSpentPerDay; IBurnableMintableERC677Token public erc677token; @@ -253,12 +304,16 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt event SignedForDeposit(address indexed signer, bytes32 message); event SignedForWithdraw(address indexed signer, bytes32 message); + event DailyLimit(uint256 newLimit); function ForeignBridge( address _validatorContract, - address _erc677token + address _erc677token, + uint256 _foreignDailyLimit ) public Validatable(_validatorContract) { + require(_foreignDailyLimit > 0); erc677token = IBurnableMintableERC677Token(_erc677token); + foreignDailyLimit = _foreignDailyLimit; } function setGasLimitDepositRelay(uint256 _gas) public onlyOwner { @@ -301,6 +356,8 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt function onTokenTransfer(address _from, uint256 _value, bytes _data) external returns(bool) { require(erc677token != address(0x0)); require(msg.sender == address(erc677token)); + require(withinLimit(_value)); + totalSpentPerDay[getCurrentDay()] = totalSpentPerDay[getCurrentDay()].add(_value); erc677token.burn(_value); Withdraw(_from, _value, homeGasPrice); return true; @@ -357,4 +414,19 @@ contract ForeignBridge is ERC677Receiver, Validatable, BridgeDeploymentAddressSt return messages[hash]; } + function getCurrentDay() public view returns(uint256) { + return now / 1 days; + } + + function setDailyLimit(uint256 _foreignDailyLimit) public onlyOwner { + require(_foreignDailyLimit > 0); + foreignDailyLimit = _foreignDailyLimit; + DailyLimit(foreignDailyLimit); + } + + function withinLimit(uint256 _amount) public view returns(bool) { + uint256 nextLimit = totalSpentPerDay[getCurrentDay()].add(_amount); + return foreignDailyLimit >= nextLimit; + } + } diff --git a/flats/HomeBridge_flat.sol b/flats/HomeBridge_flat.sol index 4c7479e96..c663fe0eb 100644 --- a/flats/HomeBridge_flat.sol +++ b/flats/HomeBridge_flat.sol @@ -230,19 +230,29 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { using SafeMath for uint256; uint256 public gasLimitWithdrawRelay; uint256 public estimatedGasCostOfWithdraw; + uint256 public homeDailyLimit; + mapping (uint256 => uint256) totalSpentPerDay; mapping (bytes32 => bool) withdraws; event GasConsumptionLimitsUpdated(uint256 gas); event Deposit (address recipient, uint256 value); event Withdraw (address recipient, uint256 value); + event DailyLimit(uint256 newLimit); function HomeBridge ( - address _validatorContract + address _validatorContract, + uint256 _homeDailyLimit ) public Validatable(_validatorContract) { + require(_homeDailyLimit > 0); + homeDailyLimit = _homeDailyLimit; + DailyLimit(homeDailyLimit); } /// Should be used to deposit money. function () public payable { + require(msg.value > 0); + require(withinLimit(msg.value)); + totalSpentPerDay[getCurrentDay()] = totalSpentPerDay[getCurrentDay()].add(msg.value); Deposit(msg.sender, msg.value); } @@ -278,4 +288,19 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { Withdraw(recipient, valueRemainingAfterSubtractingCost); } + + function setDailyLimit(uint256 _homeDailyLimit) public onlyOwner { + require(_homeDailyLimit > 0); + homeDailyLimit = _homeDailyLimit; + DailyLimit(homeDailyLimit); + } + + function getCurrentDay() public view returns(uint256) { + return now / 1 days; + } + + function withinLimit(uint256 _amount) public view returns(bool) { + uint256 nextLimit = totalSpentPerDay[getCurrentDay()].add(_amount); + return homeDailyLimit >= nextLimit; + } } diff --git a/migrations/2_bridge_deployment.js b/migrations/2_bridge_deployment.js index c044c3c90..888531cde 100644 --- a/migrations/2_bridge_deployment.js +++ b/migrations/2_bridge_deployment.js @@ -5,6 +5,8 @@ const ForeignBridge = artifacts.require("./ForeignBridge.sol"); module.exports = async function(deployer, network, accounts) { let validators = ["0xb8988b690910913c97a090c3a6f80fad8b3a4683"] + const homeDailyLimit = '1000000000000000000' // 1 ether + const foreignDailyLimit = '1000000000000000000' // 1 ether console.log('deploying token') await deployer.deploy(POA20, "POA ERC20 on Foundation", "POA20", 18) const erc677token = await POA20.deployed() @@ -12,9 +14,9 @@ module.exports = async function(deployer, network, accounts) { await deployer.deploy(BridgeValidators, '1', validators); const validatorContract = await BridgeValidators.deployed(); console.log('deploying home') - await deployer.deploy(HomeBridge, validatorContract.address); + await deployer.deploy(HomeBridge, validatorContract.address, homeDailyLimit); console.log('deploying ForeignBridge') - await deployer.deploy(ForeignBridge, validatorContract.address, erc677token.address); + await deployer.deploy(ForeignBridge, validatorContract.address, erc677token.address, foreignDailyLimit); const foreignBridge = await ForeignBridge.deployed(); await erc677token.transferOwnership(foreignBridge.address) From 48c782f5e549cf63650777c59d8d9cada2b505e7 Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Mon, 12 Mar 2018 14:52:51 -0700 Subject: [PATCH 6/8] Add hasEnoughSignatures back to working condition --- contracts/HomeBridge.sol | 3 +-- contracts/libraries/Helpers.sol | 10 ++++------ flats/ForeignBridge_flat.sol | 10 ++++------ flats/HomeBridge_flat.sol | 13 +++++-------- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/contracts/HomeBridge.sol b/contracts/HomeBridge.sol index df9e957d5..3c31e4999 100644 --- a/contracts/HomeBridge.sol +++ b/contracts/HomeBridge.sol @@ -42,10 +42,9 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { GasConsumptionLimitsUpdated(gasLimitWithdrawRelay); } - function withdraw(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) public { require(message.length == 116); - // require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract)); + require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract)); address recipient = Message.getRecipient(message); uint256 value = Message.getValue(message); diff --git a/contracts/libraries/Helpers.sol b/contracts/libraries/Helpers.sol index 7e3612b1c..fa52aecf8 100644 --- a/contracts/libraries/Helpers.sol +++ b/contracts/libraries/Helpers.sol @@ -38,16 +38,14 @@ library Helpers { bytes32[] _rs, bytes32[] _ss, IBridgeValidators _validatorContract) internal view returns (bool) { - uint8 _requiredSignatures = _validatorContract.requiredSignatures(); - require(_vs.length < _requiredSignatures); + uint8 requiredSignatures = _validatorContract.requiredSignatures(); + require(_vs.length <= requiredSignatures); bytes32 hash = MessageSigning.hashMessage(_message); - address[] memory encounteredAddresses = new address[](_requiredSignatures); + address[] memory encounteredAddresses = new address[](requiredSignatures); - for (uint8 i = 0; i < _requiredSignatures; i++) { + for (uint8 i = 0; i < requiredSignatures; i++) { address recoveredAddress = ecrecover(hash, _vs[i], _rs[i], _ss[i]); - // only signatures by addresses in `addresses` are allowed require(_validatorContract.isValidator(recoveredAddress)); - // duplicate signatures are not allowed if (addressArrayContains(encounteredAddresses, recoveredAddress)) { return false; } diff --git a/flats/ForeignBridge_flat.sol b/flats/ForeignBridge_flat.sol index f17260e1a..6970f7dbc 100644 --- a/flats/ForeignBridge_flat.sol +++ b/flats/ForeignBridge_flat.sol @@ -126,16 +126,14 @@ library Helpers { bytes32[] _rs, bytes32[] _ss, IBridgeValidators _validatorContract) internal view returns (bool) { - uint8 _requiredSignatures = _validatorContract.requiredSignatures(); - require(_vs.length < _requiredSignatures); + uint8 requiredSignatures = _validatorContract.requiredSignatures(); + require(_vs.length <= requiredSignatures); bytes32 hash = MessageSigning.hashMessage(_message); - address[] memory encounteredAddresses = new address[](_requiredSignatures); + address[] memory encounteredAddresses = new address[](requiredSignatures); - for (uint8 i = 0; i < _requiredSignatures; i++) { + for (uint8 i = 0; i < requiredSignatures; i++) { address recoveredAddress = ecrecover(hash, _vs[i], _rs[i], _ss[i]); - // only signatures by addresses in `addresses` are allowed require(_validatorContract.isValidator(recoveredAddress)); - // duplicate signatures are not allowed if (addressArrayContains(encounteredAddresses, recoveredAddress)) { return false; } diff --git a/flats/HomeBridge_flat.sol b/flats/HomeBridge_flat.sol index c663fe0eb..3cdb897d3 100644 --- a/flats/HomeBridge_flat.sol +++ b/flats/HomeBridge_flat.sol @@ -77,16 +77,14 @@ library Helpers { bytes32[] _rs, bytes32[] _ss, IBridgeValidators _validatorContract) internal view returns (bool) { - uint8 _requiredSignatures = _validatorContract.requiredSignatures(); - require(_vs.length < _requiredSignatures); + uint8 requiredSignatures = _validatorContract.requiredSignatures(); + require(_vs.length <= requiredSignatures); bytes32 hash = MessageSigning.hashMessage(_message); - address[] memory encounteredAddresses = new address[](_requiredSignatures); + address[] memory encounteredAddresses = new address[](requiredSignatures); - for (uint8 i = 0; i < _requiredSignatures; i++) { + for (uint8 i = 0; i < requiredSignatures; i++) { address recoveredAddress = ecrecover(hash, _vs[i], _rs[i], _ss[i]); - // only signatures by addresses in `addresses` are allowed require(_validatorContract.isValidator(recoveredAddress)); - // duplicate signatures are not allowed if (addressArrayContains(encounteredAddresses, recoveredAddress)) { return false; } @@ -261,10 +259,9 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { GasConsumptionLimitsUpdated(gasLimitWithdrawRelay); } - function withdraw(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) public { require(message.length == 116); - // require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract)); + require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, validatorContract)); address recipient = Message.getRecipient(message); uint256 value = Message.getValue(message); From 7d1df24f033d5348c6eceaebe568826b1bed442e Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Mon, 12 Mar 2018 15:02:50 -0700 Subject: [PATCH 7/8] remove homeGasPrice from HomeBridge --- contracts/HomeBridge.sol | 15 ++------------- flats/HomeBridge_flat.sol | 15 ++------------- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/contracts/HomeBridge.sol b/contracts/HomeBridge.sol index 3c31e4999..b81b76427 100644 --- a/contracts/HomeBridge.sol +++ b/contracts/HomeBridge.sol @@ -10,7 +10,6 @@ import "./BridgeDeploymentAddressStorage.sol"; contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { using SafeMath for uint256; uint256 public gasLimitWithdrawRelay; - uint256 public estimatedGasCostOfWithdraw; uint256 public homeDailyLimit; mapping (uint256 => uint256) totalSpentPerDay; mapping (bytes32 => bool) withdraws; @@ -49,24 +48,14 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { address recipient = Message.getRecipient(message); uint256 value = Message.getValue(message); bytes32 hash = Message.getTransactionHash(message); - uint256 homeGasPrice = Message.getHomeGasPrice(message); - require((recipient == msg.sender) || (tx.gasprice == homeGasPrice)); require(!withdraws[hash]); // Order of operations below is critical to avoid TheDAO-like re-entry bug withdraws[hash] = true; - uint256 estimatedWeiCostOfWithdraw = estimatedGasCostOfWithdraw.mul(homeGasPrice); - - // charge recipient for relay cost - uint256 valueRemainingAfterSubtractingCost = value.sub(estimatedWeiCostOfWithdraw); - // pay out recipient - recipient.transfer(valueRemainingAfterSubtractingCost); - - // refund relay cost to relaying authority - msg.sender.transfer(estimatedWeiCostOfWithdraw); + recipient.transfer(value); - Withdraw(recipient, valueRemainingAfterSubtractingCost); + Withdraw(recipient, value); } function setDailyLimit(uint256 _homeDailyLimit) public onlyOwner { diff --git a/flats/HomeBridge_flat.sol b/flats/HomeBridge_flat.sol index 3cdb897d3..d21231e8b 100644 --- a/flats/HomeBridge_flat.sol +++ b/flats/HomeBridge_flat.sol @@ -227,7 +227,6 @@ library SafeMath { contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { using SafeMath for uint256; uint256 public gasLimitWithdrawRelay; - uint256 public estimatedGasCostOfWithdraw; uint256 public homeDailyLimit; mapping (uint256 => uint256) totalSpentPerDay; mapping (bytes32 => bool) withdraws; @@ -266,24 +265,14 @@ contract HomeBridge is Validatable, BridgeDeploymentAddressStorage { address recipient = Message.getRecipient(message); uint256 value = Message.getValue(message); bytes32 hash = Message.getTransactionHash(message); - uint256 homeGasPrice = Message.getHomeGasPrice(message); - require((recipient == msg.sender) || (tx.gasprice == homeGasPrice)); require(!withdraws[hash]); // Order of operations below is critical to avoid TheDAO-like re-entry bug withdraws[hash] = true; - uint256 estimatedWeiCostOfWithdraw = estimatedGasCostOfWithdraw.mul(homeGasPrice); - - // charge recipient for relay cost - uint256 valueRemainingAfterSubtractingCost = value.sub(estimatedWeiCostOfWithdraw); - // pay out recipient - recipient.transfer(valueRemainingAfterSubtractingCost); - - // refund relay cost to relaying authority - msg.sender.transfer(estimatedWeiCostOfWithdraw); + recipient.transfer(value); - Withdraw(recipient, valueRemainingAfterSubtractingCost); + Withdraw(recipient, value); } function setDailyLimit(uint256 _homeDailyLimit) public onlyOwner { From f99f21a02cddf6c1316ae5bd59115c1317246cf4 Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Mon, 12 Mar 2018 22:05:50 -0700 Subject: [PATCH 8/8] Fix hasEnoughSignatures method. --- contracts/libraries/Helpers.sol | 2 +- contracts/libraries/Message.sol | 9 --------- flats/ForeignBridge_flat.sol | 11 +---------- flats/HomeBridge_flat.sol | 11 +---------- 4 files changed, 3 insertions(+), 30 deletions(-) diff --git a/contracts/libraries/Helpers.sol b/contracts/libraries/Helpers.sol index fa52aecf8..bae6e4daf 100644 --- a/contracts/libraries/Helpers.sol +++ b/contracts/libraries/Helpers.sol @@ -39,7 +39,7 @@ library Helpers { bytes32[] _ss, IBridgeValidators _validatorContract) internal view returns (bool) { uint8 requiredSignatures = _validatorContract.requiredSignatures(); - require(_vs.length <= requiredSignatures); + require(_vs.length >= requiredSignatures); bytes32 hash = MessageSigning.hashMessage(_message); address[] memory encounteredAddresses = new address[](requiredSignatures); diff --git a/contracts/libraries/Message.sol b/contracts/libraries/Message.sol index 53d1223a1..825ed3d4e 100644 --- a/contracts/libraries/Message.sol +++ b/contracts/libraries/Message.sol @@ -46,13 +46,4 @@ library Message { } return hash; } - - function getHomeGasPrice(bytes message) internal pure returns (uint256) { - uint256 gasPrice; - // solium-disable-next-line security/no-inline-assembly - assembly { - gasPrice := mload(add(message, 116)) - } - return gasPrice; - } } diff --git a/flats/ForeignBridge_flat.sol b/flats/ForeignBridge_flat.sol index 6970f7dbc..079389f70 100644 --- a/flats/ForeignBridge_flat.sol +++ b/flats/ForeignBridge_flat.sol @@ -127,7 +127,7 @@ library Helpers { bytes32[] _ss, IBridgeValidators _validatorContract) internal view returns (bool) { uint8 requiredSignatures = _validatorContract.requiredSignatures(); - require(_vs.length <= requiredSignatures); + require(_vs.length >= requiredSignatures); bytes32 hash = MessageSigning.hashMessage(_message); address[] memory encounteredAddresses = new address[](requiredSignatures); @@ -212,15 +212,6 @@ library Message { } return hash; } - - function getHomeGasPrice(bytes message) internal pure returns (uint256) { - uint256 gasPrice; - // solium-disable-next-line security/no-inline-assembly - assembly { - gasPrice := mload(add(message, 116)) - } - return gasPrice; - } } // File: contracts/libraries/SafeMath.sol diff --git a/flats/HomeBridge_flat.sol b/flats/HomeBridge_flat.sol index d21231e8b..cf9af7caa 100644 --- a/flats/HomeBridge_flat.sol +++ b/flats/HomeBridge_flat.sol @@ -78,7 +78,7 @@ library Helpers { bytes32[] _ss, IBridgeValidators _validatorContract) internal view returns (bool) { uint8 requiredSignatures = _validatorContract.requiredSignatures(); - require(_vs.length <= requiredSignatures); + require(_vs.length >= requiredSignatures); bytes32 hash = MessageSigning.hashMessage(_message); address[] memory encounteredAddresses = new address[](requiredSignatures); @@ -163,15 +163,6 @@ library Message { } return hash; } - - function getHomeGasPrice(bytes message) internal pure returns (uint256) { - uint256 gasPrice; - // solium-disable-next-line security/no-inline-assembly - assembly { - gasPrice := mload(add(message, 116)) - } - return gasPrice; - } } // File: contracts/libraries/SafeMath.sol