Optimized deposit and withdraw_confirm operations of ForeignBridge contract
This commit is contained in:
parent
20acfe0c04
commit
ee3b9fe746
|
@ -0,0 +1,430 @@
|
|||
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` 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();
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue