poa-bridge/contracts/bridge.sol

357 lines
12 KiB
Solidity
Raw Normal View History

2018-01-04 06:42:21 -08:00
pragma solidity ^0.4.17;
2017-06-13 02:18:03 -07:00
2017-06-13 02:18:03 -07:00
library Authorities {
function contains (address[] self, address value) internal pure returns (bool) {
2017-06-13 02:18:03 -07:00
for (uint i = 0; i < self.length; i++) {
if (self[i] == value) {
return true;
}
}
return false;
}
}
/// Library used only to test Signer library via rpc calls
library SignerTest {
function signer (bytes signature, bytes message) public pure returns (address) {
2017-09-04 07:02:41 -07:00
return Signer.signer(signature, message);
}
}
library Utils {
function toString (uint256 inputValue) internal pure returns (string str) {
// it is used only for small numbers
bytes memory reversed = new bytes(8);
uint workingValue = inputValue;
uint i = 0;
while (workingValue != 0) {
uint remainder = workingValue % 10;
workingValue = workingValue / 10;
reversed[i++] = byte(48 + remainder);
}
2017-09-04 03:50:43 -07:00
bytes memory s = new bytes(i);
for (uint j = 0; j < i; j++) {
s[j] = reversed[i - j - 1];
}
str = string(s);
}
}
library Signer {
function signer (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(hash(message), uint8(v), r, s);
}
function hash (bytes message) internal pure returns (bytes32) {
bytes memory prefix = "\x19Ethereum Signed Message:\n";
return keccak256(prefix, Utils.toString(message.length), message);
}
}
2017-10-10 02:02:46 -07:00
contract HomeBridge {
2017-06-13 02:18:03 -07:00
using Authorities for address[];
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Number of authorities signatures required to withdraw the money.
///
/// Must be lesser than number of authorities.
2017-09-01 06:11:20 -07:00
uint public requiredSignatures;
2017-08-25 07:27:55 -07:00
/// 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.
uint public estimatedGasCostOfWithdraw;
2017-06-13 02:18:03 -07:00
/// Contract authorities.
2017-09-01 06:11:20 -07:00
address[] public authorities;
2017-08-25 07:27:55 -07:00
2017-10-10 02:02:46 -07:00
/// Used foreign transaction hashes.
2017-06-13 02:18:03 -07:00
mapping (bytes32 => bool) withdraws;
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Event created on money deposit.
event Deposit (address recipient, uint value);
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Event created on money withdraw.
event Withdraw (address recipient, uint value);
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Multisig authority validation
modifier allAuthorities (uint8[] v, bytes32[] r, bytes32[] s, bytes message) {
var hash = Signer.hash(message);
2017-06-13 02:18:03 -07:00
var used = new address[](requiredSignatures);
2017-08-25 07:27:55 -07:00
require(requiredSignatures <= v.length);
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
for (uint i = 0; i < requiredSignatures; i++) {
var a = ecrecover(hash, v[i], r[i], s[i]);
require(authorities.contains(a));
require(!used.contains(a));
2017-06-13 02:18:03 -07:00
used[i] = a;
}
_;
}
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Constructor.
function HomeBridge (
uint requiredSignaturesParam,
address[] authoritiesParam,
uint estimatedGasCostOfWithdrawParam
2018-01-04 06:29:54 -08:00
) public
{
require(requiredSignaturesParam != 0);
require(requiredSignaturesParam <= authoritiesParam.length);
requiredSignatures = requiredSignaturesParam;
authorities = authoritiesParam;
estimatedGasCostOfWithdraw = estimatedGasCostOfWithdrawParam;
2017-06-13 02:18:03 -07:00
}
/// Should be used to deposit money.
function () public payable {
2017-06-13 02:18:03 -07:00
Deposit(msg.sender, msg.value);
}
// layout of message :: bytes:
// offset 0: 32 bytes :: uint (little endian) - message length
// offset 32: 20 bytes :: address - recipient address
// offset 52: 32 bytes :: uint (little endian) - value
// offset 84: 32 bytes :: bytes32 - transaction hash
// 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 getRecipientFromMessage(bytes message) public pure returns (address) {
address recipient;
// solium-disable-next-line security/no-inline-assembly
assembly {
recipient := mload(add(message, 20))
}
return recipient;
}
function getValueFromMessage(bytes message) public pure returns (uint) {
uint value;
// solium-disable-next-line security/no-inline-assembly
assembly {
value := mload(add(message, 52))
}
return value;
}
function getTransactionHashFromMessage(bytes message) public pure returns (bytes32) {
bytes32 hash;
// solium-disable-next-line security/no-inline-assembly
assembly {
hash := mload(add(message, 84))
}
return hash;
}
/// 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 messageValueSufficientToCoverRelay(bytes message) public view returns (bool) {
return getValueFromMessage(message) > getWithdrawRelayCost();
}
/// an upper bound to the cost of relaying a withdraw by calling HomeBridge.withdraw
function getWithdrawRelayCost() public view returns (uint) {
return estimatedGasCostOfWithdraw * tx.gasprice;
}
/// Used to withdraw money from the contract.
2017-06-13 02:18:03 -07:00
///
/// message contains:
/// withdrawal recipient (bytes20)
/// withdrawal value (uint)
2017-10-10 02:02:46 -07:00
/// 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 = getRecipientFromMessage(message);
uint value = getValueFromMessage(message);
bytes32 hash = getTransactionHashFromMessage(message);
2017-08-25 07:27:55 -07:00
// The following two statements guard against reentry into this function.
// Duplicated withdraw or reentry.
require(!withdraws[hash]);
2018-01-04 05:14:29 -08:00
// Order of operations below is critical to avoid TheDAO-like re-entry bug
2017-06-13 02:18:03 -07:00
withdraws[hash] = true;
uint estimatedWeiCostOfWithdraw = estimatedGasCostOfWithdraw * tx.gasprice;
// this fails if `value` is not even enough to cover the relay cost.
// Authorities simply IGNORE withdraws where `value` cant relay cost.
// Think of it as `value` getting burned entirely on the relay with no value left to pay out the recipient.
require(value > estimatedWeiCostOfWithdraw);
// charge recipient for relay cost
uint valueRemainingAfterSubtractingCost = value - estimatedWeiCostOfWithdraw;
// pay out recipient
recipient.transfer(valueRemainingAfterSubtractingCost);
// refund relay cost to relaying authority
msg.sender.transfer(estimatedWeiCostOfWithdraw);
Withdraw(recipient, valueRemainingAfterSubtractingCost);
2017-06-13 02:18:03 -07:00
}
}
2017-10-10 02:02:46 -07:00
contract ForeignBridge {
2017-06-13 02:18:03 -07:00
using Authorities for address[];
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
struct SignaturesCollection {
/// Signed message.
bytes message;
2017-08-22 04:47:01 -07:00
/// Authorities who signed the message.
2017-06-13 02:18:03 -07:00
address[] signed;
2017-08-22 04:47:01 -07:00
/// Signaturs
bytes[] signatures;
2017-06-13 02:18:03 -07:00
}
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Number of authorities signatures required to withdraw the money.
///
/// Must be lesser than number of authorities.
uint public requiredSignatures;
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Contract authorities.
address[] public authorities;
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Ether balances
mapping (address => uint) public balances;
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Pending deposits and authorities who confirmed them
mapping (bytes32 => address[]) deposits;
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Pending signatures and authorities who confirmed them
mapping (bytes32 => SignaturesCollection) signatures;
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Event created on money deposit.
event Deposit(address recipient, uint value);
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Event created on money withdraw.
event Withdraw(address recipient, uint value);
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Event created on money transfer
event Transfer(address from, address to, uint value);
2017-08-25 07:27:55 -07:00
2017-10-10 02:02:46 -07:00
/// Collected signatures which should be relayed to home chain.
2017-08-22 04:53:40 -07:00
event CollectedSignatures(address authority, bytes32 messageHash);
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Constructor.
function ForeignBridge(
uint requiredSignaturesParam,
address[] authoritiesParam
2018-01-04 06:29:54 -08:00
) public
{
require(requiredSignaturesParam != 0);
require(requiredSignaturesParam <= authoritiesParam.length);
requiredSignatures = requiredSignaturesParam;
authorities = authoritiesParam;
2017-06-13 02:18:03 -07:00
}
/// Multisig authority validation
modifier onlyAuthority () {
require(authorities.contains(msg.sender));
2017-06-13 02:18:03 -07:00
_;
}
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// 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, uint value, bytes32 transactionHash) public onlyAuthority() {
2017-09-04 03:50:43 -07:00
// Protection from misbehaing authority
var hash = keccak256(recipient, value, transactionHash);
2017-06-13 02:18:03 -07:00
// Duplicated deposits
require(!deposits[hash].contains(msg.sender));
2017-06-13 02:18:03 -07:00
deposits[hash].push(msg.sender);
// TODO: this may cause troubles if requriedSignatures len is changed
if (deposits[hash].length == requiredSignatures) {
balances[recipient] += value;
Deposit(recipient, value);
}
}
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Used to transfer money between accounts
function transfer (address recipient, uint value, bool externalTransfer) public {
require(balances[msg.sender] >= value);
// fails if value == 0, or if there is an overflow
require(balances[recipient] + value > balances[recipient]);
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
balances[msg.sender] -= value;
if (externalTransfer) {
Withdraw(recipient, value);
} else {
balances[recipient] += value;
Transfer(msg.sender, recipient, value);
}
}
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
/// Should be used as sync tool
2017-08-25 07:27:55 -07:00
///
2017-06-13 02:18:03 -07:00
/// Message is a message that should be relayed to main chain once authorities sign it.
2017-08-25 07:27:55 -07:00
///
2017-08-23 10:09:51 -07:00
/// for withdraw message contains:
/// withdrawal recipient (bytes20)
/// withdrawal value (uint)
2017-10-10 02:02:46 -07:00
/// foreign transaction hash (bytes32) // to avoid transaction duplication
function submitSignature (bytes signature, bytes message) public onlyAuthority() {
// Validate submited signatures
2017-09-04 07:02:41 -07:00
require(Signer.signer(signature, message) == msg.sender);
2017-09-04 03:50:43 -07:00
// Valid withdraw message must have 84 bytes
require(message.length == 84);
var hash = keccak256(message);
2017-08-25 07:27:55 -07:00
2017-06-13 02:18:03 -07:00
// Duplicated signatures
require(!signatures[hash].signed.contains(msg.sender));
2017-06-13 02:18:03 -07:00
signatures[hash].message = message;
signatures[hash].signed.push(msg.sender);
2017-08-22 04:47:01 -07:00
signatures[hash].signatures.push(signature);
2017-08-25 07:27:55 -07:00
// TODO: this may cause troubles if requiredSignatures len is changed
2017-06-13 02:18:03 -07:00
if (signatures[hash].signed.length == requiredSignatures) {
2017-08-22 04:53:40 -07:00
CollectedSignatures(msg.sender, hash);
2017-06-13 02:18:03 -07:00
}
}
2017-08-25 07:27:55 -07:00
/// Get signature
function signature (bytes32 hash, uint index) public constant returns (bytes) {
2017-08-22 04:47:01 -07:00
return signatures[hash].signatures[index];
}
2017-08-25 07:27:55 -07:00
2017-08-22 04:53:40 -07:00
/// Get message
function message (bytes32 hash) public constant returns (bytes) {
2017-08-22 04:53:40 -07:00
return signatures[hash].message;
}
}