merge from master branch

This commit is contained in:
Alexander Kolotov 2018-02-16 02:13:46 +03:00
commit ca64cf3b6a
39 changed files with 3157 additions and 1563 deletions

2
.gitignore vendored
View File

@ -54,3 +54,5 @@ examples/db.toml
jsTests/package-lock.json
node_modules
compiled_contracts
integration-tests/tmp

View File

@ -13,8 +13,13 @@ matrix:
- sudo add-apt-repository ppa:ethereum/ethereum -y
- sudo apt-get update -y
- sudo apt-get install solc -y
- wget https://parity-downloads-mirror.parity.io/v1.8.6/x86_64-unknown-linux-gnu/parity
- chmod +x parity
- cp parity ${HOME}/bin
- export PATH=${HOME}/bin:${PATH}
- cd integration-tests
script:
- cargo test --all
- env BACKTRACE=1 cargo test --all -- --nocapture
- language: rust
rust: beta
cache: cargo
@ -23,8 +28,13 @@ matrix:
- sudo add-apt-repository ppa:ethereum/ethereum -y
- sudo apt-get update -y
- sudo apt-get install solc -y
- wget https://parity-downloads-mirror.parity.io/v1.8.6/x86_64-unknown-linux-gnu/parity
- chmod +x parity
- cp parity ${HOME}/bin
- export PATH=${HOME}/bin:${PATH}
- cd integration-tests
script:
- cargo test --all
- env BACKTRACE=1 cargo test --all -- --nocapture
- language: rust
rust: nightly
cache: cargo
@ -33,8 +43,13 @@ matrix:
- sudo add-apt-repository ppa:ethereum/ethereum -y
- sudo apt-get update -y
- sudo apt-get install solc -y
- wget https://parity-downloads-mirror.parity.io/v1.8.6/x86_64-unknown-linux-gnu/parity
- chmod +x parity
- cp parity ${HOME}/bin
- export PATH=${HOME}/bin:${PATH}
- cd integration-tests
script:
- cargo test --all
- env BACKTRACE=1 cargo test --all -- --nocapture
- language: node_js
node_js: node
cache: yarn

847
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,2 @@
[workspace]
members = ["tests", "cli", "bridge"]
members = ["tests", "cli", "bridge", "integration-tests"]

153
README.md
View File

@ -1,5 +1,7 @@
# bridge
[![Join the chat at https://gitter.im/paritytech/parity-bridge](https://badges.gitter.im/paritytech/parity-bridge.svg)](https://gitter.im/paritytech/parity-bridge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status][travis-image]][travis-url]
[![Solidity Coverage Status][coveralls-image]][coveralls-url] (contracts only)
@ -8,82 +10,118 @@
[coveralls-image]: https://coveralls.io/repos/github/paritytech/parity-bridge/badge.svg?branch=master
[coveralls-url]: https://coveralls.io/github/paritytech/parity-bridge?branch=master
bridge between two ethereum blockchains, `home` and `foreign`.
the bridge is an
[ERC20 token](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)
contract on one ethereum-based blockchain that is backed by ether on **another** ethereum-based blockchain.
users can convert ether
on one chain into the same amount of ERC20 tokens on the other and back.
the bridge securely relays these conversions.
**the bridge can mitigate scaling issues:**
by deploying a [proof-of-authority](https://paritytech.github.io/wiki/Proof-of-Authority-Chains.html)
network and bridging it to the Ethereum Foundation network ('mainnet') users can convert their mainnet ether
into ERC20 tokens on the PoA chain
and there transfer them with much lower transaction fees,
faster block times and unaffected by mainnet congestion.
the users can withdraw their tokens worth of ether on the mainnet at any point.
parity is using the bridge project to prototype
the system that will eventually connect ethereum and other non-parachains to
[polkadot](https://polkadot.io/).
### next steps
1. deploy to bridge **ethereum** and **kovan** (bridge authorities TBD)
2. make the bridge work with contract-based dynamic validator sets
3. after kovan hardfork 2: deploy to kovan again with dynamic validator set
### current functionality
the bridge allows users to deposit ether into a smart contract on `home` and get it on `foreign` in form of a token balance.
it also allows users to withdraw their tokens on `foreign` and get the equivalent ether on `home`.
on `foreign` users can freely transfer tokens between each other.
the bridge connects two chains `home` and `foreign`.
when users deposit ether into the `HomeBridge` contract on `home`
they get the same amount of ERC20 tokens on `foreign`.
[they can use `ForeignBridge` as they would use any ERC20 token.](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)
to convert their `foreign` ERC20 into ether on `home`
users can always call `ForeignBridge.transferHomeViaRelay(homeRecipientAddress, value, homeGasPrice)`.
`foreign` is assumed to use PoA (proof of authority) consensus.
relays between the chains happen in a byzantine fault tolerant way using the authorities of `foreign`.
### next steps
1. deploy to bridge **ethereum** and **kovan** with the kovan authorities being the immutable set of bridge authorities
2. make bridge work with contract-based dynamic validator set
3. after kovan hardfork 2: deploy to kovan again with dynamic validator set
### eventual goals
connect ethereum to polkadot
### deposit ether into `HomeBridge` and get it in form of a token balance on `ForeignBridge`
### high level explanation of home ether -> foreign ERC20 relay
`sender` deposits `value` into `HomeBridge`.
the `HomeBridge` fallback function emits `Deposit(sender, value)`.
for each `Deposit` event on `HomeBridge` every authority makes a transaction
for each `Deposit` event on `HomeBridge` every authority executes
`ForeignBridge.deposit(sender, value, transactionHash)`.
once there are `ForeignBridge.requiredSignatures` such transactions
with identical arguments and from distinct authorities then
`ForeignBridge.balances(sender)` is increased by `value` and
`ForeignBridge.Deposit(sender, value)` is emitted.
`ForeignBridge.balanceOf(sender)` is increased by `value`.
### withdraw balance on `ForeignBridge` and get it as ether on `home` chain
### high level explanation of foreign ERC20 -> home ether relay
`sender` executes `ForeignBridge.transferHomeViaRelay(recipient, value, homeGasPrice)`
which checks and reduces `ForeignBridge.balances(sender)` by `value` and emits `ForeignBridge.Withdraw(recipient, value, homeGasPrice)`.
for every `ForeignBridge.Withdraw`, every bridge authority creates a message containing
`value`, `recipient` and the `transactionHash` of the transaction referenced by the `ForeignBridge.Withdraw` event;
signs that message and executes `ForeignBridge.submitSignature(signature, message)`.
this collection of signatures is on `foreign` because transactions are free for the authorities on `foreign`,
but not free on `home`.
`sender` executes `ForeignBridge.transferHomeViaRelay(recipient, value)`
which checks and reduces `ForeignBridge.balances(sender)` by `value` and emits `ForeignBridge.Withdraw(recipient, value)`.
for each `ForeignBridge.Withdraw` every bridge authority creates a message containg
`value`, `recipient` and the `transactionHash` of the transaction containing the `ForeignBridge.Withdraw` event,
signs the message and makes a transaction `ForeignBridge.submitSignature(signature, message)`.
this collection of signatures on `foreign` is necessary because transactions are free
for authorities on `foreign`, since they are the authorities of `foreign`, but not free on `home`.
once `ForeignBridge.requiredSignatures` signatures by distinct authorities are collected
a `ForeignBridge.CollectedSignatures(authorityThatSubmittedLastSignature, messageHash)` event is emitted.
everyone (usually `authorityThatSubmittedLastSignature`) can then call `ForeignBridge.message(messageHash)` and
`ForeignBridge.signature(messageHash, 0..requiredSignatures)`
to look up the message and signatures and execute `HomeBridge.withdraw(vs, rs, ss, message)`
and complete the withdraw.
### transfer on `foreign`
`HomeBridge.withdraw(vs, rs, ss, message)` recovers the addresses from the signatures,
checks that enough authorities in its authority list have signed and
finally transfers `value` ether ([minus the relay gas costs](#recipient-pays-relay-cost-to-relaying-authority))
to `recipient`.
`sender` executes `ForeignBridge.transferLocal(recipient, value)`
which checks and reduces `ForeignBridge.balances(sender)` and increases `ForeignBridge.balances(recipient)`
by `value`.
### run truffle smart contract tests
requires `yarn` to be `$PATH`. [installation instructions](https://yarnpkg.com/lang/en/docs/install/)
```
cd truffle
yarn test
```
### build
requires `rust` and `cargo`: [installation instructions.](https://www.rust-lang.org/en-US/install.html)
requires `solc` to be in `$PATH`: [installation instructions.](https://solidity.readthedocs.io/en/develop/installing-solidity.html)
assuming you've cloned the bridge (`git clone git@github.com:paritytech/parity-bridge.git`)
and are in the project directory (`cd parity-bridge`) run:
```
cargo build -p bridge-cli --release
```
### cli options
to install copy `../target/release/bridge` into a folder that's in your `$PATH`.
### run
```
Ethereum-Kovan bridge.
Copyright 2017 Parity Technologies (UK) Limited
Usage:
bridge --config <config> --database <database>
bridge -h | --help
Options:
-h, --help Display help message and exit.
bridge --config config.toml --database db.toml
```
- `--config` - location of the configuration file. configuration file must exist
- `--database` - location of the database file. if there is no file at specified location, new bridge contracts will be deployed and new database will be created
- `--database` - location of the database file.
if there is no file at specified location, new bridge contracts will be deployed
and new database will be created
### configuration [file example](./examples/config.toml)
@ -108,9 +146,9 @@ bin = "contracts/KovanBridge.bin"
[authorities]
accounts = [
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
"0x006e27b6a72e1f34c626762f3c4761547aff1421"
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
"0x006e27b6a72e1f34c626762f3c4761547aff1421",
"0x006e27b6a72e1f34c626762f3c4761547aff1421"
]
required_signatures = 2
@ -183,7 +221,7 @@ checked_withdraw_confirm = 121
- `foreign_deploy` - block number at which foreign contract has been deployed
- `checked_deposit_relay` - number of the last block for which an authority has relayed deposits to the foreign
- `checked_withdraw_relay` - number of the last block for which an authority has relayed withdraws to the home
- `checked_withdraw_confirm` - number of the last block for which an authirty has confirmed withdraw
- `checked_withdraw_confirm` - number of the last block for which an authority has confirmed withdraw
### example run
@ -205,15 +243,6 @@ checked_withdraw_confirm = 121
![withdraw](./res/withdraw.png)
### truffle tests
[requires yarn to be installed](https://yarnpkg.com/lang/en/docs/install/)
```
cd truffle
yarn test
```
### recipient pays relay cost to relaying authority
a bridge `authority` has to pay for gas (`cost`) to execute `HomeBridge.withdraw` when
@ -226,8 +255,8 @@ that shuts down an attack that enabled exhaustion of authorities funds on `home`
read on for a more thorough explanation.
parity-bridge connects a value-bearing ethereum blockchain `home`
(initally the ethereum foundation chain)
to a non-value-bearing PoA ethereum blockchain `foreign` (initally the kovan testnet).
(initially the ethereum foundation chain)
to a non-value-bearing PoA ethereum blockchain `foreign` (initially the kovan testnet).
value-bearing means that the ether on that chain has usable value in the sense that
in order to obtain it one has to either mine it (trade in electricity)
@ -244,7 +273,7 @@ pay for the gas.
this opened up an attack where a malicious user could
deposit a very small amount of wei on `HomeBridge`, get it relayed to `ForeignBridge`,
then spam `ForeignBridge.transfer` with `1` wei withdraws.
then spam `ForeignBridge.transferHomeViaRelay` with `1` wei withdraws.
it would cost the attacker very little `home` chain wei and essentially
free `foreign` testnet wei to cause the authorities to spend orders of magnitude more wei
to relay the withdraw to `home` by executing `HomeBridge.withdraw`.
@ -255,9 +284,13 @@ to shut down this attack `HomeBridge.withdraw` was modified so
doing the relay.
this way the `recipient` pays the relaying `authority` for the execution of the `withdraw` transaction.
if the value withdrawn is too low to pay for the relay at current gas prices then
bridge authorities will ignore it. one can think of it as value getting
spent entirely on paying the relay with no value left to pay out the recipient.
relayers can set the gas price for `HomeBridge.withdraw`.
they could set a very high gas price resulting in a very high `cost` through which they could burn large portions of `value`.
to shut down this attack the `homeGasPrice` param was added to `ForeignBridge.transferHomeViaRelay`.
end users have control over the cost/latency tradeoff of their relay transaction through the `homeGasPrice`.
relayers have to set gas price to `homeGasPrice` when calling `HomeBridge.withdraw`.
the `recipient` for `value` is the exception and can freely choose any gas price.
see https://github.com/paritytech/parity-bridge/issues/112 for more details.
`HomeBridge.withdraw` is currently the only transaction bridge authorities execute on `home`.
care must be taken to secure future functions that bridge authorities will execute

View File

@ -13,11 +13,14 @@ tokio-timer = "0.1.2"
toml = "0.4.2"
web3 = { git = "https://github.com/tomusdrw/rust-web3", branch = "bridge" }
error-chain = "0.11.0-rc.2"
ethabi = "4.0"
ethabi-derive = "4.0"
ethabi-contract = "4.0"
ethabi = "5.1"
ethabi-derive = "5.0"
ethabi-contract = "5.0"
rustc-hex = "1.0"
log = "0.3"
ethereum-types = "0.2"
pretty_assertions = "0.2.1"
[dev-dependencies]
tempdir = "0.3"
quickcheck = "0.6.1"

View File

@ -1,18 +1,35 @@
use std::process::Command;
fn main() {
// rerun build script if bridge contract has changed.
// without this cargo doesn't since the bridge contract
// is outside the crate directories
println!("cargo:rerun-if-changed=../contracts/bridge.sol");
let exit_status = Command::new("solc")
.arg("--abi")
.arg("--bin")
.arg("--optimize")
.arg("--output-dir").arg("../compiled_contracts")
.arg("--overwrite")
.arg("../contracts/bridge.sol")
.status()
.unwrap_or_else(|e| panic!("Error compiling solidity contracts: {}", e));
assert!(exit_status.success(), "There was an error while compiling contracts code.");
// rerun build script if bridge contract has changed.
// without this cargo doesn't since the bridge contract
// is outside the crate directories
println!("cargo:rerun-if-changed=../contracts/bridge.sol");
match Command::new("solc")
.arg("--abi")
.arg("--bin")
.arg("--optimize")
.arg("--output-dir").arg("../compiled_contracts")
.arg("--overwrite")
.arg("../contracts/bridge.sol")
.status()
{
Ok(exit_status) => {
if !exit_status.success() {
if let Some(code) = exit_status.code() {
panic!("`solc` exited with error exit status code `{}`", code);
} else {
panic!("`solc` exited because it was terminated by a signal");
}
}
},
Err(err) => {
if let std::io::ErrorKind::NotFound = err.kind() {
panic!("`solc` executable not found in `$PATH`. `solc` is required to compile the bridge contracts. please install it: https://solidity.readthedocs.io/en/develop/installing-solidity.html");
} else {
panic!("an error occurred when trying to spawn `solc`: {}", err);
}
}
}
}

View File

@ -88,7 +88,7 @@ pub struct LogStreamInit {
pub filter: FilterBuilder,
pub request_timeout: Duration,
pub poll_interval: Duration,
pub confirmations: u64,
pub confirmations: usize,
}
/// Contains all logs matching `LogStream` filter in inclusive range `[from, to]`.
@ -137,7 +137,7 @@ pub struct LogStream<T: Transport> {
state: LogStreamState<T>,
after: u64,
filter: FilterBuilder,
confirmations: u64,
confirmations: usize,
request_timeout: Duration,
}
@ -154,7 +154,7 @@ impl<T: Transport> Stream for LogStream<T> {
},
LogStreamState::FetchBlockNumber(ref mut future) => {
let last_block = try_ready!(future.poll()).low_u64();
let last_confirmed_block = last_block.saturating_sub(self.confirmations);
let last_confirmed_block = last_block.saturating_sub(self.confirmations as u64);
if last_confirmed_block > self.after {
let from = self.after + 1;
let filter = self.filter.clone()

View File

@ -6,7 +6,7 @@ use web3::types::{TransactionRequest};
use app::App;
use database::Database;
use error::{Error, ErrorKind};
use {api, ethabi};
use api;
pub enum Deployed {
/// No existing database found. Deployed new contracts.
@ -44,14 +44,15 @@ impl<T: Transport + Clone> Future for Deploy<T> {
Err(ErrorKind::MissingFile(_)) => {
let main_data = self.app.home_bridge.constructor(
self.app.config.home.contract.bin.clone().0,
ethabi::util::pad_u32(self.app.config.authorities.required_signatures),
self.app.config.authorities.accounts.iter().map(|a| a.0.clone()).collect::<Vec<_>>(),
ethabi::util::pad_u32(self.app.config.estimated_gas_cost_of_withdraw)
self.app.config.authorities.required_signatures,
self.app.config.authorities.accounts.clone(),
self.app.config.estimated_gas_cost_of_withdraw
);
let test_data = self.app.foreign_bridge.constructor(
self.app.config.foreign.contract.bin.clone().0,
ethabi::util::pad_u32(self.app.config.authorities.required_signatures),
self.app.config.authorities.accounts.iter().map(|a| a.0.clone()).collect::<Vec<_>>()
self.app.config.authorities.required_signatures,
self.app.config.authorities.accounts.clone(),
self.app.config.estimated_gas_cost_of_withdraw
);
let main_tx_request = TransactionRequest {

View File

@ -19,7 +19,7 @@ fn deposits_filter(home: &home::HomeBridge, address: Address) -> FilterBuilder {
fn deposit_relay_payload(home: &home::HomeBridge, foreign: &foreign::ForeignBridge, log: Log) -> Result<Bytes> {
let raw_log = RawLog {
topics: log.topics.into_iter().map(|t| t.0).collect(),
topics: log.topics,
data: log.data.0,
};
let deposit_log = home.events().deposit().parse_log(raw_log)?;
@ -47,11 +47,11 @@ pub fn create_deposit_relay<T: Transport + Clone>(app: Arc<App<T>>, init: &Datab
request_timeout: app.config.home.request_timeout,
poll_interval: app.config.home.poll_interval,
confirmations: app.config.home.required_confirmations,
filter: deposits_filter(&app.home_bridge, init.home_contract_address.clone()),
filter: deposits_filter(&app.home_bridge, init.home_contract_address),
};
DepositRelay {
logs: api::log_stream(app.connections.home.clone(), app.timer.clone(), logs_init),
foreign_contract: init.foreign_contract_address.clone(),
foreign_contract: init.foreign_contract_address,
state: DepositRelayState::Wait,
app,
}
@ -73,13 +73,14 @@ impl<T: Transport> Stream for DepositRelay<T> {
let next_state = match self.state {
DepositRelayState::Wait => {
let item = try_stream!(self.logs.poll());
info!("got {} new deposits to relay", item.logs.len());
let deposits = item.logs
.into_iter()
.map(|log| deposit_relay_payload(&self.app.home_bridge, &self.app.foreign_bridge, log))
.collect::<Result<Vec<_>>>()?
.into_iter()
.map(|payload| TransactionRequest {
from: self.app.config.foreign.account.clone(),
from: self.app.config.foreign.account,
to: Some(self.foreign_contract.clone()),
gas: Some(self.app.config.txs.deposit_relay.gas.into()),
gas_price: Some(self.app.config.txs.deposit_relay.gas_price.into()),
@ -95,6 +96,7 @@ impl<T: Transport> Stream for DepositRelay<T> {
})
.collect::<Vec<_>>();
info!("relaying {} deposits", deposits.len());
DepositRelayState::RelayDeposits {
future: join_all(deposits),
block: item.to,
@ -102,6 +104,7 @@ impl<T: Transport> Stream for DepositRelay<T> {
},
DepositRelayState::RelayDeposits { ref mut future, block } => {
let _ = try_ready!(future.poll());
info!("deposit relay completed");
DepositRelayState::Yield(Some(block))
},
DepositRelayState::Yield(ref mut block) => match block.take() {
@ -129,8 +132,8 @@ mod tests {
let data = "000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0".from_hex().unwrap();
let log = Log {
data: data.into(),
topics: vec!["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c".parse().unwrap()],
transaction_hash: Some("0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".parse().unwrap()),
topics: vec!["e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c".into()],
transaction_hash: Some("884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into()),
..Default::default()
};

View File

@ -3,37 +3,24 @@ use std::ops;
use futures::{Future, Stream, Poll};
use futures::future::{JoinAll, join_all};
use tokio_timer::Timeout;
use ethabi::RawLog;
use web3::Transport;
use web3::types::{H256, H520, Address, TransactionRequest, Log, Bytes, FilterBuilder};
use web3::types::{H256, H520, Address, TransactionRequest, Bytes, FilterBuilder};
use api::{self, LogStream, ApiCall};
use app::App;
use contracts::foreign;
use util::web3_filter;
use database::Database;
use error::Error;
use message_to_mainnet::{MessageToMainnet, MESSAGE_LENGTH};
fn withdraws_filter(foreign: &foreign::ForeignBridge, address: Address) -> FilterBuilder {
let filter = foreign.events().withdraw().create_filter();
web3_filter(filter, address)
}
fn withdraw_confirm_sign_payload(foreign: &foreign::ForeignBridge, log: Log) -> Result<Bytes, Error> {
let raw_log = RawLog {
topics: log.topics.into_iter().map(|t| t.0).collect(),
data: log.data.0,
};
let withdraw_log = foreign.events().withdraw().parse_log(raw_log)?;
let hash = log.transaction_hash.expect("log to be mined and contain `transaction_hash`");
let mut result = vec![0u8; 84];
result[0..20].copy_from_slice(&withdraw_log.recipient);
result[20..52].copy_from_slice(&withdraw_log.value);
result[52..84].copy_from_slice(&hash);
Ok(result.into())
}
fn withdraw_submit_signature_payload(foreign: &foreign::ForeignBridge, withdraw_payload: Bytes, signature: H520) -> Bytes {
foreign.functions().submit_signature().input(signature.to_vec(), withdraw_payload.0).into()
fn withdraw_submit_signature_payload(foreign: &foreign::ForeignBridge, withdraw_message: Vec<u8>, signature: H520) -> Bytes {
assert_eq!(withdraw_message.len(), MESSAGE_LENGTH, "ForeignBridge never accepts messages with len != {} bytes; qed", MESSAGE_LENGTH);
foreign.functions().submit_signature().input(signature.0.to_vec(), withdraw_message).into()
}
/// State of withdraw confirmation.
@ -42,7 +29,7 @@ enum WithdrawConfirmState<T: Transport> {
Wait,
/// Signing withdraws.
SignWithdraws {
withdraws: Vec<Bytes>,
messages: Vec<Vec<u8>>,
future: JoinAll<Vec<Timeout<ApiCall<H520, T::Out>>>>,
block: u64,
},
@ -66,7 +53,7 @@ pub fn create_withdraw_confirm<T: Transport + Clone>(app: Arc<App<T>>, init: &Da
WithdrawConfirm {
logs: api::log_stream(app.connections.foreign.clone(), app.timer.clone(), logs_init),
foreign_contract: init.foreign_contract_address.clone(),
foreign_contract: init.foreign_contract_address,
state: WithdrawConfirmState::Wait,
app,
}
@ -88,37 +75,45 @@ impl<T: Transport> Stream for WithdrawConfirm<T> {
let next_state = match self.state {
WithdrawConfirmState::Wait => {
let item = try_stream!(self.logs.poll());
let withdraws = item.logs
info!("got {} new withdraws to sign", item.logs.len());
let withdraw_messages = item.logs
.into_iter()
.map(|log| withdraw_confirm_sign_payload(&self.app.foreign_bridge, log))
.collect::<Result<Vec<_>, _>>()?;
.map(|log| {
info!("withdraw is ready for signature submission. tx hash {}", log.transaction_hash.unwrap());
Ok(MessageToMainnet::from_log(log)?.to_bytes())
})
.collect::<Result<Vec<_>, Error>>()?;
let requests = withdraws.clone()
let requests = withdraw_messages.clone()
.into_iter()
.map(|bytes| {
.map(|message| {
self.app.timer.timeout(
api::sign(&self.app.connections.foreign, self.app.config.foreign.account.clone(), bytes),
api::sign(&self.app.connections.foreign, self.app.config.foreign.account, Bytes(message)),
self.app.config.foreign.request_timeout)
})
.collect::<Vec<_>>();
info!("signing");
WithdrawConfirmState::SignWithdraws {
future: join_all(requests),
withdraws: withdraws,
messages: withdraw_messages,
block: item.to,
}
},
WithdrawConfirmState::SignWithdraws { ref mut future, ref mut withdraws, block } => {
WithdrawConfirmState::SignWithdraws { ref mut future, ref mut messages, block } => {
let signatures = try_ready!(future.poll());
info!("signing complete");
// borrow checker...
let app = &self.app;
let foreign_contract = &self.foreign_contract;
let confirmations = withdraws
let confirmations = messages
.drain(ops::RangeFull)
.zip(signatures.into_iter())
.map(|(withdraw, signature)| withdraw_submit_signature_payload(&app.foreign_bridge, withdraw, signature))
.map(|(withdraw_message, signature)| {
withdraw_submit_signature_payload(&app.foreign_bridge, withdraw_message, signature)
})
.map(|payload| TransactionRequest {
from: app.config.foreign.account.clone(),
from: app.config.foreign.account,
to: Some(foreign_contract.clone()),
gas: Some(app.config.txs.withdraw_confirm.gas.into()),
gas_price: Some(app.config.txs.withdraw_confirm.gas_price.into()),
@ -128,12 +123,14 @@ impl<T: Transport> Stream for WithdrawConfirm<T> {
condition: None,
})
.map(|request| {
info!("submitting signature");
app.timer.timeout(
api::send_transaction(&app.connections.foreign, request),
app.config.foreign.request_timeout)
})
.collect::<Vec<_>>();
info!("submitting {} signatures", confirmations.len());
WithdrawConfirmState::ConfirmWithdraws {
future: join_all(confirmations),
block,
@ -141,10 +138,14 @@ impl<T: Transport> Stream for WithdrawConfirm<T> {
},
WithdrawConfirmState::ConfirmWithdraws { ref mut future, block } => {
let _ = try_ready!(future.poll());
info!("submitting signatures complete");
WithdrawConfirmState::Yield(Some(block))
},
WithdrawConfirmState::Yield(ref mut block) => match block.take() {
None => WithdrawConfirmState::Wait,
None => {
info!("waiting for new withdraws that should get signed");
WithdrawConfirmState::Wait
},
some => return Ok(some.into()),
}
};
@ -152,40 +153,3 @@ impl<T: Transport> Stream for WithdrawConfirm<T> {
}
}
}
#[cfg(test)]
mod tests {
use rustc_hex::FromHex;
use web3::types::{Log, Bytes};
use contracts::foreign;
use super::{withdraw_confirm_sign_payload, withdraw_submit_signature_payload};
#[test]
fn test_withdraw_confirm_sign_payload() {
let foreign = foreign::ForeignBridge::default();
let data = "000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0".from_hex().unwrap();
let log = Log {
data: data.into(),
topics: vec!["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".parse().unwrap()],
transaction_hash: Some("0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".parse().unwrap()),
..Default::default()
};
let payload = withdraw_confirm_sign_payload(&foreign, log).unwrap();
let expected: Bytes = "aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".from_hex().unwrap().into();
assert_eq!(expected, payload);
}
#[test]
fn test_withdraw_submit_signature_payload() {
let foreign = foreign::ForeignBridge::default();
let message: Bytes = "aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".from_hex().unwrap().into();
let signature = "0x8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc".parse().unwrap();
let payload = withdraw_submit_signature_payload(&foreign, message, signature);
let expected: Bytes = "630cea8e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000418697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364000000000000000000000000".from_hex().unwrap().into();
assert_eq!(expected, payload);
}
}

View File

@ -3,14 +3,16 @@ use futures::{Future, Stream, Poll};
use futures::future::{JoinAll, join_all, Join};
use tokio_timer::Timeout;
use web3::Transport;
use web3::types::{U256, H256, Address, FilterBuilder, Log, Bytes, TransactionRequest};
use web3::types::{H256, Address, FilterBuilder, Log, Bytes, TransactionRequest};
use ethabi::{RawLog, self};
use app::App;
use api::{self, LogStream, ApiCall};
use contracts::{home, foreign};
use contracts::foreign;
use util::web3_filter;
use database::Database;
use error::{self, Error};
use message_to_mainnet::MessageToMainnet;
use signature::Signature;
/// returns a filter for `ForeignBridge.CollectedSignatures` events
fn collected_signatures_filter(foreign: &foreign::ForeignBridge, address: Address) -> FilterBuilder {
@ -32,17 +34,17 @@ fn signatures_payload(foreign: &foreign::ForeignBridge, required_signatures: u32
// convert web3::Log to ethabi::RawLog since ethabi events can
// only be parsed from the latter
let raw_log = RawLog {
topics: log.topics.into_iter().map(|t| t.0).collect(),
topics: log.topics.into_iter().map(|t| t.0.into()).collect(),
data: log.data.0,
};
let collected_signatures = foreign.events().collected_signatures().parse_log(raw_log)?;
if collected_signatures.authority != my_address.0 {
if collected_signatures.authority_responsible_for_relay != my_address.0.into() {
info!("bridge not responsible for relaying transaction to home. tx hash: {}", log.transaction_hash.unwrap());
// this authority is not responsible for relaying this transaction.
// someone else will relay this transaction to home.
return Ok(None);
}
let signature_payloads = (0..required_signatures).into_iter()
.map(|index| ethabi::util::pad_u32(index))
.map(|index| foreign.functions().signature().input(collected_signatures.message_hash, index))
.map(Into::into)
.collect();
@ -54,49 +56,14 @@ fn signatures_payload(foreign: &foreign::ForeignBridge, required_signatures: u32
}))
}
/// returns the payload for a call to `HomeBridge.isMessageValueSufficientToCoverRelay(message)`
/// for the given `message`
fn message_value_sufficient_payload(home: &home::HomeBridge, message: &Bytes) -> Bytes {
assert_eq!(message.0.len(), 84, "ForeignBridge never accepts messages with len != 84 bytes; qed");
home
.functions()
.is_message_value_sufficient_to_cover_relay()
.input(message.0.clone()).into()
}
/// returns the payload for a transaction to `HomeBridge.withdraw(r, s, v, message)`
/// for the given `signatures` (r, s, v) and `message`
fn withdraw_relay_payload(home: &home::HomeBridge, signatures: &[Bytes], message: &Bytes) -> Bytes {
assert_eq!(message.0.len(), 84, "ForeignBridge never accepts messages with len != 84 bytes; qed");
let mut v_vec = Vec::new();
let mut r_vec = Vec::new();
let mut s_vec = Vec::new();
for signature in signatures {
assert_eq!(signature.0.len(), 65, "ForeignBridge never accepts signatures with len != 65 bytes; qed");
let mut r = [0u8; 32];
let mut s= [0u8; 32];
let mut v = [0u8; 32];
r.copy_from_slice(&signature.0[0..32]);
s.copy_from_slice(&signature.0[32..64]);
v[31] = signature.0[64];
v_vec.push(v);
s_vec.push(s);
r_vec.push(r);
}
home.functions().withdraw().input(v_vec, r_vec, s_vec, message.0.clone()).into()
}
/// state of the withdraw relay state machine
pub enum WithdrawRelayState<T: Transport> {
Wait,
FetchMessagesSignatures {
future: Join<JoinAll<Vec<Timeout<ApiCall<Bytes, T::Out>>>>, JoinAll<Vec<JoinAll<Vec<Timeout<ApiCall<Bytes, T::Out>>>>>>>,
block: u64,
},
FetchMessageValueSufficient {
future: JoinAll<Vec<Timeout<ApiCall<Bytes, T::Out>>>>,
messages: Vec<Bytes>,
signatures: Vec<Vec<Bytes>>,
future: Join<
JoinAll<Vec<Timeout<ApiCall<Bytes, T::Out>>>>,
JoinAll<Vec<JoinAll<Vec<Timeout<ApiCall<Bytes, T::Out>>>>>>
>,
block: u64,
},
RelayWithdraws {
@ -112,13 +79,13 @@ pub fn create_withdraw_relay<T: Transport + Clone>(app: Arc<App<T>>, init: &Data
request_timeout: app.config.foreign.request_timeout,
poll_interval: app.config.foreign.poll_interval,
confirmations: app.config.foreign.required_confirmations,
filter: collected_signatures_filter(&app.foreign_bridge, init.foreign_contract_address.clone()),
filter: collected_signatures_filter(&app.foreign_bridge, init.foreign_contract_address),
};
WithdrawRelay {
logs: api::log_stream(app.connections.foreign.clone(), app.timer.clone(), logs_init),
home_contract: init.home_contract_address.clone(),
foreign_contract: init.foreign_contract_address.clone(),
home_contract: init.home_contract_address,
foreign_contract: init.foreign_contract_address,
state: WithdrawRelayState::Wait,
app,
}
@ -141,13 +108,17 @@ impl<T: Transport> Stream for WithdrawRelay<T> {
let next_state = match self.state {
WithdrawRelayState::Wait => {
let item = try_stream!(self.logs.poll());
info!("got {} new signed withdraws to relay", item.logs.len());
let assignments = item.logs
.into_iter()
.map(|log| signatures_payload(
.map(|log| {
info!("collected signature is ready for relay: tx hash: {}", log.transaction_hash.unwrap());
signatures_payload(
&self.app.foreign_bridge,
self.app.config.authorities.required_signatures,
self.app.config.foreign.account.clone(),
log))
self.app.config.foreign.account,
log)
})
.collect::<error::Result<Vec<_>>>()?;
let (signatures, messages): (Vec<_>, Vec<_>) = assignments.into_iter()
@ -176,82 +147,71 @@ impl<T: Transport> Stream for WithdrawRelay<T> {
.map(|calls| join_all(calls))
.collect::<Vec<_>>();
// wait for fetching of messages and signatures to complete
info!("fetching messages and signatures");
WithdrawRelayState::FetchMessagesSignatures {
future: join_all(message_calls).join(join_all(signature_calls)),
block: item.to,
}
},
WithdrawRelayState::FetchMessagesSignatures { ref mut future, block } => {
let (messages, signatures) = try_ready!(future.poll());
assert_eq!(messages.len(), signatures.len());
let (messages_raw, signatures_raw) = try_ready!(future.poll());
info!("fetching messages and signatures complete");
assert_eq!(messages_raw.len(), signatures_raw.len());
let app = &self.app;
let home_contract = &self.home_contract;
let message_value_sufficient_payloads = messages
let messages = messages_raw
.iter()
.map(|message| {
message_value_sufficient_payload(
&app.home_bridge,
message
app.foreign_bridge.functions().message().output(message.0.as_slice()).map(Bytes)
})
.collect::<ethabi::Result<Vec<_>>>()
.map_err(error::Error::from)?;
let signatures = signatures_raw
.iter()
.map(|signatures|
signatures.iter().map(
|signature| {
Signature::from_bytes(
app.foreign_bridge
.functions()
.signature()
.output(signature.0.as_slice())?
.as_slice())
}
)
})
.map(|payload| {
app.timer.timeout(
api::call(&app.connections.home, home_contract.clone(), payload),
app.config.home.request_timeout)
})
.collect::<Vec<_>>();
WithdrawRelayState::FetchMessageValueSufficient {
future: join_all(message_value_sufficient_payloads),
messages,
signatures,
block,
}
},
WithdrawRelayState::FetchMessageValueSufficient {
ref mut future,
ref messages,
ref signatures,
block
} => {
let message_value_sufficient = try_ready!(future.poll());
let app = &self.app;
let home_contract = &self.home_contract;
.collect::<Result<Vec<_>, Error>>()
.map_err(error::Error::from)
)
.collect::<error::Result<Vec<_>>>()?;
let relays = messages.into_iter()
.zip(signatures.into_iter())
.zip(message_value_sufficient.into_iter())
// ignore those messages that don't have sufficient
// value to pay for the relay gas cost
.filter(|&(_, ref is_message_value_sufficient)| {
// TODO [snd] this is ugly.
// in the future ethabi should return a bool
// for `is_message_value_sufficient`
// since the contract function returns a bool
U256::from(is_message_value_sufficient.0.as_slice()) == U256::from(1)
})
.map(|((message, signatures), _)| withdraw_relay_payload(&app.home_bridge, &signatures, message))
.map(|payload| TransactionRequest {
from: app.config.home.account.clone(),
to: Some(home_contract.clone()),
gas: Some(app.config.txs.withdraw_relay.gas.into()),
gas_price: Some(app.config.txs.withdraw_relay.gas_price.into()),
value: None,
data: Some(payload),
nonce: None,
condition: None,
})
.map(|request| {
.map(|(message, signatures)| {
let payload: Bytes = app.home_bridge.functions().withdraw().input(
signatures.iter().map(|x| x.v),
signatures.iter().map(|x| x.r),
signatures.iter().map(|x| x.s),
message.clone().0).into();
let request = TransactionRequest {
from: app.config.home.account,
to: Some(home_contract.clone()),
gas: Some(app.config.txs.withdraw_relay.gas.into()),
gas_price: Some(MessageToMainnet::from_bytes(message.0.as_slice()).mainnet_gas_price),
value: None,
data: Some(payload),
nonce: None,
condition: None,
};
app.timer.timeout(
api::send_transaction(&app.connections.home, request),
app.config.home.request_timeout)
})
.collect::<Vec<_>>();
// wait for relays to complete
info!("relaying {} withdraws", relays.len());
WithdrawRelayState::RelayWithdraws {
future: join_all(relays),
block,
@ -259,10 +219,14 @@ impl<T: Transport> Stream for WithdrawRelay<T> {
},
WithdrawRelayState::RelayWithdraws { ref mut future, block } => {
let _ = try_ready!(future.poll());
info!("relaying withdraws complete");
WithdrawRelayState::Yield(Some(block))
},
WithdrawRelayState::Yield(ref mut block) => match block.take() {
None => WithdrawRelayState::Wait,
None => {
info!("waiting for signed withdraws to relay");
WithdrawRelayState::Wait
},
some => return Ok(some.into()),
}
};
@ -275,20 +239,20 @@ impl<T: Transport> Stream for WithdrawRelay<T> {
mod tests {
use rustc_hex::FromHex;
use web3::types::{Log, Bytes};
use contracts::{home, foreign};
use super::{signatures_payload, withdraw_relay_payload};
use contracts::foreign;
use super::signatures_payload;
#[test]
fn test_signatures_payload() {
let foreign = foreign::ForeignBridge::default();
let my_address = "0xaff3454fce5edbc8cca8697c15331677e6ebcccc".parse().unwrap();
let my_address = "aff3454fce5edbc8cca8697c15331677e6ebcccc".into();
let data = "000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0".from_hex().unwrap();
let log = Log {
data: data.into(),
topics: vec!["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c".parse().unwrap()],
transaction_hash: Some("0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".parse().unwrap()),
topics: vec!["eb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c".into()],
transaction_hash: Some("884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into()),
..Default::default()
};
@ -305,32 +269,18 @@ mod tests {
#[test]
fn test_signatures_payload_not_ours() {
let foreign = foreign::ForeignBridge::default();
let my_address = "0xaff3454fce5edbc8cca8697c15331677e6ebcccd".parse().unwrap();
let my_address = "aff3454fce5edbc8cca8697c15331677e6ebcccd".into();
let data = "000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0".from_hex().unwrap();
let log = Log {
data: data.into(),
topics: vec!["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c".parse().unwrap()],
transaction_hash: Some("0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".parse().unwrap()),
topics: vec!["eb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c".into()],
transaction_hash: Some("884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into()),
..Default::default()
};
let assignment = signatures_payload(&foreign, 2, my_address, log).unwrap();
assert_eq!(None, assignment);
}
#[test]
fn test_withdraw_relay_payload() {
let home = home::HomeBridge::default();
let signatures: Vec<Bytes> = vec![
vec![0x11; 65].into(),
vec![0x22; 65].into(),
];
let message: Bytes = vec![0x33; 84].into();
let payload = withdraw_relay_payload(&home, &signatures, &message);
let expected: Bytes = "9ce318f6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000".from_hex().unwrap().into();
assert_eq!(expected, payload);
}
}

View File

@ -8,7 +8,7 @@ use error::{ResultExt, Error};
use {toml};
const DEFAULT_POLL_INTERVAL: u64 = 1;
const DEFAULT_CONFIRMATIONS: u64 = 12;
const DEFAULT_CONFIRMATIONS: usize = 12;
const DEFAULT_TIMEOUT: u64 = 5;
/// Application config.
@ -57,7 +57,7 @@ pub struct Node {
pub ipc: PathBuf,
pub request_timeout: Duration,
pub poll_interval: Duration,
pub required_confirmations: u64,
pub required_confirmations: usize,
}
impl Node {
@ -154,7 +154,7 @@ mod load {
pub ipc: PathBuf,
pub request_timeout: Option<u64>,
pub poll_interval: Option<u64>,
pub required_confirmations: Option<u64>,
pub required_confirmations: Option<usize>,
}
#[derive(Deserialize)]
@ -230,7 +230,7 @@ home_deploy = { gas = 20 }
let mut expected = Config {
txs: Transactions::default(),
home: Node {
account: "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b".parse().unwrap(),
account: "1B68Cb0B50181FC4006Ce572cF346e596E51818b".into(),
ipc: "/home.ipc".into(),
contract: ContractConfig {
bin: include_str!("../../compiled_contracts/HomeBridge.bin").from_hex().unwrap().into(),
@ -240,7 +240,7 @@ home_deploy = { gas = 20 }
required_confirmations: 100,
},
foreign: Node {
account: "0x0000000000000000000000000000000000000001".parse().unwrap(),
account: "0000000000000000000000000000000000000001".into(),
contract: ContractConfig {
bin: include_str!("../../compiled_contracts/ForeignBridge.bin").from_hex().unwrap().into(),
},
@ -251,9 +251,9 @@ home_deploy = { gas = 20 }
},
authorities: Authorities {
accounts: vec![
"0x0000000000000000000000000000000000000001".parse().unwrap(),
"0x0000000000000000000000000000000000000002".parse().unwrap(),
"0x0000000000000000000000000000000000000003".parse().unwrap(),
"0000000000000000000000000000000000000001".into(),
"0000000000000000000000000000000000000002".into(),
"0000000000000000000000000000000000000003".into(),
],
required_signatures: 2,
},
@ -299,7 +299,7 @@ required_signatures = 2
let expected = Config {
txs: Transactions::default(),
home: Node {
account: "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b".parse().unwrap(),
account: "1B68Cb0B50181FC4006Ce572cF346e596E51818b".into(),
ipc: "".into(),
contract: ContractConfig {
bin: include_str!("../../compiled_contracts/HomeBridge.bin").from_hex().unwrap().into(),
@ -309,7 +309,7 @@ required_signatures = 2
required_confirmations: 12,
},
foreign: Node {
account: "0x0000000000000000000000000000000000000001".parse().unwrap(),
account: "0000000000000000000000000000000000000001".into(),
ipc: "".into(),
contract: ContractConfig {
bin: include_str!("../../compiled_contracts/ForeignBridge.bin").from_hex().unwrap().into(),
@ -320,9 +320,9 @@ required_signatures = 2
},
authorities: Authorities {
accounts: vec![
"0x0000000000000000000000000000000000000001".parse().unwrap(),
"0x0000000000000000000000000000000000000002".parse().unwrap(),
"0x0000000000000000000000000000000000000003".parse().unwrap(),
"0000000000000000000000000000000000000001".into(),
"0000000000000000000000000000000000000002".into(),
"0000000000000000000000000000000000000003".into(),
],
required_signatures: 2,
},

View File

@ -74,8 +74,8 @@ checked_withdraw_confirm = 121
"#;
let expected = Database {
home_contract_address: "0x49edf201c1e139282643d5e7c6fb0c7219ad1db7".parse().unwrap(),
foreign_contract_address: "0x49edf201c1e139282643d5e7c6fb0c7219ad1db8".parse().unwrap(),
home_contract_address: "49edf201c1e139282643d5e7c6fb0c7219ad1db7".into(),
foreign_contract_address: "49edf201c1e139282643d5e7c6fb0c7219ad1db8".into(),
home_deploy: 100,
foreign_deploy: 101,
checked_deposit_relay: 120,

View File

@ -18,6 +18,12 @@ extern crate ethabi_contract;
extern crate rustc_hex;
#[macro_use]
extern crate log;
extern crate ethereum_types;
#[macro_use]
extern crate pretty_assertions;
#[cfg(test)]
#[macro_use]
extern crate quickcheck;
#[macro_use]
mod macros;
@ -30,4 +36,5 @@ pub mod contracts;
pub mod database;
pub mod error;
pub mod util;
pub mod message_to_mainnet;
pub mod signature;

View File

@ -0,0 +1,108 @@
use ethereum_types::{Address, U256, H256};
use contracts::foreign::events::Withdraw;
use web3::types::Log;
use ethabi;
use error::Error;
/// the message that is relayed from side to main.
/// contains all the information required for the relay.
/// validators sign off on this message.
#[derive(PartialEq, Debug)]
pub struct MessageToMainnet {
pub recipient: Address,
pub value: U256,
pub sidenet_transaction_hash: H256,
pub mainnet_gas_price: U256,
}
/// length of a `MessageToMainnet.to_bytes()` in bytes
pub const MESSAGE_LENGTH: usize = 116;
impl MessageToMainnet {
/// parses message from a byte slice
pub fn from_bytes(bytes: &[u8]) -> Self {
assert_eq!(bytes.len(), MESSAGE_LENGTH);
Self {
recipient: bytes[0..20].into(),
value: U256::from_little_endian(&bytes[20..52]),
sidenet_transaction_hash: bytes[52..84].into(),
mainnet_gas_price: U256::from_little_endian(&bytes[84..MESSAGE_LENGTH]),
}
}
/// construct a message from a `Withdraw` event that was logged on `foreign`
pub fn from_log(web3_log: Log) -> Result<Self, Error> {
let ethabi_raw_log = ethabi::RawLog {
topics: web3_log.topics,
data: web3_log.data.0,
};
let withdraw_log = Withdraw::default().parse_log(ethabi_raw_log)?;
let hash = web3_log.transaction_hash.ok_or_else(|| "`log` must be mined and contain `transaction_hash`")?;
Ok(Self {
recipient: withdraw_log.recipient,
value: withdraw_log.value,
sidenet_transaction_hash: hash,
mainnet_gas_price: withdraw_log.home_gas_price,
})
}
/// serializes message to a byte vector.
/// mainly used to construct the message byte vector that is then signed
/// and passed to `ForeignBridge.submitSignature`
pub fn to_bytes(&self) -> Vec<u8> {
let mut result = vec![0u8; MESSAGE_LENGTH];
result[0..20].copy_from_slice(&self.recipient.0[..]);
self.value.to_little_endian(&mut result[20..52]);
result[52..84].copy_from_slice(&self.sidenet_transaction_hash.0[..]);
self.mainnet_gas_price.to_little_endian(&mut result[84..MESSAGE_LENGTH]);
return result;
}
/// serializes message to an ethabi payload
pub fn to_payload(&self) -> Vec<u8> {
ethabi::encode(&[ethabi::Token::Bytes(self.to_bytes())])
}
}
#[cfg(test)]
mod test {
use quickcheck::TestResult;
use super::*;
quickcheck! {
fn quickcheck_message_to_mainnet_roundtrips_to_bytes(
recipient_raw: Vec<u8>,
value_raw: u64,
sidenet_transaction_hash_raw: Vec<u8>,
mainnet_gas_price_raw: u64
) -> TestResult {
if recipient_raw.len() != 20 || sidenet_transaction_hash_raw.len() != 32 {
return TestResult::discard();
}
let recipient: Address = recipient_raw.as_slice().into();
let value: U256 = value_raw.into();
let sidenet_transaction_hash: H256 = sidenet_transaction_hash_raw.as_slice().into();
let mainnet_gas_price: U256 = mainnet_gas_price_raw.into();
let message = MessageToMainnet {
recipient,
value,
sidenet_transaction_hash,
mainnet_gas_price
};
let bytes = message.to_bytes();
assert_eq!(message, MessageToMainnet::from_bytes(bytes.as_slice()));
let payload = message.to_payload();
let mut tokens = ethabi::decode(&[ethabi::ParamType::Bytes], payload.as_slice())
.unwrap();
let decoded = tokens.pop().unwrap().to_bytes().unwrap();
assert_eq!(message, MessageToMainnet::from_bytes(decoded.as_slice()));
TestResult::passed()
}
}
}

77
bridge/src/signature.rs Normal file
View File

@ -0,0 +1,77 @@
/// ECDSA signatures:
/// conversion from/to byte vectors.
/// from/to v, r, s components.
use ethereum_types::H256;
use ethabi;
use error::Error;
pub const SIGNATURE_LENGTH: usize = 65;
/// an ECDSA signature consisting of `v`, `r` and `s`
#[derive(PartialEq, Debug)]
pub struct Signature {
pub v: u8,
pub r: H256,
pub s: H256,
}
impl Signature {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() != SIGNATURE_LENGTH {
bail!("`bytes`.len() must be {}", SIGNATURE_LENGTH);
}
Ok(Self {
v: bytes[64],
r: bytes[0..32].into(),
s: bytes[32..64].into(),
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut result = vec![0u8; SIGNATURE_LENGTH];
result[0..32].copy_from_slice(&self.r.0[..]);
result[32..64].copy_from_slice(&self.s.0[..]);
result[64] = self.v;
return result;
}
pub fn to_payload(&self) -> Vec<u8> {
ethabi::encode(&[ethabi::Token::Bytes(self.to_bytes())])
}
}
#[cfg(test)]
mod test {
use quickcheck::TestResult;
use super::*;
quickcheck! {
fn quickcheck_signature_roundtrips(v: u8, r_raw: Vec<u8>, s_raw: Vec<u8>) -> TestResult {
if r_raw.len() != 32 || s_raw.len() != 32 {
return TestResult::discard();
}
let r: H256 = r_raw.as_slice().into();
let s: H256 = s_raw.as_slice().into();
let signature = Signature { v, r, s };
assert_eq!(v, signature.v);
assert_eq!(r, signature.r);
assert_eq!(s, signature.s);
let bytes = signature.to_bytes();
assert_eq!(signature, Signature::from_bytes(bytes.as_slice()).unwrap());
let payload = signature.to_payload();
let mut tokens = ethabi::decode(&[ethabi::ParamType::Bytes], payload.as_slice())
.unwrap();
let decoded = tokens.pop().unwrap().to_bytes().unwrap();
assert_eq!(signature, Signature::from_bytes(decoded.as_slice()).unwrap());
TestResult::passed()
}
}
}

View File

@ -7,7 +7,7 @@ fn web3_topic(topic: ethabi::Topic<ethabi::Hash>) -> Option<Vec<H256>> {
if t.is_empty() {
None
} else {
Some(t.into_iter().map(|x| H256(x)).collect())
Some(t)
}
}

View File

@ -14,5 +14,5 @@ serde_derive = "1.0"
tokio-core = "0.1.8"
docopt = "0.8.1"
log = "0.3"
env_logger = "0.3"
env_logger = "0.4"
futures = "0.1.14"

View File

@ -1,12 +1,11 @@
pragma solidity ^0.4.17;
pragma solidity ^0.4.19;
/// 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++) {
for (uint256 i = 0; i < array.length; i++) {
if (array[i] == value) {
return true;
}
@ -16,10 +15,10 @@ library Helpers {
// returns the digits of `inputValue` as a string.
// example: `uintToString(12345678)` returns `"12345678"`
function uintToString(uint inputValue) internal pure returns (string) {
function uintToString(uint256 inputValue) internal pure returns (string) {
// figure out the length of the resulting string
uint length = 0;
uint currentValue = inputValue;
uint256 length = 0;
uint256 currentValue = inputValue;
do {
length++;
currentValue /= 10;
@ -27,7 +26,7 @@ library Helpers {
// allocate enough memory
bytes memory result = new bytes(length);
// construct the string backwards
uint i = length - 1;
uint256 i = length - 1;
currentValue = inputValue;
do {
result[i--] = byte(48 + currentValue % 10);
@ -35,6 +34,35 @@ library Helpers {
} while (currentValue != 0);
return string(result);
}
/// returns whether signatures (whose components are in `vs`, `rs`, `ss`)
/// contain `requiredSignatures` distinct correct signatures
/// where signer is in `allowed_signers`
/// that signed `message`
function hasEnoughValidSignatures(bytes message, uint8[] vs, bytes32[] rs, bytes32[] ss, address[] allowed_signers, uint256 requiredSignatures) internal pure returns (bool) {
// not enough signatures
if (vs.length < requiredSignatures) {
return false;
}
var hash = MessageSigning.hashMessage(message);
var encountered_addresses = new address[](allowed_signers.length);
for (uint256 i = 0; i < requiredSignatures; i++) {
var recovered_address = ecrecover(hash, vs[i], rs[i], ss[i]);
// only signatures by addresses in `addresses` are allowed
if (!addressArrayContains(allowed_signers, recovered_address)) {
return false;
}
// duplicate signatures are not allowed
if (addressArrayContains(encountered_addresses, recovered_address)) {
return false;
}
encountered_addresses[i] = recovered_address;
}
return true;
}
}
@ -47,6 +75,10 @@ library HelpersTest {
function uintToString(uint256 inputValue) public pure returns (string str) {
return Helpers.uintToString(inputValue);
}
function hasEnoughValidSignatures(bytes message, uint8[] vs, bytes32[] rs, bytes32[] ss, address[] addresses, uint256 requiredSignatures) public pure returns (bool) {
return Helpers.hasEnoughValidSignatures(message, vs, rs, ss, addresses, requiredSignatures);
}
}
@ -84,10 +116,11 @@ library MessageSigningTest {
library Message {
// layout of message :: bytes:
// offset 0: 32 bytes :: uint (little endian) - message length
// offset 0: 32 bytes :: uint256 (little endian) - message length
// offset 32: 20 bytes :: address - recipient address
// offset 52: 32 bytes :: uint (little endian) - value
// offset 52: 32 bytes :: uint256 (little endian) - value
// offset 84: 32 bytes :: bytes32 - transaction hash
// offset 116: 32 bytes :: uint256 (little endian) - home gas price
// bytes 1 to 32 are 0 because message length is stored as little endian.
// mload always reads 32 bytes.
@ -109,8 +142,8 @@ library Message {
return recipient;
}
function getValue(bytes message) internal pure returns (uint) {
uint value;
function getValue(bytes message) internal pure returns (uint256) {
uint256 value;
// solium-disable-next-line security/no-inline-assembly
assembly {
value := mload(add(message, 52))
@ -126,6 +159,15 @@ library Message {
}
return hash;
}
function getHomeGasPrice(bytes message) internal pure returns (uint256) {
uint256 gasPrice;
// solium-disable-next-line security/no-inline-assembly
assembly {
gasPrice := mload(add(message, 116))
}
return gasPrice;
}
}
@ -135,13 +177,17 @@ library MessageTest {
return Message.getRecipient(message);
}
function getValue(bytes message) public pure returns (uint) {
function getValue(bytes message) public pure returns (uint256) {
return Message.getValue(message);
}
function getTransactionHash(bytes message) public pure returns (bytes32) {
return Message.getTransactionHash(message);
}
function getHomeGasPrice(bytes message) public pure returns (uint256) {
return Message.getHomeGasPrice(message);
}
}
@ -149,14 +195,14 @@ contract HomeBridge {
/// Number of authorities signatures required to withdraw the money.
///
/// Must be lesser than number of authorities.
uint public requiredSignatures;
uint256 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;
uint256 public estimatedGasCostOfWithdraw;
/// Contract authorities.
address[] public authorities;
@ -165,32 +211,16 @@ contract HomeBridge {
mapping (bytes32 => bool) withdraws;
/// Event created on money deposit.
event Deposit (address recipient, uint value);
event Deposit (address recipient, uint256 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;
}
_;
}
event Withdraw (address recipient, uint256 value);
/// Constructor.
function HomeBridge(
uint requiredSignaturesParam,
uint256 requiredSignaturesParam,
address[] authoritiesParam,
uint estimatedGasCostOfWithdrawParam
uint256 estimatedGasCostOfWithdrawParam
) public
{
require(requiredSignaturesParam != 0);
@ -205,31 +235,37 @@ contract HomeBridge {
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();
}
/// final step of a withdraw.
/// checks that `requiredSignatures` `authorities` have signed of on the `message`.
/// then transfers `value` to `recipient` (both extracted from `message`).
/// see message library above for a breakdown of the `message` contents.
/// `vs`, `rs`, `ss` are the components of the signatures.
/// an upper bound to the cost of relaying a withdraw by calling HomeBridge.withdraw
function getWithdrawRelayCost() public view returns (uint) {
return estimatedGasCostOfWithdraw * tx.gasprice;
}
/// anyone can call this, provided they have the message and required signatures!
/// only the `authorities` can create these signatures.
/// `requiredSignatures` authorities can sign arbitrary `message`s
/// transfering any ether `value` out of this contract to `recipient`.
/// bridge users must trust a majority of `requiredSignatures` of the `authorities`.
function withdraw(uint8[] vs, bytes32[] rs, bytes32[] ss, bytes message) public {
require(message.length == 116);
// check that at least `requiredSignatures` `authorities` have signed `message`
require(Helpers.hasEnoughValidSignatures(message, vs, rs, ss, authorities, requiredSignatures));
/// 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);
uint256 value = Message.getValue(message);
bytes32 hash = Message.getTransactionHash(message);
uint256 homeGasPrice = Message.getHomeGasPrice(message);
// if the recipient calls `withdraw` they can choose the gas price freely.
// if anyone else calls `withdraw` they have to use the gas price
// `homeGasPrice` specified by the user initiating the withdraw.
// this is a security mechanism designed to shut down
// malicious senders setting extremely high gas prices
// and effectively burning recipients withdrawn value.
// see https://github.com/paritytech/parity-bridge/issues/112
// for further explanation.
require((recipient == msg.sender) || (tx.gasprice == homeGasPrice));
// The following two statements guard against reentry into this function.
// Duplicated withdraw or reentry.
@ -237,15 +273,10 @@ contract HomeBridge {
// 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();
uint256 estimatedWeiCostOfWithdraw = estimatedGasCostOfWithdraw * homeGasPrice;
// charge recipient for relay cost
uint valueRemainingAfterSubtractingCost = value - estimatedWeiCostOfWithdraw;
uint256 valueRemainingAfterSubtractingCost = value - estimatedWeiCostOfWithdraw;
// pay out recipient
recipient.transfer(valueRemainingAfterSubtractingCost);
@ -264,165 +295,209 @@ contract ERC20 {
}
contract ForeignBridge {
struct SignaturesCollection {
/// Signed message.
bytes message;
/// Authorities who signed the message.
address[] signed;
/// Signaturs
bytes[] signatures;
}
/// Number of authorities signatures required to withdraw the money.
///
/// Must be lesser than number of authorities.
uint public requiredSignatures;
/// Must be less than number of authorities.
uint256 public requiredSignatures;
uint256 public estimatedGasCostOfWithdraw;
/// Contract authorities.
address[] public 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;
/// Pending deposits and authorities who confirmed them
mapping (bytes32 => address[]) deposits;
mapping (bytes32 => bool) deposits_signed;
mapping (bytes32 => uint) num_deposits_signed;
/// List of authorities confirmed to set up ERC-20 token address
mapping (address => address[]) public token_address;
/// Token to work with
ERC20 public erc20token;
/// Event created on money deposit.
event TokenAddress(address token);
/// Pending signatures and authorities who confirmed them
mapping (bytes32 => SignaturesCollection) signatures;
/// Event created on money deposit.
event Deposit(address recipient, uint value);
/// List of authorities confirmed to set up ERC-20 token address
mapping (bytes32 => bool) tokenAddressAprroval_signs;
mapping (address => uint256) num_tokenAddressAprroval_signs;
/// triggered when relay of deposit from HomeBridge is complete
event Deposit(address recipient, uint256 value);
/// Event created on money withdraw.
event Withdraw(address recipient, uint value);
event Withdraw(address recipient, uint256 value, uint256 homeGasPrice);
/// Collected signatures which should be relayed to home chain.
event CollectedSignatures(address authority, bytes32 messageHash);
event CollectedSignatures(address authorityResponsibleForRelay, bytes32 messageHash);
/// Event created when new token address is set up.
event TokenAddress(address token);
/// Constructor.
function ForeignBridge(
uint requiredSignaturesParam,
address[] authoritiesParam
uint256 _requiredSignatures,
address[] _authorities,
uint256 _estimatedGasCostOfWithdraw
) public
{
require(requiredSignaturesParam != 0);
require(requiredSignaturesParam <= authoritiesParam.length);
requiredSignatures = requiredSignaturesParam;
authorities = authoritiesParam;
require(_requiredSignatures != 0);
require(_requiredSignatures <= _authorities.length);
requiredSignatures = _requiredSignatures;
for (uint i = 0; i < _authorities.length; i++) {
authorities[_authorities[i]] = true;
}
estimatedGasCostOfWithdraw = _estimatedGasCostOfWithdraw;
}
/// Multisig authority validation
/// require that sender is an authority
modifier onlyAuthority() {
require(Helpers.addressArrayContains(authorities, msg.sender));
require(authorities[msg.sender]);
_;
}
/// Set up the token address.
/// Set up the token address. It allows to set up or change
/// the ERC20 token address only if authorities confirmed this.
///
/// Usage maps instead of arrey allows to reduce gas consumption
///
/// token address (address)
function setTokenAddress (ERC20 token) public onlyAuthority() {
// Protect duplicated request
require(!token_address[token].contains(msg.sender));
// Duplicated deposits
bytes32 token_sender = keccak256(msg.sender, token);
require(!tokenAddressAprroval_signs[token_sender]);
tokenAddressAprroval_signs[token_sender]= true;
uint signed = num_tokenAddressAprroval_signs[address(token)] + 1;
num_tokenAddressAprroval_signs[address(token)] = signed;
token_address[token].push(msg.sender);
// TODO: this may cause troubles if requriedSignatures len is changed
if (token_address[token].length == requiredSignatures) {
if (signed == requiredSignatures) {
erc20token = ERC20(token);
TokenAddress(token);
}
}
/// Used to deposit money to the contract.
/// Used to transfer tokens to the `recipient`.
/// The bridge contract must own enough tokens to release them for
/// recipients. Tokens must be transfered to the bridge contract BEFORE
/// the first deposit will be performed.
///
/// Usage maps instead of array allows to reduce gas consumption
/// from 91169 to 89348 (solc 0.4.19).
///
/// deposit recipient (bytes20)
/// deposit value (uint)
/// deposit value (uint256)
/// mainnet transaction hash (bytes32) // to avoid transaction duplication
function deposit(address recipient, uint value, bytes32 transactionHash) public onlyAuthority() {
require(erc20token != address(0x0));
// Protection from misbehaing authority
var hash = keccak256(recipient, value, transactionHash);
bytes32 hash_msg = keccak256(recipient, value, transactionHash);
bytes32 hash_sender = keccak256(msg.sender, hash_msg);
// Duplicated deposits
require(!Helpers.addressArrayContains(deposits[hash], msg.sender));
require(!deposits_signed[hash_sender]);
deposits_signed[hash_sender]= true;
uint signed = num_deposits_signed[hash_msg] + 1;
num_deposits_signed[hash_msg] = signed;
deposits[hash].push(msg.sender);
// TODO: this may cause troubles if requriedSignatures len is changed
if (deposits[hash].length == requiredSignatures) {
if (signed == requiredSignatures) {
// If the bridge contract does not own enough tokens to transfer
// it will couse funds lock on the home side of the bridge
erc20token.transfer(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(erc20token.allowance(msg.sender, this) >= value);
erc20token.transferFrom(msg.sender, this, value);
/// Used to transfer `value` of tokens from `_from`s balance on local
/// (`foreign`) chain to the same address (`_from`) on `home` chain.
/// Transfer of tokens within local (`foreign`) chain performed by usual
/// way through transfer method of the token contract.
/// In order to swap tokens to coins the owner (`_from`) must allow this
/// explicitly in the token contract by calling approveAndCall with address
/// of the bridge account.
/// The method locks tokens and 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`, the recipient (`_from`) 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 the
/// recipient completing the transfer.
function receiveApproval(address _from, uint256 _value, ERC20 _tokenContract, bytes _msg) external returns(bool) {
require(erc20token != address(0x0));
require(msg.sender == address(erc20token));
require(erc20token.allowance(_from, this) >= _value);
erc20token.transferFrom(_from, this, _value);
balances[msg.sender] -= value;
Withdraw(recipient, value);
}
// Need to decide what to do with homeGasPrice from the original parity-bridge contract
Withdraw(_from, _value, 18000000000 wei);
/// 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);
return true;
}
/// 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)
/// withdrawal value (uint256)
/// 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);
// ensure that `signature` is really `message` signed by `msg.sender`
require(msg.sender == MessageSigning.recoverAddressFromSignedMessage(signature, message));
// Valid withdraw message must have 84 bytes
require(message.length == 84);
var hash = keccak256(message);
require(message.length == 116);
bytes32 hash = keccak256(message);
bytes32 hash_sender = keccak256(msg.sender, hash);
// Duplicated signatures
require(!Helpers.addressArrayContains(signatures[hash].signed, msg.sender));
signatures[hash].message = message;
signatures[hash].signed.push(msg.sender);
signatures[hash].signatures.push(signature);
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 (signatures[hash].signed.length == requiredSignatures) {
if (signed == requiredSignatures) {
CollectedSignatures(msg.sender, hash);
}
}
/// Get signature
function signature(bytes32 hash, uint index) public view returns (bytes) {
return signatures[hash].signatures[index];
bytes32 sign_idx = keccak256(hash, index);
return signatures[sign_idx];
}
/// Get message
function message(bytes32 hash) public view returns (bytes) {
return signatures[hash].message;
return messages[hash];
}
}
}

View File

@ -0,0 +1,15 @@
[package]
name = "integration-tests"
version = "0.1.0"
authors = ["snd <kruemaxi@gmail.com>"]
[dependencies]
bridge = { path = "../bridge" }
futures = "0.1"
jsonrpc-core = "8.0"
web3 = { git = "https://github.com/tomusdrw/rust-web3", branch = "bridge" }
serde_json = "1.0"
pretty_assertions = "0.2.1"
tempdir = "0.3.5"
ethereum-types = "0.2"
tokio-core = "0.1.8"

View File

@ -0,0 +1,30 @@
estimated_gas_cost_of_withdraw = 0
[home]
account = "0x00bd138abd70e2f00903268f3db08f2d25677c9e"
ipc = "./home.ipc"
required_confirmations = 0
[home.contract]
bin = "../compiled_contracts/HomeBridge.bin"
[foreign]
account = "0x00bd138abd70e2f00903268f3db08f2d25677c9e"
ipc = "./foreign.ipc"
required_confirmations = 0
[foreign.contract]
bin = "../compiled_contracts/ForeignBridge.bin"
[authorities]
accounts = [
"0x00bd138abd70e2f00903268f3db08f2d25677c9e",
]
required_signatures = 1
[transactions]
home_deploy = { gas = 3000000 }
foreign_deploy = { gas = 3000000 }
deposit_relay = { gas = 3000000 }
withdraw_relay = { gas = 3000000 }
withdraw_confirm = { gas = 3000000 }

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,273 @@
/// spins up two parity nodes with the dev chain.
/// starts one bridge authority that connects the two.
/// does a deposit by sending ether to the HomeBridge.
/// asserts that the deposit got relayed to foreign chain.
/// does a withdraw by executing ForeignBridge.transferToHomeBridge.
/// asserts that the withdraw got relayed to home chain.
extern crate tempdir;
extern crate ethereum_types;
extern crate web3;
extern crate tokio_core;
extern crate bridge;
use std::process::Command;
use std::time::Duration;
use std::thread;
use std::path::Path;
use tokio_core::reactor::Core;
use web3::transports::ipc::Ipc;
use web3::api::Namespace;
use ethereum_types::{Address, U256};
const TMP_PATH: &str = "tmp";
fn parity_home_command() -> Command {
let mut command = Command::new("parity");
command
.arg("--base-path").arg(format!("{}/home", TMP_PATH))
.arg("--chain").arg("dev")
.arg("--ipc-path").arg("home.ipc")
.arg("--logging").arg("rpc=trace")
.arg("--jsonrpc-port").arg("8550")
.arg("--jsonrpc-apis").arg("all")
.arg("--port").arg("30310")
.arg("--gasprice").arg("0")
.arg("--reseal-min-period").arg("0")
.arg("--no-ws")
.arg("--no-dapps")
.arg("--no-ui");
command
}
fn parity_foreign_command() -> Command {
let mut command = Command::new("parity");
command
.arg("--base-path").arg(format!("{}/foreign", TMP_PATH))
.arg("--chain").arg("dev")
.arg("--ipc-path").arg("foreign.ipc")
.arg("--logging").arg("rpc=trace")
.arg("--jsonrpc-port").arg("8551")
.arg("--jsonrpc-apis").arg("all")
.arg("--port").arg("30311")
.arg("--gasprice").arg("0")
.arg("--reseal-min-period").arg("0")
.arg("--no-ws")
.arg("--no-dapps")
.arg("--no-ui");
command
}
fn address_from_str(string: &'static str) -> web3::types::Address {
web3::types::Address::from(&Address::from(string).0[..])
}
#[test]
fn test_basic_deposit_then_withdraw() {
if Path::new(TMP_PATH).exists() {
std::fs::remove_dir_all(TMP_PATH).expect("failed to remove tmp dir");
}
let _tmp_dir = tempdir::TempDir::new(TMP_PATH).expect("failed to create tmp dir");
println!("\nbuild the bridge cli executable so we can run it later\n");
assert!(Command::new("cargo")
.env("RUST_BACKTRACE", "1")
.current_dir("../cli")
.arg("build")
.status()
.expect("failed to build bridge cli")
.success());
// start a parity node that represents the home chain
let mut parity_home = parity_home_command()
.spawn()
.expect("failed to spawn parity home node");
// start a parity node that represents the foreign chain
let mut parity_foreign = parity_foreign_command()
.spawn()
.expect("failed to spawn parity foreign node");
// give the clients time to start up
thread::sleep(Duration::from_millis(3000));
// A address containing a lot of tokens (0x00a329c0648769a73afac7f9381e08fb43dbea72) should be
// automatically added with a password being an empty string.
// source: https://paritytech.github.io/wiki/Private-development-chain.html
let user_address = "0x00a329c0648769a73afac7f9381e08fb43dbea72";
let authority_address = "0x00bd138abd70e2f00903268f3db08f2d25677c9e";
// create authority account on home
let exit_status = Command::new("curl")
.arg("--data").arg(r#"{"jsonrpc":"2.0","method":"parity_newAccountFromPhrase","params":["node0", ""],"id":0}"#)
.arg("-H").arg("Content-Type: application/json")
.arg("-X").arg("POST")
.arg("localhost:8550")
.status()
.expect("failed to create authority account on home");
assert!(exit_status.success());
// TODO [snd] assert that created address matches authority_address
// create authority account on foreign
let exit_status = Command::new("curl")
.arg("--data").arg(r#"{"jsonrpc":"2.0","method":"parity_newAccountFromPhrase","params":["node0", ""],"id":0}"#)
.arg("-H").arg("Content-Type: application/json")
.arg("-X").arg("POST")
.arg("localhost:8551")
.status()
.expect("failed to create/unlock authority account on foreign");
assert!(exit_status.success());
// TODO [snd] assert that created address matches authority_address
// give the operations time to complete
thread::sleep(Duration::from_millis(5000));
// kill the clients so we can restart them with the accounts unlocked
parity_home.kill().unwrap();
parity_foreign.kill().unwrap();
// wait for clients to shut down
thread::sleep(Duration::from_millis(5000));
// start a parity node that represents the home chain with accounts unlocked
let mut parity_home = parity_home_command()
.arg("--unlock").arg(format!("{},{}", user_address, authority_address))
.arg("--password").arg("password.txt")
.spawn()
.expect("failed to spawn parity home node");
// start a parity node that represents the foreign chain with accounts unlocked
let mut parity_foreign = parity_foreign_command()
.arg("--unlock").arg(format!("{},{}", user_address, authority_address))
.arg("--password").arg("password.txt")
.spawn()
.expect("failed to spawn parity foreign node");
// give nodes time to start up
thread::sleep(Duration::from_millis(10000));
// start bridge authority 1
let mut bridge1 = Command::new("env")
.arg("RUST_BACKTRACE=1")
.arg("../target/debug/bridge")
.env("RUST_LOG", "info")
.arg("--config").arg("bridge_config.toml")
.arg("--database").arg("tmp/bridge1_db.txt")
.spawn()
.expect("failed to spawn bridge process");
// give the bridge time to start up and deploy the contracts
thread::sleep(Duration::from_millis(10000));
let home_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f";
let foreign_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f";
println!("\nuser deposits ether into HomeBridge\n");
// TODO [snd] use rpc client here instead of curl
let exit_status = Command::new("curl")
.arg("--data").arg(format!(r#"{{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{{
"from": "{}",
"to": "{}",
"value": "0x186a0"
}}],"id":0}}"#, user_address, home_contract_address))
.arg("-H").arg("Content-Type: application/json")
.arg("-X").arg("POST")
.arg("localhost:8550")
.status()
.expect("failed to deposit into HomeBridge");
assert!(exit_status.success());
println!("\ndeposit into home sent. give it plenty of time to get mined and relayed\n");
thread::sleep(Duration::from_millis(10000));
// connect to foreign and home via IPC
let mut event_loop = Core::new().unwrap();
let foreign_transport = Ipc::with_event_loop("foreign.ipc", &event_loop.handle())
.expect("failed to connect to foreign.ipc");
let foreign = bridge::contracts::foreign::ForeignBridge::default();
let foreign_eth = web3::api::Eth::new(foreign_transport);
let home_transport = Ipc::with_event_loop("home.ipc", &event_loop.handle())
.expect("failed to connect to home.ipc");
let home_eth = web3::api::Eth::new(home_transport);
// totalSupply on ForeignBridge should have increased
let total_supply_payload = foreign.functions().total_supply().input();
let future = foreign_eth.call(web3::types::CallRequest{
from: None,
to: address_from_str(foreign_contract_address),
gas: None,
gas_price: None,
value: None,
data: Some(web3::types::Bytes(total_supply_payload)),
}, None);
let response = event_loop.run(future).unwrap();
assert_eq!(
U256::from(response.0.as_slice()),
U256::from(100000),
"totalSupply on ForeignBridge should have increased");
// balance on ForeignBridge should have increased
let balance_payload = foreign.functions().balance_of().input(Address::from(user_address));
let future = foreign_eth.call(web3::types::CallRequest{
from: None,
to: address_from_str(foreign_contract_address),
gas: None,
gas_price: None,
value: None,
data: Some(web3::types::Bytes(balance_payload)),
}, None);
let response = event_loop.run(future).unwrap();
let balance = U256::from(response.0.as_slice());
assert_eq!(
balance,
U256::from(100000),
"balance on ForeignBridge should have increased");
println!("\nconfirmed that deposit reached foreign\n");
println!("\nuser executes ForeignBridge.transferHomeViaRelay\n");
let transfer_payload = foreign.functions()
.transfer_home_via_relay()
.input(
Address::from(user_address),
U256::from(100000),
U256::from(0));
let future = foreign_eth.send_transaction(web3::types::TransactionRequest{
from: address_from_str(user_address),
to: Some(address_from_str(foreign_contract_address)),
gas: None,
gas_price: None,
value: None,
data: Some(web3::types::Bytes(transfer_payload)),
condition: None,
nonce: None,
});
event_loop.run(future).unwrap();
println!("\nForeignBridge.transferHomeViaRelay transaction sent. give it plenty of time to get mined and relayed\n");
thread::sleep(Duration::from_millis(10000));
// test that withdraw completed
let future = home_eth.balance(address_from_str(user_address), None);
println!("waiting for future");
let balance = event_loop.run(future).unwrap();
assert!(balance > web3::types::U256::from(0));
println!("\nconfirmed that withdraw reached home\n");
bridge1.kill().unwrap();
// wait for bridge to shut down
thread::sleep(Duration::from_millis(1000));
parity_home.kill().unwrap();
parity_foreign.kill().unwrap();
}

View File

@ -6,7 +6,10 @@ authors = ["debris <marek.kotewicz@gmail.com>"]
[dependencies]
bridge = { path = "../bridge" }
futures = "0.1"
jsonrpc-core = "7.0"
jsonrpc-core = "8.0"
web3 = { git = "https://github.com/tomusdrw/rust-web3", branch = "bridge" }
serde_json = "1.0"
pretty_assertions = "0.2.1"
ethabi = "5.0"
ethereum-types = "0.2"
rustc-hex = "1.0"

View File

@ -9,24 +9,26 @@ extern crate pretty_assertions;
use std::cell::Cell;
use web3::Transport;
#[derive(Debug, Clone)]
pub struct MockedRequest {
pub method: String,
pub params: Vec<rpc::Value>,
}
impl From<(&'static str, &'static str)> for MockedRequest {
fn from(a: (&'static str, &'static str)) -> Self {
impl From<(&'static str, serde_json::Value)> for MockedRequest {
fn from(a: (&'static str, serde_json::Value)) -> Self {
MockedRequest {
method: a.0.to_owned(),
params: serde_json::from_str(a.1).unwrap(),
params: a.1.as_array().unwrap().clone()
}
}
}
#[derive(Debug, Clone)]
pub struct MockedTransport {
pub requests: Cell<usize>,
pub expected_requests: Vec<MockedRequest>,
pub mocked_responses: Vec<&'static str>,
pub mocked_responses: Vec<serde_json::Value>,
}
impl Transport for MockedTransport {
@ -44,7 +46,7 @@ impl Transport for MockedTransport {
fn send(&self, _id: usize, _request: rpc::Call) -> web3::Result<rpc::Value> {
let response = self.mocked_responses.iter().nth(self.requests.get() - 1).expect("missing response");
let f = futures::finished(serde_json::from_str(response).expect("invalid response"));
let f = futures::finished(response.clone());
Box::new(f)
}
}
@ -155,6 +157,8 @@ macro_rules! test_app_stream {
let stream = $init_stream(app, &$db);
let res = stream.collect().wait();
assert_eq!($expected, res.unwrap());
assert_eq!(
home.expected_requests.len(),
home.requests.get(),
@ -170,8 +174,6 @@ macro_rules! test_app_stream {
foreign.expected_requests.len(),
foreign.requests.get()
);
assert_eq!($expected, res.unwrap());
}
}
}

View File

@ -1,23 +1,27 @@
extern crate futures;
#[macro_use]
extern crate serde_json;
extern crate bridge;
#[macro_use]
extern crate tests;
use bridge::bridge::create_deposit_relay;
const DEPOSIT_TOPIC: &str = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c";
test_app_stream! {
name => deposit_relay_basic,
database => Database::default(),
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -25,17 +29,29 @@ test_app_stream! {
expected => vec![0x1005, 0x1006],
home_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x1005",
"topics": [[DEPOSIT_TOPIC], null, null, null]
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1006","limit":null,"toBlock":"0x1006","topics":[["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1006",
"limit": null,
"toBlock": "0x1006",
"topics": [[DEPOSIT_TOPIC], null, null, null]
}]),
res => json!([]);
],
foreign_transport => []
}
@ -47,15 +63,15 @@ test_app_stream! {
..Default::default()
},
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -63,22 +79,46 @@ test_app_stream! {
expected => vec![0x1005, 0x1006],
home_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x6","limit":null,"toBlock":"0x1005","topics":[["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000000","topics":["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x6",
"limit": null,
"toBlock":"0x1005",
"topics": [[DEPOSIT_TOPIC], null, null, null]
}]),
res => json!([{
"address": "0x0000000000000000000000000000000000000000",
"topics": [DEPOSIT_TOPIC],
"data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0",
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
}]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1006","limit":null,"toBlock":"0x1006","topics":[["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1006",
"limit": null,
"toBlock": "0x1006",
"topics":[[DEPOSIT_TOPIC], null, null, null]
}]),
res => json!([]);
],
foreign_transport => [
"eth_sendTransaction" =>
req => r#"[{"data":"0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364","from":"0x0000000000000000000000000000000000000001","gas":"0x0","gasPrice":"0x0","to":"0x0000000000000000000000000000000000000000"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": "0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364",
"from": "0x0000000000000000000000000000000000000001",
"gas": "0x0",
"gasPrice": "0x0",
"to": "0x0000000000000000000000000000000000000000"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
]
}
@ -89,15 +129,15 @@ test_app_stream! {
..Default::default()
},
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions {
@ -111,36 +151,52 @@ test_app_stream! {
expected => vec![0x1005],
home_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x6","limit":null,"toBlock":"0x1005","topics":[["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000000","topics":["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x6",
"limit": null,
"toBlock": "0x1005",
"topics": [[DEPOSIT_TOPIC], null, null, null]
}]),
res => json!([{
"address": "0x0000000000000000000000000000000000000000",
"topics": [DEPOSIT_TOPIC],
"data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
}]);
],
foreign_transport => [
"eth_sendTransaction" =>
req => r#"[{"data":"0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364","from":"0x0000000000000000000000000000000000000001","gas":"0xfd","gasPrice":"0xa0","to":"0x0000000000000000000000000000000000000000"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": "0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364",
"from": "0x0000000000000000000000000000000000000001",
"gas": "0xfd",
"gasPrice": "0xa0",
"to": "0x0000000000000000000000000000000000000000"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
]
}
test_app_stream! {
name => deposit_relay_contract_address,
database => Database {
home_contract_address: "0x0000000000000000000000000000000000000cc1".parse().unwrap(),
foreign_contract_address: "0x0000000000000000000000000000000000000dd1".parse().unwrap(),
home_contract_address: "0000000000000000000000000000000000000cc1".into(),
foreign_contract_address: "0000000000000000000000000000000000000dd1".into(),
..Default::default()
},
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -148,36 +204,54 @@ test_app_stream! {
expected => vec![0x1005],
home_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000cc1"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000cc1","topics":["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000cc1"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x1005",
"topics": [[DEPOSIT_TOPIC], null, null, null]
}]),
res => json!([{
"address": "0x0000000000000000000000000000000000000cc1",
"topics": ["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],
"data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0",
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
}]);
],
foreign_transport => [
"eth_sendTransaction" =>
req => r#"[{"data":"0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364","from":"0x0000000000000000000000000000000000000001","gas":"0x0","gasPrice":"0x0","to":"0x0000000000000000000000000000000000000dd1"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": "0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364",
"from": "0x0000000000000000000000000000000000000001",
"gas": "0x0",
"gasPrice": "0x0",
"to": "0x0000000000000000000000000000000000000dd1"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
]
}
test_app_stream! {
name => deposit_relay_accounts,
database => Database {
home_contract_address: "0x0000000000000000000000000000000000000cc1".parse().unwrap(),
foreign_contract_address: "0x0000000000000000000000000000000000000dd1".parse().unwrap(),
home_contract_address: "0000000000000000000000000000000000000cc1".into(),
foreign_contract_address: "0000000000000000000000000000000000000dd1".into(),
..Default::default()
},
home =>
account => "0x00000000000000000000000000000000000000ff",
account => "00000000000000000000000000000000000000ff",
confirmations => 12;
foreign =>
account => "0x00000000000000000000000000000000000000ee",
account => "00000000000000000000000000000000000000ee",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -185,16 +259,34 @@ test_app_stream! {
expected => vec![0x1005],
home_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000cc1"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000cc1","topics":["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000cc1"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x1005",
"topics": [[DEPOSIT_TOPIC], null, null, null]
}]),
res => json!([{
"address": "0x0000000000000000000000000000000000000cc1",
"topics": [DEPOSIT_TOPIC],
"data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0",
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
}]);
],
foreign_transport => [
"eth_sendTransaction" =>
req => r#"[{"data":"0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364","from":"0x00000000000000000000000000000000000000ee","gas":"0x0","gasPrice":"0x0","to":"0x0000000000000000000000000000000000000dd1"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": "0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364",
"from": "0x00000000000000000000000000000000000000ee",
"gas": "0x0",
"gasPrice": "0x0",
"to":"0x0000000000000000000000000000000000000dd1"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
]
}
@ -202,15 +294,15 @@ test_app_stream! {
name => deposit_relay_multiple_logs,
database => Database::default(),
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -218,18 +310,51 @@ test_app_stream! {
expected => vec![0x1005],
home_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000000","topics":["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"},{"address":"0x0000000000000000000000000000000000000000","topics":["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a942436f"}]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x1005",
"topics": [[DEPOSIT_TOPIC], null, null, null]
}]),
res => json!([
{
"address": "0x0000000000000000000000000000000000000000",
"topics": ["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],
"data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0",
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
},
{
"address":"0x0000000000000000000000000000000000000000",
"topics": ["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c"],
"data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0",
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a942436f"
}
]);
],
foreign_transport => [
"eth_sendTransaction" =>
req => r#"[{"data":"0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364","from":"0x0000000000000000000000000000000000000001","gas":"0x0","gasPrice":"0x0","to":"0x0000000000000000000000000000000000000000"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": "0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364",
"from": "0x0000000000000000000000000000000000000001",
"gas": "0x0",
"gasPrice": "0x0",
"to": "0x0000000000000000000000000000000000000000"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
"eth_sendTransaction" =>
req => r#"[{"data":"0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a942436f","from":"0x0000000000000000000000000000000000000001","gas":"0x0","gasPrice":"0x0","to":"0x0000000000000000000000000000000000000000"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": "0x26b3293f000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a942436f",
"from": "0x0000000000000000000000000000000000000001",
"gas": "0x0",
"gasPrice": "0x0",
"to": "0x0000000000000000000000000000000000000000"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
]
}

View File

@ -1,4 +1,6 @@
extern crate futures;
#[macro_use]
extern crate serde_json;
extern crate web3;
extern crate bridge;
#[macro_use]
@ -31,20 +33,32 @@ test_transport_stream! {
logs: vec![],
}],
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1010""#;
req => json!([]),
res => json!("0x1010");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0xb","limit":null,"toBlock":"0x1006","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0xb",
"limit": null,
"toBlock": "0x1006",
"topics": null
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1010""#;
req => json!([]),
res => json!("0x1010");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0x1007","limit":null,"toBlock":"0x1007","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0x1007",
"limit": null,
"toBlock": "0x1007",
"topics": null
}]),
res => json!([]);
}
test_transport_stream! {
@ -70,23 +84,35 @@ test_transport_stream! {
logs: vec![],
}],
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x17""#;
req => json!([]),
res => json!("0x17");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0xb","limit":null,"toBlock":"0xd","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0xb",
"limit": null,
"toBlock": "0xd",
"topics": null
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x16""#;
req => json!([]),
res => json!("0x16");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x17""#;
req => json!([]),
res => json!("0x17");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x19""#;
req => json!([]),
res => json!("0x19");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0xe","limit":null,"toBlock":"0xf","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0xe",
"limit": null,
"toBlock": "0xf",
"topics": null
}]),
res => json!([]);
}
test_transport_stream! {
@ -108,17 +134,23 @@ test_transport_stream! {
logs: vec![],
}],
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x13""#;
req => json!([]),
res => json!("0x13");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x14""#;
req => json!([]),
res => json!("0x14");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x17""#;
req => json!([]),
res => json!("0x17");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0xb","limit":null,"toBlock":"0xd","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0xb",
"limit": null,
"toBlock": "0xd",
"topics": null
}]),
res => json!([]);
}
test_transport_stream! {
@ -148,26 +180,44 @@ test_transport_stream! {
logs: vec![],
}],
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x13""#;
req => json!([]),
res => json!("0x13");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0xb","limit":null,"toBlock":"0x13","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0xb",
"limit": null,
"toBlock": "0x13",
"topics": null
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x14""#;
req => json!([]),
res => json!("0x14");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0x14","limit":null,"toBlock":"0x14","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0x14",
"limit": null,
"toBlock": "0x14",
"topics": null
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x14""#;
req => json!([]),
res => json!("0x14");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x17""#;
req => json!([]),
res => json!("0x17");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0x15","limit":null,"toBlock":"0x17","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0x15",
"limit": null,
"toBlock": "0x17",
"topics": null
}]),
res => json!([]);
}
test_transport_stream! {
@ -193,17 +243,29 @@ test_transport_stream! {
logs: vec![],
}],
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x13""#;
req => json!([]),
res => json!("0x13");
"eth_getLogs" =>
req => r#"[{"address":["0x1111111111111111111111111111111111111111"],"fromBlock":"0xc","limit":null,"toBlock":"0x13","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x1111111111111111111111111111111111111111"],
"fromBlock": "0xc",
"limit": null,
"toBlock": "0x13",
"topics": null
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x14""#;
req => json!([]),
res => json!("0x14");
"eth_getLogs" =>
req => r#"[{"address":["0x1111111111111111111111111111111111111111"],"fromBlock":"0x14","limit":null,"toBlock":"0x14","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address":["0x1111111111111111111111111111111111111111"],
"fromBlock": "0x14",
"limit": null,
"toBlock": "0x14",
"topics": null
}]),
res => json!([]);
}
test_transport_stream! {
@ -229,17 +291,29 @@ test_transport_stream! {
logs: vec![],
}],
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x13""#;
req => json!([]),
res => json!("0x13");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0xc","limit":null,"toBlock":"0x13","topics":[["0x2222222222222222222222222222222222222222222222222222222222222222"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0xc",
"limit": null,
"toBlock": "0x13",
"topics":[["0x2222222222222222222222222222222222222222222222222222222222222222"], null, null, null]
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x14""#;
req => json!([]),
res => json!("0x14");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0x14","limit":null,"toBlock":"0x14","topics":[["0x2222222222222222222222222222222222222222222222222222222222222222"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0x14",
"limit": null,
"toBlock": "0x14",
"topics": [["0x2222222222222222222222222222222222222222222222222222222222222222"], null, null, null]
}]),
res => json!([]);
}
test_transport_stream! {
@ -259,7 +333,7 @@ test_transport_stream! {
from: 0xb,
to: 0x1006,
logs: vec![Log {
address: "0x0000000000000000000000000000000000000001".parse().unwrap(),
address: "0000000000000000000000000000000000000001".into(),
topics: vec![],
data: vec![0x10].into(),
log_type: "".into(),
@ -267,12 +341,22 @@ test_transport_stream! {
}],
}],
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1010""#;
req => json!([]),
res => json!("0x1010");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0xb","limit":null,"toBlock":"0x1006","topics":null}]"#,
res =>
r#"[{"address":"0x0000000000000000000000000000000000000001","topics":[],"data":"0x10","type":""}]"#;
req => json!([{
"address": null,
"fromBlock": "0xb",
"limit": null,
"toBlock": "0x1006",
"topics": null
}]),
res => json!([{
"address": "0x0000000000000000000000000000000000000001",
"topics": [],
"data": "0x10",
"type": ""
}]);
}
test_transport_stream! {
@ -292,7 +376,7 @@ test_transport_stream! {
from: 0xb,
to: 0x1006,
logs: vec![Log {
address: "0x0000000000000000000000000000000000000001".parse().unwrap(),
address: "0000000000000000000000000000000000000001".into(),
topics: vec![],
data: vec![0x10].into(),
log_type: "".into(),
@ -306,13 +390,13 @@ test_transport_stream! {
from: 0x1008,
to: 0x1008,
logs: vec![Log {
address: "0x0000000000000000000000000000000000000002".parse().unwrap(),
address: "0000000000000000000000000000000000000002".into(),
topics: vec![],
data: vec![0x20].into(),
log_type: "".into(),
..Default::default()
}, Log {
address: "0x0000000000000000000000000000000000000002".parse().unwrap(),
address: "0000000000000000000000000000000000000002".into(),
topics: vec![],
data: vec![0x30].into(),
log_type: "".into(),
@ -320,21 +404,57 @@ test_transport_stream! {
}],
}],
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1010""#;
req => json!([]),
res => json!("0x1010");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0xb","limit":null,"toBlock":"0x1006","topics":null}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000001","topics":[],"data":"0x10","type":""}]"#;
req => json!([{
"address": null,
"fromBlock": "0xb",
"limit": null,
"toBlock": "0x1006",
"topics": null
}]),
res => json!([{
"address": "0x0000000000000000000000000000000000000001",
"topics": [],
"data": "0x10",
"type": ""
}]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0x1007","limit":null,"toBlock":"0x1007","topics":null}]"#,
res => r#"[]"#;
req => json!([{
"address": null,
"fromBlock": "0x1007",
"limit": null,
"toBlock": "0x1007",
"topics": null
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":null,"fromBlock":"0x1008","limit":null,"toBlock":"0x1008","topics":null}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000002","topics":[],"data":"0x20","type":""},{"address":"0x0000000000000000000000000000000000000002","topics":[],"data":"0x30","type":""}]"#;
req => json!([{
"address": null,
"fromBlock": "0x1008",
"limit": null,
"toBlock": "0x1008",
"topics": null
}]),
res => json!([
{
"address": "0x0000000000000000000000000000000000000002",
"topics": [],
"data": "0x20",
"type":""
},
{
"address":"0x0000000000000000000000000000000000000002",
"topics": [],
"data": "0x30",
"type": ""
}
]);
}

View File

@ -1,23 +1,36 @@
/// test interactions of withdraw_confirm state machine with RPC
extern crate futures;
#[macro_use]
extern crate serde_json;
extern crate bridge;
#[macro_use]
extern crate tests;
extern crate ethabi;
extern crate rustc_hex;
extern crate ethereum_types;
use rustc_hex::{ToHex, FromHex};
use bridge::bridge::create_withdraw_confirm;
use bridge::message_to_mainnet::MessageToMainnet;
use bridge::contracts;
use ethabi::{encode, Token};
const WITHDRAW_TOPIC: &str = "0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568";
test_app_stream! {
name => withdraw_confirm_basic,
database => Database::default(),
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -26,17 +39,29 @@ test_app_stream! {
home_transport => [],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x1005",
"topics": [[WITHDRAW_TOPIC], null, null, null]
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1006","limit":null,"toBlock":"0x1006","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1006",
"limit": null,
"toBlock": "0x1006",
"topics":[[WITHDRAW_TOPIC], null, null, null]
}]),
res => json!([]);
]
}
@ -47,15 +72,15 @@ test_app_stream! {
..Database::default()
},
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 1;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -64,20 +89,31 @@ test_app_stream! {
home_transport => [],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x0100""#;
req => json!([]),
res => json!("0x0100");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0xf6","limit":null,"toBlock":"0x1005","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address":["0x0000000000000000000000000000000000000000"],
"fromBlock": "0xf6",
"limit": null, "toBlock": "0x1005",
"topics": [[WITHDRAW_TOPIC], null, null, null]
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1006","limit":null,"toBlock":"0x1006","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address":["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1006",
"limit": null,
"toBlock": "0x1006",
"topics":[[WITHDRAW_TOPIC], null, null, null]
}]),
res => json!([]);
]
}
@ -85,20 +121,20 @@ test_app_stream! {
name => withdraw_confirm_contract_address,
database => Database {
checked_withdraw_confirm: 0x00F5,
home_contract_address: "0x49edf201c1e139282643d5e7c6fb0c7219ad1db7".parse().unwrap(),
foreign_contract_address: "0x49edf201c1e139282643d5e7c6fb0c7219ad1db8".parse().unwrap(),
home_contract_address: "49edf201c1e139282643d5e7c6fb0c7219ad1db7".into(),
foreign_contract_address: "49edf201c1e139282643d5e7c6fb0c7219ad1db8".into(),
..Database::default()
},
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -107,20 +143,32 @@ test_app_stream! {
home_transport => [],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x0100""#;
req => json!([]),
res => json!("0x0100");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],"fromBlock":"0xf6","limit":null,"toBlock":"0x1005","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address":["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],
"fromBlock": "0xf6",
"limit": null,
"toBlock": "0x1005",
"topics":[[WITHDRAW_TOPIC],null,null,null]
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],"fromBlock":"0x1006","limit":null,"toBlock":"0x1006","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],
"fromBlock": "0x1006",
"limit": null,
"toBlock": "0x1006",
"topics":[[WITHDRAW_TOPIC],null,null,null]
}]),
res => json!([]);
]
}
@ -128,20 +176,20 @@ test_app_stream! {
name => withdraw_confirm_payload_gas,
database => Database {
checked_withdraw_confirm: 0x00F5,
home_contract_address: "0x49edf201c1e139282643d5e7c6fb0c7219ad1db7".parse().unwrap(),
foreign_contract_address: "0x49edf201c1e139282643d5e7c6fb0c7219ad1db8".parse().unwrap(),
home_contract_address: "49edf201c1e139282643d5e7c6fb0c7219ad1db7".into(),
foreign_contract_address: "49edf201c1e139282643d5e7c6fb0c7219ad1db8".into(),
..Database::default()
},
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x00000000000000000000000000000000000000F1",
"0x00000000000000000000000000000000000000F2",
"00000000000000000000000000000000000000F1",
"00000000000000000000000000000000000000F2",
],
signatures => 1;
txs => Transactions {
@ -156,46 +204,97 @@ test_app_stream! {
home_transport => [],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x0100""#;
req => json!([]),
res => json!("0x0100");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],"fromBlock":"0xf6","limit":null,"toBlock":"0x1005","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[{"address":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8","topics":["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
req => json!([{
"address": ["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],
"fromBlock": "0xf6",
"limit": null,
"toBlock": "0x1005",
"topics": [[WITHDRAW_TOPIC], null, null, null]
}]),
res => json!([{
"address": "0x49edf201c1e139282643d5e7c6fb0c7219ad1db8",
"topics": [WITHDRAW_TOPIC],
"data": format!("0x{}", encode(&[
Token::Address([1u8; 20].into()),
Token::Uint(10000.into()),
Token::Uint(1000.into()),
]).to_hex()),
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
}]);
"eth_sign" =>
req => r#"["0x0000000000000000000000000000000000000001","0xaff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"]"#,
res => r#""0x8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc""#;
req => json!([
"0x0000000000000000000000000000000000000001",
format!("0x{}", MessageToMainnet {
recipient: [1u8; 20].into(),
value: 10000.into(),
sidenet_transaction_hash: "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into(),
mainnet_gas_price: 1000.into(),
}
.to_bytes()
.to_hex())
]),
res => json!("0x8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc");
// `submitSignature`
"eth_sendTransaction" =>
req => r#"[{"data":"0x630cea8e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000418697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364000000000000000000000000","from":"0x0000000000000000000000000000000000000001","gas":"0xfe","gasPrice":"0xa1","to":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": format!("0x{}", contracts::foreign::ForeignBridge::default()
.functions()
.submit_signature()
.input(
"8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc".from_hex().unwrap(),
MessageToMainnet {
recipient: [1u8; 20].into(),
value: 10000.into(),
sidenet_transaction_hash: "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into(),
mainnet_gas_price: 1000.into(),
}.to_bytes()
)
.to_hex()),
"from": "0x0000000000000000000000000000000000000001",
"gas": "0xfe",
"gasPrice": "0xa1",
"to":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],"fromBlock":"0x1006","limit":null,"toBlock":"0x1006","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],
"fromBlock": "0x1006",
"limit": null,
"toBlock": "0x1006",
"topics":[[WITHDRAW_TOPIC], null, null, null]
}]),
res => json!([]);
]
}
test_app_stream! {
name => withdraw_confirm_payload_multiple,
database => Database {
home_contract_address: "0x49edf201c1e139282643d5e7c6fb0c7219ad1db7".parse().unwrap(),
foreign_contract_address: "0x49edf201c1e139282643d5e7c6fb0c7219ad1db8".parse().unwrap(),
home_contract_address: "49edf201c1e139282643d5e7c6fb0c7219ad1db7".into(),
foreign_contract_address: "49edf201c1e139282643d5e7c6fb0c7219ad1db8".into(),
..Database::default()
},
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x00000000000000000000000000000000000000F1",
"0x00000000000000000000000000000000000000F2",
"00000000000000000000000000000000000000F1",
"00000000000000000000000000000000000000F2",
],
signatures => 1;
txs => Transactions {
@ -210,28 +309,118 @@ test_app_stream! {
home_transport => [],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0xe""#;
req => json!([]),
res => json!("0xe");
"eth_getLogs" =>
req => r#"[{"address":["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],"fromBlock":"0x1","limit":null,"toBlock":"0x2","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[{"address":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8","topics":["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"},{"address":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8","topics":["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],"data":"0x000000000000000000000000001da5bcab735024168f00b43abcc9ef522392e90000000000000000000000000000000000000000000000000000000000000099","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424399"}]"#;
req => json!([{
"address": ["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x2",
"topics": [[WITHDRAW_TOPIC], null, null, null]
}]),
res => json!([{
"address": "0x49edf201c1e139282643d5e7c6fb0c7219ad1db8",
"topics": [WITHDRAW_TOPIC],
"data": format!("0x{}", encode(&[
Token::Address([1u8; 20].into()),
Token::Uint(10000.into()),
Token::Uint(1000.into()),
]).to_hex()),
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"},
{
"address":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8",
"topics": [WITHDRAW_TOPIC],
"data": format!("0x{}", encode(&[
Token::Address([2u8; 20].into()),
Token::Uint(42.into()),
Token::Uint(100.into()),
]).to_hex()),
"type":"",
"transactionHash":"0xfffedad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
}]);
"eth_sign" =>
req => r#"["0x0000000000000000000000000000000000000001","0xaff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"]"#,
res => r#""0x8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc""#;
req => json!([
"0x0000000000000000000000000000000000000001",
format!("0x{}", MessageToMainnet {
recipient: [1u8; 20].into(),
value: 10000.into(),
sidenet_transaction_hash: "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into(),
mainnet_gas_price: 1000.into(),
}
.to_bytes()
.to_hex())
]),
res => json!("0x8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc");
"eth_sign" =>
req => r#"["0x0000000000000000000000000000000000000001","0x001da5bcab735024168f00b43abcc9ef522392e90000000000000000000000000000000000000000000000000000000000000099884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424399"]"#,
res => r#""0x8a3b24c56e46f6fc9fa7ed14795745348059b8ac84d6ee93323e83a429e760ae6e89510834ee4d65eefacd74cddca53df61b5eba1c3007ed88d2eebff2e0e2151b""#;
req => json!([
"0x0000000000000000000000000000000000000001",
format!("0x{}", MessageToMainnet {
recipient: [2u8; 20].into(),
value: 42.into(),
sidenet_transaction_hash: "0xfffedad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into(),
mainnet_gas_price: 100.into(),
}
.to_bytes()
.to_hex())
]),
res => json!("0x8a3b24c56e46f6fc9fa7ed14795745348059b8ac84d6ee93323e83a429e760ae6e89510834ee4d65eefacd74cddca53df61b5eba1c3007ed88d2eebff2e0e2151b");
// `submitSignature`
"eth_sendTransaction" =>
req => r#"[{"data":"0x630cea8e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000418697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364000000000000000000000000","from":"0x0000000000000000000000000000000000000001","gas":"0xff","gasPrice":"0xaa","to":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": format!("0x{}", contracts::foreign::ForeignBridge::default()
.functions()
.submit_signature()
.input(
"8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677aff3454fce5edbc8cca8697c15331677e6ebccccaff3454fce5edbc8cca8697c15331677e6ebc".from_hex().unwrap(),
MessageToMainnet {
recipient: [1u8; 20].into(),
value: 10000.into(),
sidenet_transaction_hash: "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into(),
mainnet_gas_price: 1000.into(),
}.to_bytes()
)
.to_hex()),
"from": "0x0000000000000000000000000000000000000001",
"gas": "0xff",
"gasPrice": "0xaa",
"to":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
// `submitSignature`
"eth_sendTransaction" =>
req => r#"[{"data":"0x630cea8e000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000418a3b24c56e46f6fc9fa7ed14795745348059b8ac84d6ee93323e83a429e760ae6e89510834ee4d65eefacd74cddca53df61b5eba1c3007ed88d2eebff2e0e2151b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000054001da5bcab735024168f00b43abcc9ef522392e90000000000000000000000000000000000000000000000000000000000000099884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424399000000000000000000000000","from":"0x0000000000000000000000000000000000000001","gas":"0xff","gasPrice":"0xaa","to":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0c""#;
req => json!([{
"data": format!("0x{}", contracts::foreign::ForeignBridge::default()
.functions()
.submit_signature()
.input(
"8a3b24c56e46f6fc9fa7ed14795745348059b8ac84d6ee93323e83a429e760ae6e89510834ee4d65eefacd74cddca53df61b5eba1c3007ed88d2eebff2e0e2151b".from_hex().unwrap(),
MessageToMainnet {
recipient: [2u8; 20].into(),
value: 42.into(),
sidenet_transaction_hash: "0xfffedad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into(),
mainnet_gas_price: 100.into(),
}.to_bytes()
)
.to_hex()),
"from": "0x0000000000000000000000000000000000000001",
"gas": "0xff",
"gasPrice": "0xaa",
"to":"0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0c");
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],"fromBlock":"0x3","limit":null,"toBlock":"0x1006","topics":[["0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x49edf201c1e139282643d5e7c6fb0c7219ad1db8"],
"fromBlock": "0x3",
"limit": null,
"toBlock": "0x1006",
"topics":[[WITHDRAW_TOPIC], null, null, null]
}]),
res => json!([]);
]
}

View File

@ -1,9 +1,24 @@
/// test interactions of withdraw_relay state machine with RPC
extern crate futures;
#[macro_use]
extern crate serde_json;
extern crate bridge;
#[macro_use]
extern crate tests;
extern crate ethabi;
extern crate ethereum_types;
extern crate rustc_hex;
use ethereum_types::{U256, H256};
use rustc_hex::ToHex;
use bridge::bridge::create_withdraw_relay;
use bridge::message_to_mainnet::MessageToMainnet;
use bridge::signature::Signature;
use bridge::contracts;
const COLLECTED_SIGNATURES_TOPIC: &str = "0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c";
// 1 signature required. relay polled twice.
// no CollectedSignatures on ForeignBridge.
@ -12,15 +27,15 @@ test_app_stream! {
name => withdraw_relay_no_log_no_relay,
database => Database::default(),
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -29,17 +44,29 @@ test_app_stream! {
home_transport => [],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x1005",
"topics": [[COLLECTED_SIGNATURES_TOPIC], null, null, null]
}]),
res => json!([]);
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1012""#;
req => json!([]),
res => json!("0x1012");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1006","limit":null,"toBlock":"0x1006","topics":[["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],null,null,null]}]"#,
res => r#"[]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1006",
"limit": null,
"toBlock": "0x1006",
"topics": [[COLLECTED_SIGNATURES_TOPIC], null, null, null]
}]),
res => json!([]);
]
}
@ -51,15 +78,15 @@ test_app_stream! {
name => withdraw_relay_single_log_authority_not_responsible_no_relay,
database => Database::default(),
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 1;
txs => Transactions::default(),
@ -68,227 +95,119 @@ test_app_stream! {
home_transport => [],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000000","topics":["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
req => json!([{
"address": ["0x0000000000000000000000000000000000000000"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x1005",
"topics": [[COLLECTED_SIGNATURES_TOPIC], null, null, null]
}]),
res => json!([{
"address": "0x0000000000000000000000000000000000000000",
"topics": [COLLECTED_SIGNATURES_TOPIC],
"data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0",
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
}]);
]
}
// 2 signatures required. relay polled twice.
// single CollectedSignatures log present. message value covers relay cost.
// single CollectedSignatures log present.
// message gets relayed.
test_app_stream! {
name => withdraw_relay_single_log_sufficient_value_relay,
database => Database::default(),
home =>
account => "0x0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0xaff3454fce5edbc8cca8697c15331677e6ebcccc",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
],
signatures => 2;
txs => Transactions::default(),
init => |app, db| create_withdraw_relay(app, db).take(1),
expected => vec![0x1005],
home_transport => [
// call to `isValueInMessageLargeEnoughToCoverWithdrawCost`
"eth_call" =>
req => r#"[{"data":"0x6498d59000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
// respond with `true`
res => r#""0x0000000000000000000000000000000000000000000000000000000000000001""#;
// `withdraw`
"eth_sendTransaction" =>
req => r#"[{"data":"0x9ce318f6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000","from":"0x0000000000000000000000000000000000000001","gas":"0x0","gasPrice":"0x0","to":"0x0000000000000000000000000000000000000000"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000000","topics":["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
// call to `message`
"eth_call" =>
req => r#"[{"data":"0x490a32c600000000000000000000000000000000000000000000000000000000000000f0","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333""#;
// calls to `signature`
"eth_call" =>
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000000","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111""#;
"eth_call" =>
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222""#;
]
}
// 2 signatures required. relay polled twice.
// single CollectedSignatures log present. message value doesn't cover cost.
// message is ignored.
test_app_stream! {
name => withdraw_relay_single_log_insufficient_value_no_relay,
database => Database::default(),
home =>
account => "0x0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0xaff3454fce5edbc8cca8697c15331677e6ebcccc",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
],
signatures => 2;
txs => Transactions::default(),
init => |app, db| create_withdraw_relay(app, db).take(1),
expected => vec![0x1005],
home_transport => [
// call to `isValueInMessageLargeEnoughToCoverWithdrawCost`
"eth_call" =>
req => r#"[{"data":"0x6498d59000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
// respond with `false`
res => r#""0x0000000000000000000000000000000000000000000000000000000000000000""#;
// no `withdraw`
],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000000","topics":["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
// call to `message`
"eth_call" =>
req => r#"[{"data":"0x490a32c600000000000000000000000000000000000000000000000000000000000000f0","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333""#;
// calls to `signature`
"eth_call" =>
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000000","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111""#;
"eth_call" =>
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222""#;
]
}
// like `withdraw_relay_single_log_sufficient_value_relay`
// but with explicit gas
test_app_stream! {
name => withdraw_relay_explicit_gas,
database => Database::default(),
home =>
account => "0x0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0xaff3454fce5edbc8cca8697c15331677e6ebcccc",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
],
signatures => 2;
txs => Transactions {
withdraw_relay: TransactionConfig {
gas: 0x10,
gas_price: 0x20,
},
..Default::default()
},
init => |app, db| create_withdraw_relay(app, db).take(1),
expected => vec![0x1005],
home_transport => [
// call to `isValueInMessageLargeEnoughToCoverWithdrawCost`
"eth_call" =>
req => r#"[{"data":"0x6498d59000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
// true
res => r#""0x0000000000000000000000000000000000000000000000000000000000000001""#;
// `withdraw`
"eth_sendTransaction" =>
req => r#"[{"data":"0x9ce318f6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000","from":"0x0000000000000000000000000000000000000001","gas":"0x10","gasPrice":"0x20","to":"0x0000000000000000000000000000000000000000"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
"eth_getLogs" =>
req => r#"[{"address":["0x0000000000000000000000000000000000000000"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],null,null,null]}]"#,
res => r#"[{"address":"0x0000000000000000000000000000000000000000","topics":["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
// call to `message`
"eth_call" =>
req => r#"[{"data":"0x490a32c600000000000000000000000000000000000000000000000000000000000000f0","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333""#;
// calls to `signature`
"eth_call" =>
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000000","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111""#;
"eth_call" =>
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000001","to":"0x0000000000000000000000000000000000000000"},"latest"]"#,
res => r#""0x2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222""#;
]
}
// like `withdraw_relay_single_log_sufficient_value_relay`
// but with explicit contract addresses
test_app_stream! {
name => withdraw_relay_single_explicit_contract_addresses,
database => Database {
home_contract_address: "0x00000000000000000000000000000000000000dd".parse().unwrap(),
foreign_contract_address: "0x00000000000000000000000000000000000000ee".parse().unwrap(),
home_contract_address: "00000000000000000000000000000000000000dd".into(),
foreign_contract_address: "00000000000000000000000000000000000000ee".into(),
..Default::default()
},
home =>
account => "0x0000000000000000000000000000000000000001",
account => "0000000000000000000000000000000000000001",
confirmations => 12;
foreign =>
account => "0xaff3454fce5edbc8cca8697c15331677e6ebcccc",
account => "aff3454fce5edbc8cca8697c15331677e6ebcccc",
confirmations => 12;
authorities =>
accounts => [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0000000000000000000000000000000000000001",
"0000000000000000000000000000000000000002",
],
signatures => 2;
txs => Transactions::default(),
init => |app, db| create_withdraw_relay(app, db).take(1),
expected => vec![0x1005],
home_transport => [
// call to `isValueInMessageLargeEnoughToCoverWithdrawCost`
"eth_call" =>
req => r#"[{"data":"0x6498d59000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000","to":"0x00000000000000000000000000000000000000dd"},"latest"]"#,
// true
res => r#""0x0000000000000000000000000000000000000000000000000000000000000001""#;
// `withdraw`
// `HomeBridge.withdraw`
"eth_sendTransaction" =>
req => r#"[{"data":"0x9ce318f6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000","from":"0x0000000000000000000000000000000000000001","gas":"0x0","gasPrice":"0x0","to":"0x00000000000000000000000000000000000000dd"}]"#,
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
req => json!([{
"data": format!("0x{}", contracts::home::HomeBridge::default()
.functions()
.withdraw()
.input(
vec![U256::from(1), U256::from(4)],
vec![H256::from(2), H256::from(5)],
vec![H256::from(3), H256::from(6)],
MessageToMainnet {
recipient: [1u8; 20].into(),
value: 10000.into(),
sidenet_transaction_hash: "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into(),
mainnet_gas_price: 1000.into(),
}.to_bytes()
).to_hex()),
"from": "0x0000000000000000000000000000000000000001",
"gas": "0x0",
"gasPrice": "0x3e8",
"to": "0x00000000000000000000000000000000000000dd"
}]),
res => json!("0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b");
],
foreign_transport => [
"eth_blockNumber" =>
req => r#"[]"#,
res => r#""0x1011""#;
req => json!([]),
res => json!("0x1011");
"eth_getLogs" =>
req => r#"[{"address":["0x00000000000000000000000000000000000000ee"],"fromBlock":"0x1","limit":null,"toBlock":"0x1005","topics":[["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],null,null,null]}]"#,
res => r#"[{"address":"0x00000000000000000000000000000000000000ee","topics":["0xeb043d149eedb81369bec43d4c3a3a53087debc88d2525f13bfaa3eecda28b5c"],"data":"0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0","type":"","transactionHash":"0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"}]"#;
req => json!([{
"address": ["0x00000000000000000000000000000000000000ee"],
"fromBlock": "0x1",
"limit": null,
"toBlock": "0x1005",
"topics": [[COLLECTED_SIGNATURES_TOPIC], null, null, null]
}]),
res => json!([{
"address": "0x00000000000000000000000000000000000000ee",
"topics": [COLLECTED_SIGNATURES_TOPIC],
"data": "0x000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebcccc00000000000000000000000000000000000000000000000000000000000000f0",
"type": "",
"transactionHash": "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364"
}]);
// call to `message`
"eth_call" =>
req => r#"[{"data":"0x490a32c600000000000000000000000000000000000000000000000000000000000000f0","to":"0x00000000000000000000000000000000000000ee"},"latest"]"#,
res => r#""0x333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333""#;
req => json!([{
"data": "0x490a32c600000000000000000000000000000000000000000000000000000000000000f0",
"to": "0x00000000000000000000000000000000000000ee"
}, "latest"]),
res => json!(format!("0x{}", MessageToMainnet {
recipient: [1u8; 20].into(),
value: 10000.into(),
sidenet_transaction_hash: "0x884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a9424364".into(),
mainnet_gas_price: 1000.into(),
}.to_payload().to_hex()));
// calls to `signature`
"eth_call" =>
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000000","to":"0x00000000000000000000000000000000000000ee"},"latest"]"#,
res => r#""0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111""#;
req => json!([{
"data": "0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000000",
"to": "0x00000000000000000000000000000000000000ee"
},"latest"]),
res => json!(format!("0x{}", Signature { v: 1, r: 2.into(), s: 3.into() }.to_payload().to_hex()));
"eth_call" =>
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000001","to":"0x00000000000000000000000000000000000000ee"},"latest"]"#,
res => r#""0x2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222""#;
req => json!([{
"data": "0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000001",
"to": "0x00000000000000000000000000000000000000ee"
},"latest"]),
res => json!(format!("0x{}", Signature { v: 4, r: 5.into(), s: 6.into() }.to_payload().to_hex()));
]
}

View File

@ -0,0 +1 @@
{"err":null,"data":{"statusCode":200,"headers":{"access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","access-control-allow-origin":"*","access-control-allow-methods":"*","content-type":"application/json","date":"Fri, 26 Jan 2018 09:06:17 GMT","connection":"close","transfer-encoding":"chunked"},"text":"{\"id\":723,\"jsonrpc\":\"2.0\",\"result\":\"0x00000000000000056bc75e2d63100000\"}"}}

View File

View File

@ -0,0 +1,267 @@
var ForeignBridge = artifacts.require("ForeignBridge");
var helpers = require("./helpers/helpers");
contract('ForeignBridge', function(accounts) {
it("totalSupply", function() {
var contract;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var owner = accounts[2];
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
var value = web3.toWei(3, "ether");
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
contract = instance;
return contract.totalSupply();
}).then(function(result) {
assert.equal(0, result, "initial supply should be 0");
return contract.deposit(owner, value, hash, {from: authorities[0]});
}).then(function(result) {
return contract.totalSupply();
}).then(function(result) {
console.log(result);
assert(result.equals(value), "deposit should increase supply");
var homeGasPrice = 1000;
return contract.transferHomeViaRelay(owner, value, homeGasPrice, {from: owner});
}).then(function() {
return contract.totalSupply();
}).then(function(result) {
assert.equal(0, result, "home transfer should decrease supply");
})
})
it("should be able to approve others to spend tokens in their name", function() {
var contract;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var owner = accounts[2];
var spender = accounts[3];
var receiver = accounts[4];
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
contract = instance;
// deposit something so we can transfer it
return contract.deposit(owner, web3.toWei(3, "ether"), hash, {from: authorities[0]});
}).then(function(result) {
return contract.allowance(owner, spender);
}).then(function(result) {
assert.equal(0, result, "initial allowance should be 0");
return contract.transferFrom(owner, receiver, web3.toWei(1, "ether"), {from: spender})
.then(function() {
assert(false, "transfer without allowance should fail");
}, helpers.ignoreExpectedError)
}).then(function() {
// transfer 0 without allowance should work
return contract.transferFrom(owner, receiver, 0, {from: spender});
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
assert.equal(owner, result.logs[0].args.from);
assert.equal(receiver, result.logs[0].args.to);
assert.equal(0, result.logs[0].args.tokens);
// transfer should work
return contract.approve(spender, web3.toWei(4, "ether"), {from: owner});
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Approval", result.logs[0].event, "Event name should be Approval");
assert.equal(owner, result.logs[0].args.tokenOwner);
assert.equal(spender, result.logs[0].args.spender);
assert.equal(web3.toWei(4, "ether"), result.logs[0].args.tokens);
return contract.allowance(owner, spender);
}).then(function(result) {
assert.equal(web3.toWei(4, "ether"), result, "approval should set allowance");
return contract.transferFrom(owner, receiver, web3.toWei(4, "ether"), {from: spender})
.then(function() {
assert(false, "transferring more than balance should fail");
}, helpers.ignoreExpectedError)
}).then(function() {
return contract.approve(spender, web3.toWei(2, "ether"), {from: owner});
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Approval", result.logs[0].event, "Event name should be Approval");
assert.equal(owner, result.logs[0].args.tokenOwner);
assert.equal(spender, result.logs[0].args.spender);
assert.equal(web3.toWei(2, "ether"), result.logs[0].args.tokens);
return contract.allowance(owner, spender);
}).then(function(result) {
assert.equal(web3.toWei(2, "ether"), result, "approval should update allowance");
return contract.transferFrom(owner, receiver, web3.toWei(2, "ether") + 2, {from: spender})
.then(function() {
assert(false, "transferring more than allowance should fail");
}, helpers.ignoreExpectedError)
}).then(function() {
return contract.transferFrom(owner, receiver, web3.toWei(2, "ether"), {from: spender});
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
assert.equal(owner, result.logs[0].args.from);
assert.equal(receiver, result.logs[0].args.to);
assert.equal(web3.toWei(2, "ether"), result.logs[0].args.tokens);
return contract.balanceOf(owner);
}).then(function(result) {
assert.equal(web3.toWei(1, "ether"), result, "transferring should reduce owners balance");
return contract.balanceOf(receiver);
}).then(function(result) {
assert.equal(web3.toWei(2, "ether"), result, "transferring should increase receivers balance");
return contract.balanceOf(spender);
}).then(function(result) {
assert.equal(0, result, "transferring should not modify spenders balance");
return contract.allowance(owner, spender);
}).then(function(result) {
assert.equal(0, result, "transferring whole allowance should set allowance to 0");
})
})
it("should allow user to transfer value locally", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var userAccount2 = accounts[3];
var user1InitialValue = web3.toWei(3, "ether");
var transferedValue = web3.toWei(1, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
// top up balance so we can transfer
return meta.deposit(userAccount, user1InitialValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transfer(userAccount2, transferedValue, { from: userAccount });
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
assert.equal(userAccount, result.logs[0].args.from, "Event from should be transaction sender");
assert.equal(userAccount2, result.logs[0].args.to, "Event from should be transaction recipient");
assert.equal(transferedValue, result.logs[0].args.tokens, "Event tokens should match transaction value");
return Promise.all([
meta.balances.call(userAccount),
meta.balances.call(userAccount2)
])
}).then(function(result) {
assert.equal(web3.toWei(2, "ether"), result[0]);
assert.equal(transferedValue, result[1]);
})
})
it("should not allow user to transfer value they don't have", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var userValue = web3.toWei(3, "ether");
var transferedValue = web3.toWei(4, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, userValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transfer(recipientAccount, transferedValue, { from: userAccount })
.then(function() {
assert(false, "transfer should fail");
}, helpers.ignoreExpectedError)
})
})
it("should allow transfer of 0 value according to ERC20", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var userValue = web3.toWei(3, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, userValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transfer(recipientAccount, 0, { from: userAccount });
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
assert.equal(userAccount, result.logs[0].args.from, "Event from should be transaction sender");
assert.equal(recipientAccount, result.logs[0].args.to, "Event from should be transaction recipient");
assert.equal(0, result.logs[0].args.tokens, "Event tokens should match transaction value");
return Promise.all([
meta.balances.call(userAccount),
meta.balances.call(recipientAccount)
])
}).then(function(result) {
assert.equal(userValue, result[0]);
assert.equal(0, result[1]);
})
})
it("transfer that results in overflow should fail", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var maxValue = web3.toWei("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "wei");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(recipientAccount, maxValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.deposit(userAccount, 1, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transfer(recipientAccount, 1, { from: userAccount })
.then(function() {
assert(false, "transfer should fail");
}, helpers.ignoreExpectedError)
})
})
it("transferFrom that results in overflow should fail", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var spenderAccount = accounts[3];
var recipientAccount = accounts[4];
var maxValue = web3.toWei("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "wei");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(recipientAccount, maxValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.deposit(userAccount, 1, hash, { from: authorities[0] });
}).then(function(result) {
return meta.approve(spenderAccount, 1, {from: userAccount});
}).then(function(result) {
return meta.transferFrom(userAccount, recipientAccount, 1, { from: spenderAccount })
.then(function() {
assert(false, "transfer should fail");
}, helpers.ignoreExpectedError)
})
})
})

View File

@ -5,9 +5,10 @@ contract('ForeignBridge', function(accounts) {
it("should deploy contract", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.requiredSignatures.call();
}).then(function(result) {
@ -20,38 +21,44 @@ contract('ForeignBridge', function(accounts) {
it("should fail to deploy contract with not enough required signatures", function() {
var authorities = [accounts[0], accounts[1]];
return ForeignBridge.new(0, authorities).then(function(_) {
assert(false, "Contract should fail to deploy");
}, function(err) {
// do nothing
})
return ForeignBridge.new(0, authorities, 0)
.then(function() {
assert(false, "Contract should fail to deploy");
}, helpers.ignoreExpectedError)
})
it("should fail to deploy contract with to many signatures", function() {
var authorities = [accounts[0], accounts[1]];
return ForeignBridge.new(3, authorities).then(function(_) {
assert(false, "Contract should fail to deploy");
}, function(err) {
// do nothing
})
return ForeignBridge.new(3, authorities, 0)
.then(function() {
assert(false, "Contract should fail to deploy");
}, helpers.ignoreExpectedError)
})
it("should allow a single authority to confirm a deposit", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var value = web3.toWei(1, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Deposit", result.logs[0].event, "Event name should be Deposit");
assert.equal(userAccount, result.logs[0].args.recipient, "Event recipient should be transaction sender");
assert.equal(value, result.logs[0].args.value, "Event value should match deposited ether");
assert.equal(2, result.logs.length)
assert.equal("Transfer", result.logs[0].event);
assert.equal("0x0000000000000000000000000000000000000000", result.logs[0].args.from);
assert.equal(userAccount, result.logs[0].args.to);
assert.equal(value, result.logs[0].args.tokens);
assert.equal("Deposit", result.logs[1].event);
assert.equal(userAccount, result.logs[1].args.recipient);
assert.equal(value, result.logs[1].args.value);
return meta.balances.call(userAccount);
}).then(function(result) {
assert.equal(value, result, "Contract balance should change");
@ -61,12 +68,13 @@ contract('ForeignBridge', function(accounts) {
it("should require 2 authorities to confirm deposit", function() {
var meta;
var requiredSignatures = 2;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var value = web3.toWei(1, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
}).then(function(result) {
@ -76,10 +84,16 @@ contract('ForeignBridge', function(accounts) {
assert.equal(web3.toWei(0, "ether"), result, "Contract balance should not change yet");
return meta.deposit(userAccount, value, hash, { from: authorities[1] });
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Deposit", result.logs[0].event, "Event name should be Deposit");
assert.equal(userAccount, result.logs[0].args.recipient, "Event recipient should be transaction sender");
assert.equal(value, result.logs[0].args.value, "Event value should match deposited ether");
assert.equal(2, result.logs.length)
assert.equal("Transfer", result.logs[0].event);
assert.equal("0x0000000000000000000000000000000000000000", result.logs[0].args.from);
assert.equal(userAccount, result.logs[0].args.to);
assert.equal(value, result.logs[0].args.tokens);
assert.equal("Deposit", result.logs[1].event, "Event name should be Deposit");
assert.equal(userAccount, result.logs[1].args.recipient, "Event recipient should be transaction sender");
assert.equal(value, result.logs[1].args.value, "Event value should match deposited ether");
return meta.balances.call(userAccount);
}).then(function(result) {
assert.equal(value, result, "Contract balance should change");
@ -89,49 +103,52 @@ contract('ForeignBridge', function(accounts) {
it("should not be possible to do same deposit twice for same authority", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var value = web3.toWei(1, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
}).then(function(_) {
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
}).then(function(result) {
assert(false, "doing same deposit twice from same authority should fail");
}, function(err) {
return meta.deposit(userAccount, value, hash, { from: authorities[0] })
.then(function() {
assert(false, "doing same deposit twice from same authority should fail");
}, helpers.ignoreExpectedError)
})
})
it("should not allow non-authorities to execute deposit", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var value = web3.toWei(1, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, value, hash, { from: userAccount });
}).then(function(result) {
assert(false, "should fail");
}, function(err) {
return meta.deposit(userAccount, value, hash, { from: userAccount })
.then(function() {
assert(false, "should fail");
}, helpers.ignoreExpectedError)
})
})
it("should ignore misbehaving authority when confirming deposit", function() {
var meta;
var requiredSignatures = 2;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1], accounts[2]];
var userAccount = accounts[3];
var invalidValue = web3.toWei(2, "ether");
var value = web3.toWei(1, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
}).then(function(result) {
@ -141,146 +158,148 @@ contract('ForeignBridge', function(accounts) {
assert.equal(0, result.logs.length, "Misbehaving authority should be ignored");
return meta.deposit(userAccount, value, hash, { from: authorities[2] })
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Deposit", result.logs[0].event, "Event name should be Deposit");
assert.equal(userAccount, result.logs[0].args.recipient, "Event recipient should be transaction sender");
assert.equal(value, result.logs[0].args.value, "Event value should match transaction value");
assert.equal(2, result.logs.length)
assert.equal("Transfer", result.logs[0].event);
assert.equal("0x0000000000000000000000000000000000000000", result.logs[0].args.from);
assert.equal(userAccount, result.logs[0].args.to);
assert.equal(value, result.logs[0].args.tokens);
assert.equal("Deposit", result.logs[1].event, "Event name should be Deposit");
assert.equal(userAccount, result.logs[1].args.recipient, "Event recipient should be transaction sender");
assert.equal(value, result.logs[1].args.value, "Event value should match transaction value");
return meta.balances.call(userAccount);
}).then(function(result) {
assert.equal(value, result, "Contract balance should change");
})
})
it("should allow user to transfer value locally", function() {
var meta;
var requiredSignatures = 1;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var userAccount2 = accounts[3];
var user1InitialValue = web3.toWei(3, "ether");
var transferedValue = web3.toWei(1, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
meta = instance;
// top up balance so we can transfer
return meta.deposit(userAccount, user1InitialValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transferLocal(userAccount2, transferedValue, { from: userAccount });
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
assert.equal(userAccount, result.logs[0].args.from, "Event from should be transaction sender");
assert.equal(userAccount2, result.logs[0].args.to, "Event from should be transaction recipient");
assert.equal(transferedValue, result.logs[0].args.value, "Event value should match transaction value");
return Promise.all([
meta.balances.call(userAccount),
meta.balances.call(userAccount2)
])
}).then(function(result) {
assert.equal(web3.toWei(2, "ether"), result[0]);
assert.equal(transferedValue, result[1]);
})
})
it("should not allow user to transfer value they don't have either locally or to home", function() {
it("should not allow user to transfer value they don't have to home", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var homeGasPrice = web3.toBigNumber(10000);
var recipientAccount = accounts[3];
var userValue = web3.toWei(3, "ether");
var transferedValue = web3.toWei(4, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, userValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transferLocal(recipientAccount, transferedValue, { from: userAccount });
}).then(function(result) {
assert(false, "transferLocal should fail");
}, function(err) {
return meta.transferHomeViaRelay(recipientAccount, transferedValue, { from: userAccount });
}).then(function(result) {
assert(false, "transferHomeViaRelay should fail");
}, function(err) {
return meta.transferHomeViaRelay(recipientAccount, transferedValue, homeGasPrice, { from: userAccount })
.then(function() {
assert(false, "transferHomeViaRelay should fail");
}, helpers.ignoreExpectedError)
})
})
it("should fail to transfer 0 value both locally and to home", function() {
it("should fail to transfer 0 value to home", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var homeGasPrice = web3.toBigNumber(10000);
var recipientAccount = accounts[3];
var userValue = web3.toWei(3, "ether");
var transferedValue = web3.toWei(0, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, userValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transferLocal(recipientAccount, transferedValue, { from: userAccount });
}).then(function(result) {
assert(false, "transferLocal should fail");
}, function(err) {
return meta.transferHomeViaRelay(recipientAccount, transferedValue, { from: userAccount });
}).then(function(result) {
assert(false, "transferHomeViaRelay should fail");
}, function(err) {
return meta.transferHomeViaRelay(recipientAccount, transferedValue, homeGasPrice, { from: userAccount })
.then(function() {
assert(false, "transferHomeViaRelay should fail");
}, helpers.ignoreExpectedError)
})
})
it("should fail to transfer with value overflow both locally and to home", function() {
it("should fail to transfer more than balance home", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var homeGasPrice = web3.toBigNumber(10000);
var recipientAccount = accounts[3];
var userValue = web3.toWei(3, "ether");
var transferedvalue = web3.toWei("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "wei");
var transferedValue = web3.toWei(4, "ether");
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, userValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transferLocal(recipientAccount, transferedValue, { from: userAccount });
}).then(function(result) {
assert(false, "transferLocal should fail");
}, function(err) {
return meta.transferHomeViaRelay(recipientAccount, transferedValue, { from: userAccount });
}).then(function(result) {
assert(false, "transferHomeViaRelay should fail");
}, function(err) {
return meta.transferHomeViaRelay(recipientAccount, transferedValue, homeGasPrice, { from: userAccount })
.then(function() {
assert(false, "transferHomeViaRelay should fail");
}, helpers.ignoreExpectedError)
})
})
it("should allow user to trigger withdraw", function() {
it("should fail to transfer home with value that gets entirely burned on gas", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = web3.toBigNumber(10000);
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var homeGasPrice = web3.toBigNumber(10000);
var recipientAccount = accounts[3];
var userValue = web3.toWei(3, "ether");
var transferedValue = estimatedGasCostOfWithdraw.times(homeGasPrice);
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return meta.deposit(userAccount, userValue, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transferHomeViaRelay(recipientAccount, transferedValue, homeGasPrice, { from: userAccount })
.then(function() {
assert(false, "transferHomeViaRelay should fail");
}, helpers.ignoreExpectedError)
})
})
it("should allow user to transfer home", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = web3.toBigNumber(10000);
var authorities = [accounts[0], accounts[1]];
var userAccount = accounts[2];
var userAccount2 = accounts[3];
var value = web3.toWei(3, "ether");
var value2 = web3.toWei(1, "ether");
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var transferedValue = estimatedGasCostOfWithdraw.times(homeGasPrice).plus(1);
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
// top up balance so we can transfer
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
}).then(function(result) {
return meta.transferHomeViaRelay(userAccount2, value2, { from: userAccount });
return meta.transferHomeViaRelay(userAccount2, transferedValue, homeGasPrice, { from: userAccount });
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("Withdraw", result.logs[0].event, "Event name should be Withdraw");
assert.equal(userAccount2, result.logs[0].args.recipient, "Event recipient should be equal to transaction recipient");
assert.equal(value2, result.logs[0].args.value, "Event value should match transaction value");
assert.equal(2, result.logs.length)
assert.equal("Transfer", result.logs[0].event);
assert.equal(userAccount, result.logs[0].args.from);
assert.equal("0x0000000000000000000000000000000000000000", result.logs[0].args.to);
assert(transferedValue.equals(result.logs[0].args.tokens));
assert.equal("Withdraw", result.logs[1].event, "Event name should be Withdraw");
assert.equal(userAccount2, result.logs[1].args.recipient, "Event recipient should be equal to transaction recipient");
assert(transferedValue.equals(result.logs[1].args.value));
assert(homeGasPrice.equals(result.logs[1].args.homeGasPrice));
return Promise.all([
meta.balances.call(userAccount),
meta.balances.call(userAccount2)
])
}).then(function(result) {
assert.equal(web3.toWei(2, "ether"), result[0]);
assert.equal(web3.toWei(0, "ether"), result[1]);
assert(web3.toBigNumber(value).minus(transferedValue).equals(result[0]));
assert(web3.toBigNumber(web3.toWei(0, "ether")).equals(result[1]));
})
})
@ -288,9 +307,13 @@ contract('ForeignBridge', function(accounts) {
var meta;
var signature;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
var recipientAccount = accounts[2];
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var message = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice);
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return helpers.sign(authorities[0], message);
}).then(function(result) {
@ -299,7 +322,7 @@ contract('ForeignBridge', function(accounts) {
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
assert.equal(authorities[0], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
assert.equal(authorities[0], result.logs[0].args.authorityResponsibleForRelay, "Event authority should be equal to transaction sender");
return Promise.all([
meta.signature.call(result.logs[0].args.messageHash, 0),
meta.message(result.logs[0].args.messageHash),
@ -313,9 +336,13 @@ contract('ForeignBridge', function(accounts) {
it("should successfully submit signature but not trigger CollectedSignatures event", function() {
var meta;
var requiredSignatures = 2;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
var recipientAccount = accounts[2];
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var message = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice);
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return helpers.sign(authorities[0], message);
}).then(function(result) {
@ -330,10 +357,14 @@ contract('ForeignBridge', function(accounts) {
var signatures_for_message = [];
var signatures_for_message2 = [];
var requiredSignatures = 2;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
var message2 = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
var recipientAccount = accounts[2];
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var message = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice);
var message2 = helpers.createMessage(recipientAccount, web3.toBigNumber(2000), transactionHash, homeGasPrice);
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return Promise.all([
helpers.sign(authorities[0], message),
@ -356,7 +387,7 @@ contract('ForeignBridge', function(accounts) {
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
assert.equal(authorities[0], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
assert.equal(authorities[0], result.logs[0].args.authorityResponsibleForRelay, "Event authority should be equal to transaction sender");
return Promise.all([
meta.signature.call(result.logs[0].args.messageHash, 0),
meta.signature.call(result.logs[0].args.messageHash, 1),
@ -370,7 +401,7 @@ contract('ForeignBridge', function(accounts) {
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
assert.equal(authorities[1], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
assert.equal(authorities[1], result.logs[0].args.authorityResponsibleForRelay, "Event authority should be equal to transaction sender");
return Promise.all([
meta.signature.call(result.logs[0].args.messageHash, 0),
meta.signature.call(result.logs[0].args.messageHash, 1),
@ -383,76 +414,92 @@ contract('ForeignBridge', function(accounts) {
})
})
it("should not be possible to submit to short message", function() {
it("should not be possible to submit message that is too short", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var message = "0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
var recipientAccount = accounts[2];
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var message = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice);
var truncatedMessage = message.substr(0, 84);
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return helpers.sign(authorities[0], message);
}).then(function(result) {
return meta.submitSignature(result, message, { from: authorities[0] });
}).then(function(result) {
assert(false, "submitSignature should fail");
}, function (err) {
// nothing
return helpers.sign(authorities[0], truncatedMessage);
}).then(function(signature) {
return meta.submitSignature(signature, truncatedMessage, { from: authorities[0] })
.then(function() {
assert(false, "submitSignature should fail for message that is too short");
}, helpers.ignoreExpectedError)
})
})
it("should not be possible to submit different message then the signed one", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
var message2 = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
var recipientAccount = accounts[2];
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var homeGasPrice2 = web3.toBigNumber(web3.toWei(2, "gwei"));
var message = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice);
var message2 = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice2);
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return helpers.sign(authorities[0], message);
}).then(function(result) {
return meta.submitSignature(result, message2, { from: authorities[0] });
}).then(function(result) {
assert(false, "submitSignature should fail");
}, function (err) {
// nothing
return meta.submitSignature(result, message2, { from: authorities[0] })
.then(function() {
assert(false, "submitSignature should fail");
}, helpers.ignoreExpectedError)
})
})
it("should not be possible to submit signature signed by different authority", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
var recipientAccount = accounts[2];
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var homeGasPrice2 = web3.toBigNumber(web3.toWei(2, "gwei"));
var message = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice);
var message2 = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice2);
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return helpers.sign(authorities[0], message);
}).then(function(result) {
return meta.submitSignature(result, message, { from: authorities[1] });
}).then(function(result) {
assert(false, "submitSignature should fail");
}, function (err) {
// nothing
return meta.submitSignature(result, message, { from: authorities[1] })
.then(function() {
assert(false, "submitSignature should fail");
}, helpers.ignoreExpectedError)
})
})
it("should not be possible to submit signature twice", function() {
var meta;
var requiredSignatures = 1;
var estimatedGasCostOfWithdraw = 0;
var authorities = [accounts[0], accounts[1]];
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
var recipientAccount = accounts[2];
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var message = helpers.createMessage(recipientAccount, web3.toBigNumber(1000), transactionHash, homeGasPrice);
var signature;
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
return ForeignBridge.new(requiredSignatures, authorities, estimatedGasCostOfWithdraw).then(function(instance) {
meta = instance;
return helpers.sign(authorities[0], message);
}).then(function(result) {
signature = result;
return meta.submitSignature(signature, message, { from: authorities[0] });
}).then(function(_) {
return meta.submitSignature(signature, message, { from: authorities[0] });
}).then(function(_) {
assert(false, "submitSignature should fail");
}, function (_) {
// nothing
return meta.submitSignature(signature, message, { from: authorities[0] })
.then(function() {
assert(false, "submitSignature should fail");
}, helpers.ignoreExpectedError)
})
})
})

View File

@ -3,14 +3,10 @@ var Helpers = artifacts.require("HelpersTest");
// testing helpers
var helpers = require("./helpers/helpers");
contract("Helpers", function() {
contract("Helpers", function(accounts) {
it("`addressArrayContains` should function correctly", function() {
var addresses = [
"0xd4f04f18d253f831e5b9bcfde7f20450562e03da",
"0x46ee1abbcd7215364174f84c3cbc4770d45966e9",
"0x5ef98710ff315ded660fe757bf7a861114287c1e",
];
var otherAddress = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
var addresses = accounts.slice(0, 3);
var otherAddress = accounts[3];
var library;
return Helpers.new().then(function(instance) {
library = instance;
@ -84,4 +80,205 @@ contract("Helpers", function() {
assert.equal(result, "131242344353464564564574574567456");
})
})
it("`hasEnoughValidSignatures` should pass for 1 required signature", function() {
var library;
var signature;
var requiredSignatures = 1;
var authorities = [accounts[0], accounts[1]];
var recipientAccount = accounts[2];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return Helpers.new().then(function(instance) {
library = instance;
}).then(function(result) {
return helpers.sign(authorities[0], message);
}).then(function(result) {
signature = result;
var vrs = helpers.signatureToVRS(signature);
return library.hasEnoughValidSignatures.call(
message,
[vrs.v],
[vrs.r],
[vrs.s],
authorities,
requiredSignatures
).then(function(result) {
assert(result, "should return true");
})
})
})
it("`verifySignatures` should pass for multiple signatures", function() {
var library;
var signatures = [];
var requiredSignatures = 3;
var authorities = [accounts[0], accounts[1], accounts[2]];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return Helpers.new().then(function(instance) {
library = instance;
}).then(function(result) {
return helpers.sign(authorities[0], message);
}).then(function(result) {
signatures[0] = result;
return helpers.sign(authorities[1], message);
}).then(function(result) {
signatures[1] = result;
return helpers.sign(authorities[2], message);
}).then(function(result) {
signatures[2] = result;
var vrs = [];
vrs[0] = helpers.signatureToVRS(signatures[0]);
vrs[1] = helpers.signatureToVRS(signatures[1]);
vrs[2] = helpers.signatureToVRS(signatures[2]);
return library.hasEnoughValidSignatures.call(
message,
[vrs[0].v, vrs[1].v, vrs[2].v],
[vrs[0].r, vrs[1].r, vrs[2].r],
[vrs[0].s, vrs[1].s, vrs[2].s],
authorities,
requiredSignatures
).then(function(result) {
assert(result, "should return true");
})
})
})
it("`verifySignatures` should fail for signature for other message", function() {
var library;
var signature;
var requiredSignatures = 1;
var authorities = [accounts[0], accounts[1]];
var recipientAccount = accounts[2];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var homeGasPrice = web3.toBigNumber(10000);
var homeGasPrice2 = web3.toBigNumber(100);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
var message2 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice2);
return Helpers.new().then(function(instance) {
library = instance;
}).then(function(result) {
return helpers.sign(authorities[0], message);
}).then(function(result) {
signature = result;
var vrs = helpers.signatureToVRS(signature);
return library.hasEnoughValidSignatures.call(
message2,
[vrs.v],
[vrs.r],
[vrs.s],
authorities,
requiredSignatures
).then(function(result) {
assert.equal(result, false, "should return false");
})
})
})
it("`verifySignatures` should fail if signer not in addresses", function() {
var library;
var signature;
var requiredSignatures = 1;
var authorities = [accounts[0], accounts[1]];
var recipientAccount = accounts[2];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return Helpers.new().then(function(instance) {
library = instance;
}).then(function(result) {
return helpers.sign(accounts[3], message);
}).then(function(result) {
signature = result;
var vrs = helpers.signatureToVRS(signature);
return library.hasEnoughValidSignatures.call(
message,
[vrs.v],
[vrs.r],
[vrs.s],
authorities,
requiredSignatures
).then(function(result) {
assert.equal(result, false, "should return false");
})
})
})
it("`verifySignatures` should fail for not enough signatures", function() {
var library;
var signature;
var requiredSignatures = 2;
var authorities = [accounts[0], accounts[1]];
var recipientAccount = accounts[2];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return Helpers.new().then(function(instance) {
library = instance;
}).then(function(result) {
return helpers.sign(authorities[0], message);
}).then(function(result) {
signature = result;
var vrs = helpers.signatureToVRS(signature);
return library.hasEnoughValidSignatures.call(
message,
[vrs.v],
[vrs.r],
[vrs.s],
authorities,
requiredSignatures
).then(function(result) {
assert.equal(result, false, "should return false");
})
})
})
it("`verifySignatures` should fail for duplicated signature", function() {
var library;
var signature;
var requiredSignatures = 2;
var authorities = [accounts[0], accounts[1]];
var recipientAccount = accounts[2];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return Helpers.new().then(function(instance) {
library = instance;
}).then(function(result) {
return helpers.sign(authorities[0], message);
}).then(function(result) {
signature = result;
var vrs = helpers.signatureToVRS(signature);
return library.hasEnoughValidSignatures.call(
message,
[vrs.v, vrs.v],
[vrs.r, vrs.r],
[vrs.s, vrs.r],
authorities,
requiredSignatures
).then(function(result) {
assert.equal(result, false, "should return false");
})
})
})
})

View File

@ -76,18 +76,23 @@ module.exports.getBalances = getBalances;
// returns hex string of the bytes of the message
// composed from `recipient`, `value` and `transactionHash`
// that is relayed from `foreign` to `home` on withdraw
function createMessage(recipient, value, transactionHash) {
function createMessage(recipient, value, transactionHash, homeGasPrice) {
web3._extend.utils.isBigNumber(value);
recipient = strip0x(recipient);
assert.equal(recipient.length, 20 * 2);
var value = strip0x(bigNumberToPaddedBytes32(value));
assert.equal(value.length, 64);
transactionHash = strip0x(transactionHash);
assert.equal(transactionHash.length, 32 * 2);
var value = strip0x(bigNumberToPaddedBytes32(value));
assert.equal(value.length, 64);
var message = "0x" + recipient + value + transactionHash;
var expectedMessageLength = (20 + 32 + 32) * 2 + 2;
web3._extend.utils.isBigNumber(homeGasPrice);
homeGasPrice = strip0x(bigNumberToPaddedBytes32(homeGasPrice));
assert.equal(homeGasPrice.length, 64);
var message = "0x" + recipient + value + transactionHash + homeGasPrice;
var expectedMessageLength = (20 + 32 + 32 + 32) * 2 + 2;
assert.equal(message.length, expectedMessageLength);
return message;
}
@ -102,3 +107,8 @@ function range(start, end) {
return result;
}
module.exports.range = range;
// just used to signal/document that we're explicitely ignoring/expecting an error
function ignoreExpectedError() {
}
module.exports.ignoreExpectedError = ignoreExpectedError;

View File

@ -25,20 +25,18 @@ contract('HomeBridge', function(accounts) {
it("should fail to deploy contract with not enough required signatures", function() {
var authorities = [accounts[0], accounts[1]];
return HomeBridge.new(0, authorities).then(function(_) {
assert(false, "Contract should fail to deploy");
}, function(err) {
// do nothing
})
return HomeBridge.new(0, authorities)
.then(function() {
assert(false, "Contract should fail to deploy");
}, helpers.ignoreExpectedError)
})
it("should fail to deploy contract with too many signatures", function() {
var authorities = [accounts[0], accounts[1]];
return HomeBridge.new(3, authorities, 0).then(function(_) {
assert(false, "Contract should fail to deploy");
}, function(err) {
// do nothing
})
return HomeBridge.new(3, authorities, 0)
.then(function() {
assert(false, "Contract should fail to deploy");
}, helpers.ignoreExpectedError)
})
it("should create deposit event", function() {
@ -67,54 +65,6 @@ contract('HomeBridge', function(accounts) {
})
})
it("isMessageValueSufficientToCoverRelay should work correctly", function() {
var homeBridge;
var gasPrice;
var requiredSignatures = 1;
var authorities = [accounts[0], accounts[1]];
var estimatedGasCostOfWithdraw = web3.toBigNumber(100000);
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var estimatedWeiCostOfWithdraw;
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
return HomeBridge.new(
requiredSignatures,
authorities,
estimatedGasCostOfWithdraw
).then(function(instance) {
homeBridge = instance;
// do a test transaction so we can figure out the gasPrice
return homeBridge.sendTransaction({
value: 1,
from: userAccount
})
}).then(function(result) {
return web3.eth.getTransaction(result.tx);
}).then(function(tx) {
// getting the gas price dynamically instead of hardcoding it
// (which would require less code)
// is required because solidity-coverage sets it to 1
// and the usual default is 100000000000
gasPrice = tx.gasPrice;
estimatedWeiCostOfWithdraw = gasPrice.times(estimatedGasCostOfWithdraw);
return homeBridge.getWithdrawRelayCost();
}).then(function(result) {
assert(result.equals(estimatedWeiCostOfWithdraw), "getWithdrawRelayCost should return correct value");
var message = helpers.createMessage(recipientAccount, estimatedWeiCostOfWithdraw, transactionHash);
return homeBridge.isMessageValueSufficientToCoverRelay(message);
}).then(function(result) {
assert.equal(result, false, "exactly estimatedWeiCostOfWithdraw is not sufficient value");
var message = helpers.createMessage(recipientAccount, estimatedWeiCostOfWithdraw.plus(1), transactionHash);
return homeBridge.isMessageValueSufficientToCoverRelay(message);
}).then(function(result) {
assert.equal(result, true, "estimatedWeiCostOfWithdraw + 1 is sufficient value");
})
})
it("should allow correct withdraw without recipient paying for gas", function() {
var homeBridge;
var signature;
@ -124,7 +74,8 @@ contract('HomeBridge', function(accounts) {
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var homeGasPrice = web3.toBigNumber(0);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return HomeBridge.new(
requiredSignatures,
@ -149,7 +100,7 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message,
{from: authorities[0]}
{from: userAccount, gasPrice: homeGasPrice}
);
}).then(function(result) {
console.log("estimated gas cost of HomeBridge.withdraw =", result);
@ -160,8 +111,7 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message,
// anyone can call withdraw (provided they have the message and required signatures)
{from: userAccount}
{from: userAccount, gasPrice: homeGasPrice}
);
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
@ -186,7 +136,8 @@ contract('HomeBridge', function(accounts) {
var recipientAccount = accounts[3];
var chargerAccount = accounts[4];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return HomeBridge.new(
requiredSignatures,
@ -215,8 +166,7 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message,
// anyone can call withdraw (provided they have the message and required signatures)
{ from: relayerAccount }
{ from: relayerAccount, gasPrice: homeGasPrice }
);
}).then(function(result) {
transactionResult = result;
@ -251,6 +201,90 @@ contract('HomeBridge', function(accounts) {
})
})
it("withdraw should fail if gas price != requested gas price", function() {
var homeBridge;
var signature;
var requiredSignatures = 1;
var authorities = [accounts[0], accounts[1]];
var estimatedGasCostOfWithdraw = 0;
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var requestedGasPrice = web3.toBigNumber(100);
var usedGasPrice = web3.toBigNumber(1000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", requestedGasPrice);
return HomeBridge.new(
requiredSignatures,
authorities,
estimatedGasCostOfWithdraw
).then(function(instance) {
homeBridge = instance;
// "charge" HomeBridge so we can withdraw later
return homeBridge.sendTransaction({
value: value,
from: userAccount
})
}).then(function(result) {
return helpers.sign(authorities[0], message);
}).then(function(result) {
signature = result;
var vrs = helpers.signatureToVRS(signature);
return homeBridge.withdraw(
[vrs.v],
[vrs.r],
[vrs.s],
message,
{from: userAccount, gasPrice: usedGasPrice}
).then(function() {
assert(false, "withdraw should fail if used gas price != requested gas price");
}, helpers.ignoreExpectedError)
})
})
it("withdraw should succeed if gas price != requested gas price but sender is receiver", function() {
var homeBridge;
var signature;
var requiredSignatures = 1;
var authorities = [accounts[0], accounts[1]];
var estimatedGasCostOfWithdraw = 0;
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var requestedGasPrice = web3.toBigNumber(100);
var usedGasPrice = web3.toBigNumber(1000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", requestedGasPrice);
return HomeBridge.new(
requiredSignatures,
authorities,
estimatedGasCostOfWithdraw
).then(function(instance) {
homeBridge = instance;
// "charge" HomeBridge so we can withdraw later
return homeBridge.sendTransaction({
value: value,
from: userAccount
})
}).then(function(result) {
return helpers.sign(authorities[0], message);
}).then(function(result) {
signature = result;
var vrs = helpers.signatureToVRS(signature);
return homeBridge.withdraw(
[vrs.v],
[vrs.r],
[vrs.s],
message,
{from: recipientAccount, gasPrice: usedGasPrice}
)
})
})
it("should revert withdraw if value is insufficient to cover costs", function() {
var homeBridge;
var initialBalances;
@ -262,7 +296,8 @@ contract('HomeBridge', function(accounts) {
var recipientAccount = accounts[3];
var chargerAccount = accounts[4];
var value = estimatedGasCostOfWithdraw;
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return HomeBridge.new(
requiredSignatures,
@ -291,13 +326,10 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message,
// anyone can call withdraw (provided they have the message and required signatures)
{ from: relayerAccount }
);
}).then(function(result) {
assert(false, "withdraw if value <= estimatedGasCostOfWithdraw should fail");
}, function (err) {
// nothing
{ from: relayerAccount, gasPrice: homeGasPrice }
).then(function() {
assert(false, "withdraw if value <= estimatedGasCostOfWithdraw should fail");
}, helpers.ignoreExpectedError)
})
})
@ -309,8 +341,9 @@ contract('HomeBridge', function(accounts) {
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var message1 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var message2 = helpers.createMessage(recipientAccount, value, "0x038c79eb958a13aa71996bac27c628f33f227288bd27d5e157b97e55e08fd2b3");
var homeGasPrice = web3.toBigNumber(10000);
var message1 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
var message2 = helpers.createMessage(recipientAccount, value, "0x038c79eb958a13aa71996bac27c628f33f227288bd27d5e157b97e55e08fd2b3", homeGasPrice);
return HomeBridge.new(
requiredSignatures,
@ -332,7 +365,7 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message1,
{from: authorities[0]}
{from: authorities[0], gasPrice: homeGasPrice}
);
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
@ -348,7 +381,7 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message2,
{from: authorities[0]}
{from: authorities[0], gasPrice: homeGasPrice}
);
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
@ -366,8 +399,9 @@ contract('HomeBridge', function(accounts) {
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var message1 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var message2 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var homeGasPrice = web3.toBigNumber(10000);
var message1 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
var message2 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return HomeBridge.new(
requiredSignatures,
@ -389,7 +423,7 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message1,
{from: authorities[0]}
{from: authorities[0], gasPrice: homeGasPrice}
);
}).then(function(result) {
assert.equal(1, result.logs.length, "Exactly one event should be created");
@ -405,12 +439,10 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message2,
{from: authorities[0]}
);
}).then(function(result) {
assert(false, "should fail");
}, function (err) {
// nothing
{from: authorities[0], gasPrice: homeGasPrice}
).then(function() {
assert(false, "should fail");
}, helpers.ignoreExpectedError)
})
})
@ -423,7 +455,8 @@ contract('HomeBridge', function(accounts) {
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return HomeBridge.new(
requiredSignatures,
@ -440,12 +473,10 @@ contract('HomeBridge', function(accounts) {
[vrs.r],
[vrs.s],
message.substr(0, 83),
{from: authorities[0]}
);
}).then(function(result) {
assert(false, "should fail");
}, function (err) {
// nothing
{from: authorities[0], gasPrice: homeGasPrice}
).then(function() {
assert(false, "should fail");
}, helpers.ignoreExpectedError)
})
})
@ -458,7 +489,8 @@ contract('HomeBridge', function(accounts) {
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
// make message too short
message = message.substr(0, 83);
@ -486,11 +518,10 @@ contract('HomeBridge', function(accounts) {
[vrs.s],
message,
// anyone can call withdraw (provided they have the message and required signatures)
{from: userAccount}
);
}).then(function(result) {
assert(false, "withdraw should fail");
}, function(err) {
{from: userAccount, gasPrice: homeGasPrice}
).then(function() {
assert(false, "withdraw should fail");
}, helpers.ignoreExpectedError)
})
})
@ -503,7 +534,8 @@ contract('HomeBridge', function(accounts) {
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return HomeBridge.new(
requiredSignatures,
@ -529,11 +561,10 @@ contract('HomeBridge', function(accounts) {
[vrs.s],
message,
// anyone can call withdraw (provided they have the message and required signatures)
{from: userAccount}
);
}).then(function(result) {
assert(false, "should fail");
}, function(err) {
{from: userAccount, gasPrice: homeGasPrice}
).then(function() {
assert(false, "should fail");
}, helpers.ignoreExpectedError)
})
})
@ -546,7 +577,8 @@ contract('HomeBridge', function(accounts) {
var userAccount = accounts[2];
var recipientAccount = accounts[3];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
var homeGasPrice = web3.toBigNumber(10000);
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80", homeGasPrice);
return HomeBridge.new(
requiredSignatures,
@ -572,11 +604,10 @@ contract('HomeBridge', function(accounts) {
[vrs.s, vrs.s],
message,
// anyone can call withdraw (provided they have the message and required signatures)
{from: userAccount}
);
}).then(function(result) {
assert(false, "should fail");
}, function(err) {
{from: userAccount, gasPrice: homeGasPrice}
).then(function() {
assert(false, "should fail");
}, helpers.ignoreExpectedError)
})
})
})

View File

@ -1,11 +1,12 @@
var Message = artifacts.require("MessageTest");
var helpers = require("./helpers/helpers");
contract("Message", function() {
var recipientAccount = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
contract("Message", function(accounts) {
var recipientAccount = accounts[0];
var value = web3.toBigNumber(web3.toWei(1, "ether"));
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
var message = helpers.createMessage(recipientAccount, value, transactionHash);
var homeGasPrice = web3.toBigNumber(web3.toWei(3, "gwei"));
var message = helpers.createMessage(recipientAccount, value, transactionHash, homeGasPrice);
it("should extract value", function() {
return Message.new().then(function(instance) {
@ -30,4 +31,12 @@ contract("Message", function() {
assert.equal(result, transactionHash);
})
})
it("should extract homeGasPrice", function() {
return Message.new().then(function(instance) {
return instance.getHomeGasPrice.call(message)
}).then(function(result) {
assert(result.equals(homeGasPrice));
})
})
})

View File

@ -1,6 +1,7 @@
var MessageSigning = artifacts.require("MessageSigningTest");
var helpers = require("./helpers/helpers");
contract("MessageSigning", function() {
contract("MessageSigning", function(accounts) {
it("should recover address from signed message", function() {
var signature = "0xb585c41f3cceb2ff9b5c033f2edbefe93415bde365489c989bad8cef3b18e38148a13e100608a29735d709fe708926d37adcecfffb32b1d598727028a16df5db1b";
var message = "0xdeadbeaf";
@ -32,9 +33,9 @@ contract("MessageSigning", function() {
return MessageSigning.new().then(function(instance) {
return instance.recoverAddressFromSignedMessage.call(signature, message)
}).then(function(result) {
assert(false, "should fail because signature is too short");
}, function(err) {
})
.then(function() {
assert(false, "should fail because signature is too short");
}, helpers.ignoreExpectedError)
})
})
})