Skip to content

Commit

Permalink
Merge 2655b4d into 63c11df
Browse files Browse the repository at this point in the history
  • Loading branch information
patitonar committed Sep 5, 2019
2 parents 63c11df + 2655b4d commit 543bb69
Show file tree
Hide file tree
Showing 36 changed files with 2,654 additions and 177 deletions.
5 changes: 3 additions & 2 deletions README.md
Expand Up @@ -23,11 +23,11 @@ The POA Bridge allows users to transfer assets between two chains in the Ethereu

### Operations

Currently, the contracts support two types of relay operations:
Currently, the contracts support four types of relay operations:
* Tokenize the native coin in one blockchain network (Home) into an ERC20 token in another network (Foreign).
* Swap a token presented by an existing ERC20 contract in a Foreign network into an ERC20 token in the Home network, where one pair of bridge contracts corresponds to one pair of ERC20 tokens.
* to mint new native coins in Home blockchain network from a token presented by an existing ERC20 contract in a Foreign network.

* Transfer arbitrary data between two blockchain networks as so the data could be interpreted as an arbitrary contract method invocation.

### Components

Expand Down Expand Up @@ -60,6 +60,7 @@ Responsibilities and roles of the bridge:
- in `NATIVE-TO-ERC` mode: send native coins to the Home Bridge to receive ERC20 tokens from the Foreign Bridge, send ERC20 tokens to the Foreign Bridge to unlock native coins from the Home Bridge;
- in `ERC-TO-ERC` mode: transfer ERC20 tokens to the Foreign Bridge to mint ERC20 tokens on the Home Network, transfer ERC20 tokens to the Home Bridge to unlock ERC20 tokens on Foreign networks;
- in `ERC-TO-NATIVE` mode: send ERC20 tokens to the Foreign Bridge to receive native coins from the Home Bridge, send native coins to the Home Bridge to unlock ERC20 tokens from the Foreign Bridge.
- in `ARBITRARY-MESSAGE` mode: Invoke Home/Foreign Bridge to send a message that will be executed on the other Network as an arbitrary contract method invocation.

## Usage

Expand Down
76 changes: 76 additions & 0 deletions contracts/libraries/ArbitraryMessage.sol
@@ -0,0 +1,76 @@
pragma solidity 0.4.24;

import "../interfaces/IBridgeValidators.sol";
import "./Message.sol";

library ArbitraryMessage {
// layout of message :: bytes:
// offset 0: 32 bytes :: uint256 - message length
// offset 32: 32 bytes :: bytes32 txHash
// offset 52: 20 bytes :: address - sender address
// offset 72: 20 bytes :: address - executor contract
// offset 104: 32 bytes :: uint256 - gasLimit
// offset 136: 1 bytes :: bytes1 - dataType
// (optional) 137: 32 bytes :: uint256 - gasPrice
// (optional) 137: 1 bytes :: bytes1 - gasPriceSpeed

// 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 unpackData(bytes _data, bool applyDataOffset)
internal
pure
returns (
address sender,
address executor,
bytes32 txHash,
uint256 gasLimit,
bytes1 dataType,
uint256 gasPrice,
bytes memory data
)
{
uint256 dataOffset = 0;
uint256 datasize;
// 32 (tx hash) + 20 (sender) + 20 (executor) + 32 (gasLimit) + 1 (dataType)
uint256 srcdataptr = 32 + 20 + 20 + 32 + 1;
assembly {
txHash := mload(add(_data, 32))
sender := mload(add(_data, 52))
executor := mload(add(_data, 72))
gasLimit := mload(add(_data, 104))
dataType := and(mload(add(_data, 136)), 0xFF00000000000000000000000000000000000000000000000000000000000000)
switch dataType
case 0x0000000000000000000000000000000000000000000000000000000000000000 {
gasPrice := 0
}
case 0x0100000000000000000000000000000000000000000000000000000000000000 {
gasPrice := mload(add(_data, 137)) // 32
srcdataptr := add(srcdataptr, 0x20)
}
case 0x0200000000000000000000000000000000000000000000000000000000000000 {
gasPrice := 0
srcdataptr := add(srcdataptr, 0x01)
}
datasize := sub(mload(_data), srcdataptr)
}
data = new bytes(datasize);
assembly {
// BYTES_HEADER_SIZE
let dataptr := add(data, 32)
if eq(applyDataOffset, 1) {
dataOffset := 32
}
// 68 = 4 (selector) + 32 (bytes header) + 32 (bytes length)
calldatacopy(dataptr, add(add(68, srcdataptr), dataOffset), datasize)
}
}
}
79 changes: 70 additions & 9 deletions contracts/libraries/Message.sol
Expand Up @@ -68,40 +68,49 @@ library Message {
return 104;
}

function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) {
function recoverAddressFromSignedMessage(bytes signature, bytes message, bool isAMBMessage)
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);
return ecrecover(hashMessage(message, isAMBMessage), uint8(v), r, s);
}

function hashMessage(bytes message) internal pure returns (bytes32) {
function hashMessage(bytes message, bool isAMBMessage) internal pure returns (bytes32) {
bytes memory prefix = "\x19Ethereum Signed Message:\n";
string memory msgLength = "104";
return keccak256(abi.encodePacked(prefix, msgLength, message));
if (isAMBMessage) {
return keccak256(abi.encodePacked(prefix, uintToString(message.length), message));
} else {
string memory msgLength = "104";
return keccak256(abi.encodePacked(prefix, msgLength, message));
}
}

function hasEnoughValidSignatures(
bytes _message,
uint8[] _vs,
bytes32[] _rs,
bytes32[] _ss,
IBridgeValidators _validatorContract
IBridgeValidators _validatorContract,
bool isAMBMessage
) internal view {
require(isMessageValid(_message));
require(isAMBMessage || (!isAMBMessage && isMessageValid(_message)));
uint256 requiredSignatures = _validatorContract.requiredSignatures();
// It is not necessary to check that arrays have the same length since it will be handled
// during attempt to access to the corresponding elements in the loop and the call will be reverted.
// It will save gas for the rational validators actions and still be safe enough from security point of view
require(_vs.length >= requiredSignatures);
bytes32 hash = hashMessage(_message);
bytes32 hash = hashMessage(_message, isAMBMessage);
address[] memory encounteredAddresses = new address[](requiredSignatures);

for (uint256 i = 0; i < requiredSignatures; i++) {
Expand All @@ -111,4 +120,56 @@ library Message {
encounteredAddresses[i] = recoveredAddress;
}
}

function hasEnoughValidSignatures(
bytes _message,
bytes _signatures,
IBridgeValidators _validatorContract,
bool isAMBMessage
) internal view {
require(isAMBMessage || (!isAMBMessage && isMessageValid(_message)));
uint256 requiredSignatures = _validatorContract.requiredSignatures();
uint8 amount;
assembly {
amount := mload(add(_signatures, 1))
}
require(amount >= requiredSignatures);
bytes32 hash = hashMessage(_message, isAMBMessage);
address[] memory encounteredAddresses = new address[](requiredSignatures);

for (uint256 i = 0; i < requiredSignatures; i++) {
uint8 v;
bytes32 r;
bytes32 s;
uint256 posr = 33 + amount + 32 * i;
uint256 poss = posr + 32 * amount;
assembly {
v := mload(add(_signatures, add(2, i)))
r := mload(add(_signatures, posr))
s := mload(add(_signatures, poss))
}

address recoveredAddress = ecrecover(hash, v, r, s);
require(_validatorContract.isValidator(recoveredAddress));
require(!addressArrayContains(encounteredAddresses, recoveredAddress));
encounteredAddresses[i] = recoveredAddress;
}
}

function uintToString(uint256 i) internal pure returns (string) {
if (i == 0) return "0";
uint256 j = i;
uint256 length;
while (j != 0) {
length++;
j /= 10;
}
bytes memory bstr = new bytes(length);
uint256 k = length - 1;
while (i != 0) {
bstr[k--] = bytes1(48 + i % 10);
i /= 10;
}
return string(bstr);
}
}
44 changes: 44 additions & 0 deletions contracts/mocks/Box.sol
@@ -0,0 +1,44 @@
pragma solidity 0.4.24;

import "./IAMB.sol";

contract Box {
uint256 public value;
address public lastSender;
bytes32 public txHash;

function setValue(uint256 _value) public {
value = _value;
lastSender = IAMB(msg.sender).messageSender();
txHash = IAMB(msg.sender).transactionHash();
}

function methodWillFail() public {
revert();
}

function methodOutOfGas() public {
uint256 a = 0;
for (uint256 i = 0; i < 1000; i++) {
a = a + i;
}
}

function methodWillFailOnOtherNetwork(address _bridge, address _executor) public {
bytes4 methodSelector = this.methodWillFail.selector;
bytes memory encodedData = abi.encodeWithSelector(methodSelector);
IAMB(_bridge).requireToPassMessage(_executor, encodedData, 141647);
}

function methodOutOfGasOnOtherNetwork(address _bridge, address _executor) public {
bytes4 methodSelector = this.methodOutOfGas.selector;
bytes memory encodedData = abi.encodeWithSelector(methodSelector);
IAMB(_bridge).requireToPassMessage(_executor, encodedData, 1000);
}

function setValueOnOtherNetwork(uint256 _i, address _bridge, address _executor) public {
bytes4 methodSelector = this.setValue.selector;
bytes memory encodedData = abi.encodeWithSelector(methodSelector, _i);
IAMB(_bridge).requireToPassMessage(_executor, encodedData, 141647);
}
}
8 changes: 8 additions & 0 deletions contracts/mocks/IAMB.sol
@@ -0,0 +1,8 @@
pragma solidity 0.4.24;

interface IAMB {
function messageSender() external view returns (address);
function transactionHash() external view returns (bytes32);
function withdrawFromDeposit(address _recipient) external;
function requireToPassMessage(address _contract, bytes _data, uint256 _gas) public;
}
41 changes: 41 additions & 0 deletions contracts/mocks/MessageTest.sol
@@ -0,0 +1,41 @@
pragma solidity 0.4.24;

import "../libraries/ArbitraryMessage.sol";

contract MessageTest {
function unpackData(bytes _data)
public
pure
returns (
address sender,
address executor,
bytes32 txHash,
uint256 gasLimit,
bytes1 dataType,
uint256 gasPrice,
bytes memory data
)
{
(sender, executor, txHash, gasLimit, dataType, gasPrice, data) = ArbitraryMessage.unpackData(_data, false);
}

function unpackDataWithExtraParams(
bytes _data,
bytes /*signatures*/
)
public
pure
returns (
address sender,
address executor,
bytes32 txHash,
uint256 gasLimit,
bytes1 dataType,
uint256 gasPrice,
bytes memory data
)
{
(sender, executor, txHash, gasLimit, dataType, gasPrice, data) = ArbitraryMessage.unpackData(_data, true);
}

}

0 comments on commit 543bb69

Please sign in to comment.