diff --git a/bridge.orig/contracts/bridge_foreign_optimized.sol b/bridge.orig/contracts/bridge_foreign_optimized.sol new file mode 100644 index 0000000..8d4390a --- /dev/null +++ b/bridge.orig/contracts/bridge_foreign_optimized.sol @@ -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]; + } +}