merge from master branch
This commit is contained in:
commit
ca64cf3b6a
|
@ -54,3 +54,5 @@ examples/db.toml
|
|||
jsTests/package-lock.json
|
||||
node_modules
|
||||
compiled_contracts
|
||||
|
||||
integration-tests/tmp
|
||||
|
|
21
.travis.yml
21
.travis.yml
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +1,2 @@
|
|||
[workspace]
|
||||
members = ["tests", "cli", "bridge"]
|
||||
members = ["tests", "cli", "bridge", "integration-tests"]
|
||||
|
|
153
README.md
153
README.md
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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` can’t relay cost.
|
||||
// Think of it as `value` getting burned entirely on the relay with no value left to pay out the recipient.
|
||||
require(isMessageValueSufficientToCoverRelay(message));
|
||||
|
||||
uint estimatedWeiCostOfWithdraw = getWithdrawRelayCost();
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
|
@ -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 }
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -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();
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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": ""
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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!([]);
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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\"}"}}
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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");
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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));
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue