Skip to content

Commit

Permalink
Merge pull request #3 from rstormsf/master
Browse files Browse the repository at this point in the history
Create Governance based bridge contracts
  • Loading branch information
akolotov authored Mar 13, 2018
2 parents 5631b13 + f99f21a commit d53ae35
Show file tree
Hide file tree
Showing 25 changed files with 2,193 additions and 265 deletions.
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
```
10 changes: 10 additions & 0 deletions contracts/BridgeDeploymentAddressStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma solidity ^0.4.19;


contract BridgeDeploymentAddressStorage {
uint256 public deployedAtBlock;

function BridgeDeploymentAddressStorage() public {
deployedAtBlock = block.number;
}
}
104 changes: 55 additions & 49 deletions contracts/BridgeValidators.sol
Original file line number Diff line number Diff line change
@@ -1,52 +1,58 @@
pragma solidity ^0.4.18;

import 'zeppelin-solidity/contracts/ownership/Ownable.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;

mapping (address => bool) public validators;

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]);
}
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;
}
import "zeppelin-solidity/contracts/ownership/Ownable.sol";
import "./IBridgeValidators.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;
}

function currentOwner() public view returns(address) {
return owner;
}
}
10 changes: 10 additions & 0 deletions contracts/ERC677.sol
Original file line number Diff line number Diff line change
@@ -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) public returns (bool);

}
6 changes: 6 additions & 0 deletions contracts/ERC677Receiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pragma solidity ^0.4.19;


contract ERC677Receiver {
function onTokenTransfer(address _from, uint _value, bytes _data) external returns(bool);
}
182 changes: 117 additions & 65 deletions contracts/ForeignBridge.sol
Original file line number Diff line number Diff line change
@@ -1,75 +1,99 @@
pragma solidity 0.4.19;
import "./libraries/SafeMath.sol";
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 {
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 => uint8) num_deposits_signed;
mapping (bytes32 => bool) mintRequestsDone;
mapping (bytes32 => uint8) withdrawRequests;

/// Pending signatures and authorities who confirmed them
BridgeValidators public validatorsContract;
mapping (bytes32 => uint256) num_deposits_signed;
mapping (uint256 => uint256) totalSpentPerDay;

/// 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 GasConsumptionLimitsUpdated(uint256 gasLimitDepositRelay, uint256 gasLimitWithdrawConfirm);

event SignedForDeposit(address indexed signer, bytes32 message);
event SignedForWithdraw(address indexed signer, bytes32 message);
event DailyLimit(uint256 newLimit);

function ForeignBridge(
address _token,
address _validatorsContract
) public
{
require(_token != address(0) && _validatorsContract != address(0));
validatorsContract = BridgeValidators(_validatorsContract);
token = _token;
address _validatorContract,
address _erc677token,
uint256 _foreignDailyLimit
) public Validatable(_validatorContract) {
require(_foreignDailyLimit > 0);
erc677token = IBurnableMintableERC677Token(_erc677token);
foreignDailyLimit = _foreignDailyLimit;
}

function setGasLimitDepositRelay(uint256 _gas) public onlyOwner {
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) public onlyOwner {
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;
uint256 signed = num_deposits_signed[hash_msg] + 1;
num_deposits_signed[hash_msg] = signed;

if (signed == requiredValidators) {
// token.mint(recipient, value);
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);
}

}

/// 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));
require(withinLimit(_value));
totalSpentPerDay[getCurrentDay()] = totalSpentPerDay[getCurrentDay()].add(_value);
erc677token.burn(_value);
Withdraw(_from, _value, homeGasPrice);
return true;
}

/// Should be used as sync tool
Expand All @@ -80,34 +104,62 @@ 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));

// Valid withdraw message must have 84 bytes
require(message.length == 84);
var hash = keccak256(message);
require(message.length == 116);
bytes32 hash = keccak256(message);
bytes32 hash_sender = keccak256(msg.sender, hash);

// 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);
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 (signatures[hash].signed.length == requiredSignatures) {
SignedForWithdraw(msg.sender, hash);
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 signature
// function signature(bytes32 hash, uint index) public view returns (bytes) {
// return signatures[hash].signatures[index];
// }
/// Get message
function message(bytes32 hash) public view returns (bytes) {
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;
}

// /// Get message
// function message(bytes32 hash) public view returns (bytes) {
// return signatures[hash].message;
// }
}
}
Loading

0 comments on commit d53ae35

Please sign in to comment.