parity-bridge-research/bridge.orig/contracts/bridge_foreign_optimized.sol

431 lines
15 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

pragma solidity ^0.4.17;
/// 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++) {
if (array[i] == value) {
return true;
}
}
return false;
}
// returns the digits of `inputValue` as a string.
// example: `uintToString(12345678)` returns `"12345678"`
function uintToString(uint inputValue) internal pure returns (string) {
// figure out the length of the resulting string
uint length = 0;
uint currentValue = inputValue;
do {
length++;
currentValue /= 10;
} while (currentValue != 0);
// allocate enough memory
bytes memory result = new bytes(length);
// construct the string backwards
uint i = length - 1;
currentValue = inputValue;
do {
result[i--] = byte(48 + currentValue % 10);
currentValue /= 10;
} while (currentValue != 0);
return string(result);
}
}
/// Library used only to test Helpers library via rpc calls
library HelpersTest {
function addressArrayContains(address[] array, address value) public pure returns (bool) {
return Helpers.addressArrayContains(array, value);
}
function uintToString(uint256 inputValue) public pure returns (string str) {
return Helpers.uintToString(inputValue);
}
}
// helpers for message signing.
// `internal` so they get compiled into contracts using them.
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);
}
}
/// Library used only to test MessageSigning library via rpc calls
library MessageSigningTest {
function recoverAddressFromSignedMessage(bytes signature, bytes message) public pure returns (address) {
return MessageSigning.recoverAddressFromSignedMessage(signature, message);
}
}
library Message {
// 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 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 (uint) {
uint 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;
}
}
/// Library used only to test Message library via rpc calls
library MessageTest {
function getRecipient(bytes message) public pure returns (address) {
return Message.getRecipient(message);
}
function getValue(bytes message) public pure returns (uint) {
return Message.getValue(message);
}
function getTransactionHash(bytes message) public pure returns (bytes32) {
return Message.getTransactionHash(message);
}
}
contract HomeBridge {
/// Number of authorities signatures required to withdraw the money.
///
/// Must be lesser than number of authorities.
uint 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.
uint public estimatedGasCostOfWithdraw;
/// Contract authorities.
address[] public authorities;
/// Used foreign transaction hashes.
mapping (bytes32 => bool) withdraws;
/// Event created on money deposit.
event Deposit (address recipient, uint value);
/// Event created on money withdraw.
event Withdraw (address recipient, uint 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 (uint 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(
uint requiredSignaturesParam,
address[] authoritiesParam,
uint estimatedGasCostOfWithdrawParam
) public
{
require(requiredSignaturesParam != 0);
require(requiredSignaturesParam <= authoritiesParam.length);
requiredSignatures = requiredSignaturesParam;
authorities = authoritiesParam;
estimatedGasCostOfWithdraw = estimatedGasCostOfWithdrawParam;
}
/// Should be used to deposit money.
function () public payable {
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();
}
/// 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.
///
/// 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);
bytes32 hash = Message.getTransactionHash(message);
// The following two statements guard against reentry into this function.
// Duplicated withdraw or reentry.
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` cant 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();
// 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);
}
}
contract ForeignBridge {
/// Number of authorities signatures required to withdraw the money.
///
/// Must be lesser than number of authorities.
uint public requiredSignatures;
/// Contract authorities.
mapping (address => bool) authorities;
/// Pending mesages
mapping (bytes32 => bytes) messages;
/// ???
mapping (bytes32 => bytes) signatures;
/// Pending deposits and authorities who confirmed them
mapping (bytes32 => bool) messages_signed;
mapping (bytes32 => uint) num_messages_signed;
/// Ether balances
mapping (address => uint) public balances;
/// Pending deposits and authorities who confirmed them
mapping (bytes32 => bool) deposits_signed;
mapping (bytes32 => uint) num_deposits_signed;
/// Event created on money deposit.
event Deposit(address recipient, uint value);
/// Event created on money withdraw.
event Withdraw(address recipient, uint value);
/// Event created on money transfer
event Transfer(address from, address to, uint value);
/// Collected signatures which should be relayed to home chain.
event CollectedSignatures(address authority, bytes32 messageHash);
/// Constructor.
function ForeignBridge(
uint requiredSignaturesParam,
address[] authoritiesParam
) public
{
require(requiredSignaturesParam != 0);
require(requiredSignaturesParam <= authoritiesParam.length);
requiredSignatures = requiredSignaturesParam;
for (uint i = 0; i < authoritiesParam.length; i++) {
authorities[authoritiesParam[i]] = true;
}
}
/// Multisig authority validation
modifier onlyAuthority() {
require(authorities[msg.sender]);
_;
}
/// Used to deposit money to the contract.
///
/// Usage maps instead of arrey allows to reduce gas consumption
/// from 91169 to 89348 (solc 0.4.19).
///
/// deposit recipient (bytes20)
/// deposit value (uint)
/// mainnet transaction hash (bytes32) // to avoid transaction duplication
function deposit(address recipient, uint value, bytes32 transactionHash) public onlyAuthority() {
// 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;
// TODO: this may cause troubles if requriedSignatures len is changed
if (signed == requiredSignatures) {
balances[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, uint value) public {
require(balances[msg.sender] >= value);
balances[msg.sender] -= value;
Withdraw(recipient, value);
}
/// Transfer `value` to `recipient` on this `foreign` chain.
///
/// does not affect `home` chain. does not do a relay.
function transferLocal(address recipient, uint value) public {
require(balances[msg.sender] >= value);
// fails if value == 0, or if there is an overflow
require(balances[recipient] + value > balances[recipient]);
balances[msg.sender] -= value;
balances[recipient] += value;
Transfer(msg.sender, recipient, value);
}
/// Should be used as sync tool
///
/// Message is a message that should be relayed to main chain once authorities sign it.
///
/// Usage several maps instead of structure allows to reduce gas consumption
/// from 265102 to 242334 (solc 0.4.19).
///
/// 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 onlyAuthority() {
// Validate submited signatures
require(MessageSigning.recoverAddressFromSignedMessage(signature, message) == msg.sender);
// Valid withdraw message must have 84 bytes
require(message.length == 84);
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 == requiredSignatures) {
CollectedSignatures(msg.sender, hash);
}
}
/// Get signature
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];
}
}