Upstream from paritytech master branch
This commit is contained in:
commit
199e7ec271
|
@ -51,4 +51,6 @@ examples/parity_ora-2/keys/OraclesPoA/dapps_history.json
|
|||
examples/parity_ora-2/keys/OraclesPoA/address_book.json
|
||||
jsTests/node_modules/
|
||||
examples/db.toml
|
||||
jsTests/package-lock.json
|
||||
jsTests/package-lock.json
|
||||
node_modules
|
||||
compiled_contracts
|
||||
|
|
49
.travis.yml
49
.travis.yml
|
@ -1,16 +1,51 @@
|
|||
sudo: false
|
||||
language: rust
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
matrix:
|
||||
fast_finish: false
|
||||
include:
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
script:
|
||||
- cargo test --all
|
||||
- language: rust
|
||||
rust: stable
|
||||
cache: cargo
|
||||
fast_finish: false
|
||||
before_script:
|
||||
- sudo add-apt-repository ppa:ethereum/ethereum -y
|
||||
- sudo apt-get update -y
|
||||
- sudo apt-get install solc -y
|
||||
script:
|
||||
- cargo test --all
|
||||
- language: rust
|
||||
rust: beta
|
||||
cache: cargo
|
||||
fast_finish: false
|
||||
before_script:
|
||||
- sudo add-apt-repository ppa:ethereum/ethereum -y
|
||||
- sudo apt-get update -y
|
||||
- sudo apt-get install solc -y
|
||||
script:
|
||||
- cargo test --all
|
||||
- language: rust
|
||||
rust: nightly
|
||||
cache: cargo
|
||||
fast_finish: false
|
||||
before_script:
|
||||
- sudo add-apt-repository ppa:ethereum/ethereum -y
|
||||
- sudo apt-get update -y
|
||||
- sudo apt-get install solc -y
|
||||
script:
|
||||
- cargo test --all
|
||||
- language: node_js
|
||||
node_js: node
|
||||
cache: yarn
|
||||
before_script:
|
||||
- cd truffle
|
||||
- yarn install
|
||||
script:
|
||||
- yarn run ci
|
||||
after_script:
|
||||
- cat coverage/lcov.info | yarn run coveralls
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: zdEco0QAPik4peDfWuLHHex67LVe3E7c5VJNx+7ygH1pt+mzgobKo8jgT7WuH70xPRA717txNaj/zYGj5EuBKLn+Tkw3feDjrISYRD7ZOXFm1urv53KDx8xh2QJld2fHOc4UWcQ1qqBOWWOR9donuOaRfdDSOpWjLhl14heMgsW3o5Q/V4HN//VPHQctzaCq6r5eerx82B6SSNQ7+42rESu37N0Plv8JtCswihCuoUsMuzbXGwGzafR8IVf5WJPB1WM1KpjdWHgZCCgIfdH6C9fJ1P4fd2Z7EQJ0PYwxRntPlONzUr5khGPldXn7Czwoq9Go4eOZaTwHizprI/KCXBXASXQ/Z7EsU2AKl90qvUHLDB9i4aa/eDrkzQGPQ+dkjNckdQaaucIKX/r8VDm7ZVefkLOgbzc1plE6/TXslAS/n0OoXUXydzueyqi8oeVEagt/nSYaR4t/8C10eC/6gjVF6X6mpgM6/p8eVrN8bltMa0KSDfRvhi3kU1Nmc5b3CWg+neWYYFPHak3GyFwh3uRC0LJroO+j+dkQZiEpSsMgthx69RBDjYvoi3T5FGwt5s/FfnOtcHM65M9sGubMW4DsVaI7OHt7FUnp5dlqxk6NGT68R/E1ZeCwr7Y4QCXr4agew5OpxTni4MK7aCVnmAtabNVLI4wKdCy2ULJWLsE=
|
||||
|
|
128
README.md
128
README.md
|
@ -1,11 +1,66 @@
|
|||
# bridge
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Solidity Coverage Status][coveralls-image]][coveralls-url] (contracts only)
|
||||
|
||||
[travis-image]: https://travis-ci.org/paritytech/parity-bridge.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/paritytech/parity-bridge
|
||||
[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
|
||||
|
||||
Simple bridge between ValidatorSet-based parity chain (foreign) with any other Parity chain (home).
|
||||
bridge between two ethereum blockchains, `home` and `foreign`.
|
||||
|
||||
### 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.
|
||||
|
||||
`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`
|
||||
|
||||
`sender` deposits `value` into `HomeBridge`.
|
||||
the `HomeBridge` fallback function emits `Deposit(sender, value)`.
|
||||
for each `Deposit` event on `HomeBridge` every authority makes a transaction
|
||||
`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.
|
||||
|
||||
### withdraw balance on `ForeignBridge` and get it as ether on `home` chain
|
||||
|
||||
`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`
|
||||
|
||||
`sender` executes `ForeignBridge.transferLocal(recipient, value)`
|
||||
which checks and reduces `ForeignBridge.balances(sender)` and increases `ForeignBridge.balances(recipient)`
|
||||
by `value`.
|
||||
|
||||
### build
|
||||
|
||||
|
@ -33,6 +88,8 @@ Options:
|
|||
### configuration [file example](./examples/config.toml)
|
||||
|
||||
```toml
|
||||
estimated_gas_cost_of_withdraw = 100000
|
||||
|
||||
[home]
|
||||
account = "0x006e27b6a72e1f34c626762f3c4761547aff1421"
|
||||
ipc = "/Users/marek/Library/Application Support/io.parity.ethereum/jsonrpc.ipc"
|
||||
|
@ -62,6 +119,13 @@ home_deploy = { gas = 500000 }
|
|||
foreign_deploy = { gas = 500000 }
|
||||
```
|
||||
|
||||
#### options
|
||||
|
||||
- `estimated_gas_cost_of_withdraw` - how much gas a transaction to `HomeBridge.withdraw` consumes (**required**)
|
||||
- currently recommended value: `100000`
|
||||
- run [tools/estimate_gas_costs.sh](tools/estimate_gas_costs.sh) to compute an estimate
|
||||
- see [recipient pays relay cost to relaying authority](#recipient-pays-relay-cost-to-relaying-authority) for why this config option is needed
|
||||
|
||||
#### home options
|
||||
|
||||
- `home.account` - authority address on the home (**required**)
|
||||
|
@ -91,7 +155,7 @@ foreign_deploy = { gas = 500000 }
|
|||
- `transaction.home_deploy.gas` - specify how much gas should be consumed by home contract deploy
|
||||
- `transaction.home_deploy.gas_price` - specify gas price for home contract deploy
|
||||
- `transaction.foreign_deploy.gas` - specify how much gas should be consumed by foreign contract deploy
|
||||
- `transaction.foreign_deploy.gas_price` - specify gas price for home contract deploy
|
||||
- `transaction.foreign_deploy.gas_price` - specify gas price for foreign contract deploy
|
||||
- `transaction.deposit_relay.gas` - specify how much gas should be consumed by deposit relay
|
||||
- `transaction.deposit_relay.gas_price` - specify gas price for deposit relay
|
||||
- `transaction.withdraw_confirm.gas` - specify how much gas should be consumed by withdraw confirm
|
||||
|
@ -124,7 +188,7 @@ checked_withdraw_confirm = 121
|
|||
### example run
|
||||
|
||||
```
|
||||
./target/debug/bridge --config examples/config.toml --database db.toml
|
||||
./target/release/bridge --config examples/config.toml --database db.toml
|
||||
```
|
||||
|
||||
- example run requires a parity instance running
|
||||
|
@ -140,3 +204,61 @@ checked_withdraw_confirm = 121
|
|||
### withdraw
|
||||
|
||||
![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
|
||||
withdrawing `value` from `foreign` chain to `home` chain.
|
||||
`value - cost` is transferred to the `recipient`. `cost` is transferred to the `authority`
|
||||
executing `HomeBridge.withdraw`.
|
||||
the `recipient` pays the relaying `authority` for the execution of the transaction.
|
||||
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).
|
||||
|
||||
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)
|
||||
or trade in another currency.
|
||||
non-value-bearing means that one can easily obtain a large amount of ether
|
||||
on that chain for free.
|
||||
through a faucet in the case of testnets for example.
|
||||
|
||||
the bridge authorities are also the validators of the `foreign` PoA chain.
|
||||
transactions by the authorities are therefore free (gas price = 0) on `foreign`.
|
||||
|
||||
to execute a transaction on `home` a bridge authority has to spend ether to
|
||||
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.
|
||||
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`.
|
||||
an attacker was able to exhaust bridge authorities funds on `home`.
|
||||
|
||||
to shut down this attack `HomeBridge.withdraw` was modified so
|
||||
`value - cost` is transferred to the `recipient` and `cost` is transferred to the `authority`
|
||||
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.
|
||||
|
||||
`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
|
||||
on `home` in similar ways.
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
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.");
|
||||
}
|
|
@ -45,7 +45,8 @@ impl<T: Transport + Clone> Future for Deploy<T> {
|
|||
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<_>>()
|
||||
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)
|
||||
);
|
||||
let test_data = self.app.foreign_bridge.constructor(
|
||||
self.app.config.foreign.contract.bin.clone().0,
|
||||
|
|
|
@ -3,7 +3,7 @@ use futures::{Future, Stream, Poll};
|
|||
use futures::future::{JoinAll, join_all, Join};
|
||||
use tokio_timer::Timeout;
|
||||
use web3::Transport;
|
||||
use web3::types::{H256, Address, FilterBuilder, Log, Bytes, TransactionRequest};
|
||||
use web3::types::{U256, H256, Address, FilterBuilder, Log, Bytes, TransactionRequest};
|
||||
use ethabi::{RawLog, self};
|
||||
use app::App;
|
||||
use api::{self, LogStream, ApiCall};
|
||||
|
@ -12,28 +12,36 @@ use util::web3_filter;
|
|||
use database::Database;
|
||||
use error::{self, Error};
|
||||
|
||||
/// returns a filter for `ForeignBridge.CollectedSignatures` events
|
||||
fn collected_signatures_filter(foreign: &foreign::ForeignBridge, address: Address) -> FilterBuilder {
|
||||
let filter = foreign.events().collected_signatures().create_filter();
|
||||
web3_filter(filter, address)
|
||||
}
|
||||
|
||||
/// payloads for calls to `ForeignBridge.signature` and `ForeignBridge.message`
|
||||
/// to retrieve the signatures (v, r, s) and messages
|
||||
/// which the withdraw relay process should later relay to `HomeBridge`
|
||||
/// by calling `HomeBridge.withdraw(v, r, s, message)`
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct RelayAssignment {
|
||||
signature_payloads: Vec<Bytes>,
|
||||
message_payload: Bytes,
|
||||
}
|
||||
|
||||
fn signatures_payload(foreign: &foreign::ForeignBridge, signatures: u32, my_address: Address, log: Log) -> error::Result<Option<RelayAssignment>> {
|
||||
fn signatures_payload(foreign: &foreign::ForeignBridge, required_signatures: u32, my_address: Address, log: Log) -> error::Result<Option<RelayAssignment>> {
|
||||
// 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(),
|
||||
data: log.data.0,
|
||||
};
|
||||
let collected_signatures = foreign.events().collected_signatures().parse_log(raw_log)?;
|
||||
if collected_signatures.authority != my_address.0 {
|
||||
// someone else will relay this transaction to home
|
||||
// this authority is not responsible for relaying this transaction.
|
||||
// someone else will relay this transaction to home.
|
||||
return Ok(None);
|
||||
}
|
||||
let signature_payloads = (0..signatures).into_iter()
|
||||
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)
|
||||
|
@ -46,7 +54,19 @@ fn signatures_payload(foreign: &foreign::ForeignBridge, signatures: u32, my_addr
|
|||
}))
|
||||
}
|
||||
|
||||
fn withdraw_relay_payload(home: &home::HomeBridge, signatures: Vec<Bytes>, message: Bytes) -> Bytes {
|
||||
/// 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();
|
||||
|
@ -63,15 +83,22 @@ fn withdraw_relay_payload(home: &home::HomeBridge, signatures: Vec<Bytes>, messa
|
|||
s_vec.push(s);
|
||||
r_vec.push(r);
|
||||
}
|
||||
home.functions().withdraw().input(v_vec, r_vec, s_vec, message.0).into()
|
||||
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,
|
||||
Fetch {
|
||||
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>>,
|
||||
block: u64,
|
||||
},
|
||||
RelayWithdraws {
|
||||
future: JoinAll<Vec<Timeout<ApiCall<H256, T::Out>>>>,
|
||||
block: u64,
|
||||
|
@ -149,19 +176,65 @@ impl<T: Transport> Stream for WithdrawRelay<T> {
|
|||
.map(|calls| join_all(calls))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
WithdrawRelayState::Fetch {
|
||||
// wait for fetching of messages and signatures to complete
|
||||
WithdrawRelayState::FetchMessagesSignatures {
|
||||
future: join_all(message_calls).join(join_all(signature_calls)),
|
||||
block: item.to,
|
||||
}
|
||||
},
|
||||
WithdrawRelayState::Fetch { ref mut future, block } => {
|
||||
WithdrawRelayState::FetchMessagesSignatures { ref mut future, block } => {
|
||||
let (messages, signatures) = try_ready!(future.poll());
|
||||
assert_eq!(messages.len(), signatures.len());
|
||||
|
||||
let app = &self.app;
|
||||
let home_contract = &self.home_contract;
|
||||
|
||||
let relays = messages.into_iter().zip(signatures.into_iter())
|
||||
.map(|(message, signatures)| withdraw_relay_payload(&app.home_bridge, signatures, message))
|
||||
let message_value_sufficient_payloads = messages
|
||||
.iter()
|
||||
.map(|message| {
|
||||
message_value_sufficient_payload(
|
||||
&app.home_bridge,
|
||||
message
|
||||
)
|
||||
})
|
||||
.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;
|
||||
|
||||
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()),
|
||||
|
@ -178,6 +251,7 @@ impl<T: Transport> Stream for WithdrawRelay<T> {
|
|||
app.config.home.request_timeout)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// wait for relays to complete
|
||||
WithdrawRelayState::RelayWithdraws {
|
||||
future: join_all(relays),
|
||||
block,
|
||||
|
@ -255,7 +329,7 @@ mod tests {
|
|||
];
|
||||
let message: Bytes = vec![0x33; 84].into();
|
||||
|
||||
let payload = withdraw_relay_payload(&home, signatures, message);
|
||||
let payload = withdraw_relay_payload(&home, &signatures, &message);
|
||||
let expected: Bytes = "9ce318f6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000".from_hex().unwrap().into();
|
||||
assert_eq!(expected, payload);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ pub struct Config {
|
|||
pub foreign: Node,
|
||||
pub authorities: Authorities,
|
||||
pub txs: Transactions,
|
||||
pub estimated_gas_cost_of_withdraw: u32,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -42,6 +43,7 @@ impl Config {
|
|||
required_signatures: config.authorities.required_signatures,
|
||||
},
|
||||
txs: config.transactions.map(Transactions::from_load_struct).unwrap_or_default(),
|
||||
estimated_gas_cost_of_withdraw: config.estimated_gas_cost_of_withdraw,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
|
@ -141,6 +143,7 @@ mod load {
|
|||
pub foreign: Node,
|
||||
pub authorities: Authorities,
|
||||
pub transactions: Option<Transactions>,
|
||||
pub estimated_gas_cost_of_withdraw: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -194,6 +197,8 @@ mod tests {
|
|||
#[test]
|
||||
fn load_full_setup_from_str() {
|
||||
let toml = r#"
|
||||
estimated_gas_cost_of_withdraw = 100000
|
||||
|
||||
[home]
|
||||
account = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b"
|
||||
ipc = "/home.ipc"
|
||||
|
@ -201,14 +206,14 @@ poll_interval = 2
|
|||
required_confirmations = 100
|
||||
|
||||
[home.contract]
|
||||
bin = "../contracts/HomeBridge.bin"
|
||||
bin = "../compiled_contracts/HomeBridge.bin"
|
||||
|
||||
[foreign]
|
||||
account = "0x0000000000000000000000000000000000000001"
|
||||
ipc = "/foreign.ipc"
|
||||
|
||||
[foreign.contract]
|
||||
bin = "../contracts/ForeignBridge.bin"
|
||||
bin = "../compiled_contracts/ForeignBridge.bin"
|
||||
|
||||
[authorities]
|
||||
accounts = [
|
||||
|
@ -228,7 +233,7 @@ home_deploy = { gas = 20 }
|
|||
account: "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b".parse().unwrap(),
|
||||
ipc: "/home.ipc".into(),
|
||||
contract: ContractConfig {
|
||||
bin: include_str!("../../contracts/HomeBridge.bin").from_hex().unwrap().into(),
|
||||
bin: include_str!("../../compiled_contracts/HomeBridge.bin").from_hex().unwrap().into(),
|
||||
},
|
||||
poll_interval: Duration::from_secs(2),
|
||||
request_timeout: Duration::from_secs(5),
|
||||
|
@ -237,7 +242,7 @@ home_deploy = { gas = 20 }
|
|||
foreign: Node {
|
||||
account: "0x0000000000000000000000000000000000000001".parse().unwrap(),
|
||||
contract: ContractConfig {
|
||||
bin: include_str!("../../contracts/ForeignBridge.bin").from_hex().unwrap().into(),
|
||||
bin: include_str!("../../compiled_contracts/ForeignBridge.bin").from_hex().unwrap().into(),
|
||||
},
|
||||
ipc: "/foreign.ipc".into(),
|
||||
poll_interval: Duration::from_secs(1),
|
||||
|
@ -251,7 +256,8 @@ home_deploy = { gas = 20 }
|
|||
"0x0000000000000000000000000000000000000003".parse().unwrap(),
|
||||
],
|
||||
required_signatures: 2,
|
||||
}
|
||||
},
|
||||
estimated_gas_cost_of_withdraw: 100_000,
|
||||
};
|
||||
|
||||
expected.txs.home_deploy = TransactionConfig {
|
||||
|
@ -266,19 +272,21 @@ home_deploy = { gas = 20 }
|
|||
#[test]
|
||||
fn load_minimal_setup_from_str() {
|
||||
let toml = r#"
|
||||
estimated_gas_cost_of_withdraw = 200000000
|
||||
|
||||
[home]
|
||||
account = "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b"
|
||||
ipc = ""
|
||||
|
||||
[home.contract]
|
||||
bin = "../contracts/HomeBridge.bin"
|
||||
bin = "../compiled_contracts/HomeBridge.bin"
|
||||
|
||||
[foreign]
|
||||
account = "0x0000000000000000000000000000000000000001"
|
||||
ipc = ""
|
||||
|
||||
[foreign.contract]
|
||||
bin = "../contracts/ForeignBridge.bin"
|
||||
bin = "../compiled_contracts/ForeignBridge.bin"
|
||||
|
||||
[authorities]
|
||||
accounts = [
|
||||
|
@ -294,7 +302,7 @@ required_signatures = 2
|
|||
account: "0x1B68Cb0B50181FC4006Ce572cF346e596E51818b".parse().unwrap(),
|
||||
ipc: "".into(),
|
||||
contract: ContractConfig {
|
||||
bin: include_str!("../../contracts/HomeBridge.bin").from_hex().unwrap().into(),
|
||||
bin: include_str!("../../compiled_contracts/HomeBridge.bin").from_hex().unwrap().into(),
|
||||
},
|
||||
poll_interval: Duration::from_secs(1),
|
||||
request_timeout: Duration::from_secs(5),
|
||||
|
@ -304,7 +312,7 @@ required_signatures = 2
|
|||
account: "0x0000000000000000000000000000000000000001".parse().unwrap(),
|
||||
ipc: "".into(),
|
||||
contract: ContractConfig {
|
||||
bin: include_str!("../../contracts/ForeignBridge.bin").from_hex().unwrap().into(),
|
||||
bin: include_str!("../../compiled_contracts/ForeignBridge.bin").from_hex().unwrap().into(),
|
||||
},
|
||||
poll_interval: Duration::from_secs(1),
|
||||
request_timeout: Duration::from_secs(5),
|
||||
|
@ -317,7 +325,8 @@ required_signatures = 2
|
|||
"0x0000000000000000000000000000000000000003".parse().unwrap(),
|
||||
],
|
||||
required_signatures: 2,
|
||||
}
|
||||
},
|
||||
estimated_gas_cost_of_withdraw: 200_000_000,
|
||||
};
|
||||
|
||||
let config = Config::load_from_str(toml).unwrap();
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
use_contract!(home, "HomeBridge", "../contracts/HomeBridge.abi");
|
||||
use_contract!(foreign, "ForeignBridge", "../contracts/ForeignBridge.abi");
|
||||
use_contract!(home, "HomeBridge", "../compiled_contracts/HomeBridge.abi");
|
||||
use_contract!(foreign, "ForeignBridge", "../compiled_contracts/ForeignBridge.abi");
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a723058201dad5c43bba0bffc89ca5cf275cee04095a323df75058b9e5cfb736e54381f790029
|
|
@ -1,246 +0,0 @@
|
|||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "hash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "signature",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "transactionHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "deposit",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balances",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "hash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "message",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "authorities",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "signature",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "submitSignature",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "requiredSignatures",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "externalTransfer",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "n",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "a",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Withdraw",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "authority",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "messageHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "CollectedSignatures",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
File diff suppressed because one or more lines are too long
|
@ -1,115 +0,0 @@
|
|||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "authorities",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "requiredSignatures",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "v",
|
||||
"type": "uint8[]"
|
||||
},
|
||||
{
|
||||
"name": "r",
|
||||
"type": "bytes32[]"
|
||||
},
|
||||
{
|
||||
"name": "s",
|
||||
"type": "bytes32[]"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "n",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "a",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Withdraw",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a723058204971c18e0e1ddf69fded6644effcdb50c80ccc6d5a8aa81fe9fbfa53e1b68d100029
|
|
@ -1,25 +0,0 @@
|
|||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "signature",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "signer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
6060604052341561000f57600080fd5b6105818061001e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680631c7ede5f1461003d57600080fd5b6100d0600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610112565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600061011e8383610126565b905092915050565b6000806000806041865114151561013c57600080fd5b602086015192506040860151915060608601519050600161015c8661020e565b827f010000000000000000000000000000000000000000000000000000000000000090048585604051600081526020016040526000604051602001526040518085600019166000191681526020018460ff1660ff16815260200183600019166000191681526020018260001916600019168152602001945050505050602060405160208103908084039060008661646e5a03f115156101fa57600080fd5b505060206040510351935050505092915050565b600061021861052d565b6040805190810160405280601a81526020017f19457468657265756d205369676e6564204d6573736167653a0a00000000000081525090508061025b845161036e565b846040518084805190602001908083835b602083101515610291578051825260208201915060208101905060208303925061026c565b6001836020036101000a03801982511681845116808217855250505050505090500183805190602001908083835b6020831015156102e457805182526020820191506020810190506020830392506102bf565b6001836020036101000a03801982511681845116808217855250505050505090500182805190602001908083835b6020831015156103375780518252602082019150602081019050602083039250610312565b6001836020036101000a03801982511681845116808217855250505050505090500193505050506040518091039020915050919050565b610376610541565b61037e61052d565b60008061038961052d565b6000600860405180591061039a5750595b90808252806020026020018201604052509450600093505b60008714151561044957600a878115156103c857fe5b069250600a878115156103d757fe5b049650826030017f010000000000000000000000000000000000000000000000000000000000000002858580600101965081518110151561041457fe5b9060200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506103b2565b836040518059106104575750595b90808252806020026020018201604052509150600090505b83811015610520578460018286030381518110151561048a57fe5b9060200101517f010000000000000000000000000000000000000000000000000000000000000090047f01000000000000000000000000000000000000000000000000000000000000000282828151811015156104e357fe5b9060200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350808060010191505061046f565b8195505050505050919050565b602060405190810160405280600081525090565b6020604051908101604052806000815250905600a165627a7a72305820fb76108a72fef4e281cb9e45df4aa8d8c33dcc5bff00ea6f7e5d9719a27745260029
|
|
@ -1 +0,0 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a723058200905d3e14b8b5697c0ffc99aac06fac68a87f03b29568419dadf089de31f7f130029
|
|
@ -1,69 +1,163 @@
|
|||
pragma solidity ^0.4.15;
|
||||
pragma solidity ^0.4.17;
|
||||
|
||||
library Authorities {
|
||||
function contains (address[] self, address value) internal returns (bool) {
|
||||
for (uint i = 0; i < self.length; i++) {
|
||||
if (self[i] == value) {
|
||||
|
||||
/// general helpers.
|
||||
/// `internal` so they get compiled into contracts using them.
|
||||
library Helpers {
|
||||
/// returns whether `array` contains `value`.
|
||||
function addressArrayContains(address[] array, address value) internal pure returns (bool) {
|
||||
for (uint i = 0; i < array.length; i++) {
|
||||
if (array[i] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Library used only to test Signer library via rpc calls
|
||||
library SignerTest {
|
||||
function signer (bytes signature, bytes message) constant returns (address) {
|
||||
return Signer.signer(signature, message);
|
||||
// returns the digits of `inputValue` as a string.
|
||||
// example: `uintToString(12345678)` returns `"12345678"`
|
||||
function uintToString(uint inputValue) internal pure returns (string) {
|
||||
// figure out the length of the resulting string
|
||||
uint length = 0;
|
||||
uint currentValue = inputValue;
|
||||
do {
|
||||
length++;
|
||||
currentValue /= 10;
|
||||
} while (currentValue != 0);
|
||||
// allocate enough memory
|
||||
bytes memory result = new bytes(length);
|
||||
// construct the string backwards
|
||||
uint i = length - 1;
|
||||
currentValue = inputValue;
|
||||
do {
|
||||
result[i--] = byte(48 + currentValue % 10);
|
||||
currentValue /= 10;
|
||||
} while (currentValue != 0);
|
||||
return string(result);
|
||||
}
|
||||
}
|
||||
|
||||
library Utils {
|
||||
function toString (uint256 v) internal returns (string str) {
|
||||
// it is used only for small numbers
|
||||
bytes memory reversed = new bytes(8);
|
||||
uint i = 0;
|
||||
while (v != 0) {
|
||||
uint remainder = v % 10;
|
||||
v = v / 10;
|
||||
reversed[i++] = byte(48 + remainder);
|
||||
}
|
||||
bytes memory s = new bytes(i);
|
||||
for (uint j = 0; j < i; j++) {
|
||||
s[j] = reversed[i - j - 1];
|
||||
}
|
||||
str = string(s);
|
||||
|
||||
/// Library used only to test Helpers library via rpc calls
|
||||
library HelpersTest {
|
||||
function addressArrayContains(address[] array, address value) public pure returns (bool) {
|
||||
return Helpers.addressArrayContains(array, value);
|
||||
}
|
||||
|
||||
function uintToString(uint256 inputValue) public pure returns (string str) {
|
||||
return Helpers.uintToString(inputValue);
|
||||
}
|
||||
}
|
||||
|
||||
library Signer {
|
||||
function signer (bytes signature, bytes message) internal returns (address) {
|
||||
|
||||
// helpers for message signing.
|
||||
// `internal` so they get compiled into contracts using them.
|
||||
library MessageSigning {
|
||||
function recoverAddressFromSignedMessage(bytes signature, bytes message) internal pure returns (address) {
|
||||
require(signature.length == 65);
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
bytes1 v;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
r := mload(add(signature, 0x20))
|
||||
s := mload(add(signature, 0x40))
|
||||
v := mload(add(signature, 0x60))
|
||||
}
|
||||
return ecrecover(hash(message), uint8(v), r, s);
|
||||
return ecrecover(hashMessage(message), uint8(v), r, s);
|
||||
}
|
||||
|
||||
function hash (bytes message) internal returns (bytes32) {
|
||||
function hashMessage(bytes message) internal pure returns (bytes32) {
|
||||
bytes memory prefix = "\x19Ethereum Signed Message:\n";
|
||||
return sha3(prefix, Utils.toString(message.length), message);
|
||||
return keccak256(prefix, Helpers.uintToString(message.length), message);
|
||||
}
|
||||
}
|
||||
|
||||
contract HomeBridge {
|
||||
using Authorities for address[];
|
||||
|
||||
/// Library used only to test MessageSigning library via rpc calls
|
||||
library MessageSigningTest {
|
||||
function recoverAddressFromSignedMessage(bytes signature, bytes message) public pure returns (address) {
|
||||
return MessageSigning.recoverAddressFromSignedMessage(signature, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
library Message {
|
||||
// layout of message :: bytes:
|
||||
// offset 0: 32 bytes :: uint (little endian) - message length
|
||||
// offset 32: 20 bytes :: address - recipient address
|
||||
// offset 52: 32 bytes :: uint (little endian) - value
|
||||
// offset 84: 32 bytes :: bytes32 - transaction hash
|
||||
|
||||
// bytes 1 to 32 are 0 because message length is stored as little endian.
|
||||
// mload always reads 32 bytes.
|
||||
// so we can and have to start reading recipient at offset 20 instead of 32.
|
||||
// if we were to read at 32 the address would contain part of value and be corrupted.
|
||||
// when reading from offset 20 mload will read 12 zero bytes followed
|
||||
// by the 20 recipient address bytes and correctly convert it into an address.
|
||||
// this saves some storage/gas over the alternative solution
|
||||
// which is padding address to 32 bytes and reading recipient at offset 32.
|
||||
// for more details see discussion in:
|
||||
// https://github.com/paritytech/parity-bridge/issues/61
|
||||
|
||||
function getRecipient(bytes message) internal pure returns (address) {
|
||||
address recipient;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
recipient := mload(add(message, 20))
|
||||
}
|
||||
return recipient;
|
||||
}
|
||||
|
||||
function getValue(bytes message) internal pure returns (uint) {
|
||||
uint value;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
value := mload(add(message, 52))
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function getTransactionHash(bytes message) internal pure returns (bytes32) {
|
||||
bytes32 hash;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
hash := mload(add(message, 84))
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Library used only to test Message library via rpc calls
|
||||
library MessageTest {
|
||||
function getRecipient(bytes message) public pure returns (address) {
|
||||
return Message.getRecipient(message);
|
||||
}
|
||||
|
||||
function getValue(bytes message) public pure returns (uint) {
|
||||
return Message.getValue(message);
|
||||
}
|
||||
|
||||
function getTransactionHash(bytes message) public pure returns (bytes32) {
|
||||
return Message.getTransactionHash(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contract HomeBridge {
|
||||
/// Number of authorities signatures required to withdraw the money.
|
||||
///
|
||||
/// Must be lesser than number of authorities.
|
||||
uint public requiredSignatures;
|
||||
|
||||
/// The gas cost of calling `HomeBridge.withdraw`.
|
||||
///
|
||||
/// Is subtracted from `value` on withdraw.
|
||||
/// recipient pays the relaying authority for withdraw.
|
||||
/// this shuts down attacks that exhaust authorities funds on home chain.
|
||||
uint public estimatedGasCostOfWithdraw;
|
||||
|
||||
/// Contract authorities.
|
||||
address[] public authorities;
|
||||
|
||||
|
@ -77,57 +171,89 @@ contract HomeBridge {
|
|||
event Withdraw (address recipient, uint value);
|
||||
|
||||
/// Multisig authority validation
|
||||
modifier allAuthorities (uint8[] v, bytes32[] r, bytes32[] s, bytes message) {
|
||||
var hash = Signer.hash(message);
|
||||
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(authorities.contains(a));
|
||||
require(!used.contains(a));
|
||||
require(Helpers.addressArrayContains(authorities, a));
|
||||
require(!Helpers.addressArrayContains(used, a));
|
||||
used[i] = a;
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
/// Constructor.
|
||||
function HomeBridge (uint n, address[] a) {
|
||||
require(n != 0);
|
||||
require(n <= a.length);
|
||||
requiredSignatures = n;
|
||||
authorities = a;
|
||||
function HomeBridge(
|
||||
uint requiredSignaturesParam,
|
||||
address[] authoritiesParam,
|
||||
uint estimatedGasCostOfWithdrawParam
|
||||
) public
|
||||
{
|
||||
require(requiredSignaturesParam != 0);
|
||||
require(requiredSignaturesParam <= authoritiesParam.length);
|
||||
requiredSignatures = requiredSignaturesParam;
|
||||
authorities = authoritiesParam;
|
||||
estimatedGasCostOfWithdraw = estimatedGasCostOfWithdrawParam;
|
||||
}
|
||||
|
||||
/// Should be used to deposit money.
|
||||
function () payable {
|
||||
function () public payable {
|
||||
Deposit(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
/// Used to withdrawn money from the contract.
|
||||
/// to be called by authorities to check
|
||||
/// whether they withdraw message should be relayed or whether it
|
||||
/// is too low to cover the cost of calling withdraw and can be ignored
|
||||
function isMessageValueSufficientToCoverRelay(bytes message) public view returns (bool) {
|
||||
return Message.getValue(message) > getWithdrawRelayCost();
|
||||
}
|
||||
|
||||
/// an upper bound to the cost of relaying a withdraw by calling HomeBridge.withdraw
|
||||
function getWithdrawRelayCost() public view returns (uint) {
|
||||
return estimatedGasCostOfWithdraw * tx.gasprice;
|
||||
}
|
||||
|
||||
/// Used to withdraw money from the contract.
|
||||
///
|
||||
/// message contains:
|
||||
/// withdrawal recipient (bytes20)
|
||||
/// withdrawal value (uint)
|
||||
/// foreign transaction hash (bytes32) // to avoid transaction duplication
|
||||
function withdraw (uint8[] v, bytes32[] r, bytes32[] s, bytes message) allAuthorities(v, r, s, message) {
|
||||
address recipient;
|
||||
uint value;
|
||||
bytes32 hash;
|
||||
assembly {
|
||||
recipient := mload(add(message, 0x20))
|
||||
value := mload(add(message, 0x40))
|
||||
hash := mload(add(message, 0x60))
|
||||
}
|
||||
///
|
||||
/// NOTE that anyone can call withdraw provided they have the message and required signatures!
|
||||
function withdraw(uint8[] v, bytes32[] r, bytes32[] s, bytes message) public allAuthorities(v, r, s, message) {
|
||||
require(message.length == 84);
|
||||
address recipient = Message.getRecipient(message);
|
||||
uint value = Message.getValue(message);
|
||||
bytes32 hash = Message.getTransactionHash(message);
|
||||
|
||||
// Duplicated withdraw
|
||||
// The following two statements guard against reentry into this function.
|
||||
// Duplicated withdraw or reentry.
|
||||
require(!withdraws[hash]);
|
||||
|
||||
// Order of operations below is critical to avoid TheDAO-like bug
|
||||
// Order of operations below is critical to avoid TheDAO-like re-entry bug
|
||||
withdraws[hash] = true;
|
||||
recipient.transfer(value);
|
||||
Withdraw(recipient, value);
|
||||
|
||||
// this fails if `value` is not even enough to cover the relay cost.
|
||||
// Authorities simply IGNORE withdraws where `value` can’t relay cost.
|
||||
// Think of it as `value` getting burned entirely on the relay with no value left to pay out the recipient.
|
||||
require(isMessageValueSufficientToCoverRelay(message));
|
||||
|
||||
uint estimatedWeiCostOfWithdraw = getWithdrawRelayCost();
|
||||
|
||||
// charge recipient for relay cost
|
||||
uint valueRemainingAfterSubtractingCost = value - estimatedWeiCostOfWithdraw;
|
||||
|
||||
// pay out recipient
|
||||
recipient.transfer(valueRemainingAfterSubtractingCost);
|
||||
|
||||
// refund relay cost to relaying authority
|
||||
msg.sender.transfer(estimatedWeiCostOfWithdraw);
|
||||
|
||||
Withdraw(recipient, valueRemainingAfterSubtractingCost);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,8 +264,6 @@ contract ERC20 {
|
|||
}
|
||||
|
||||
contract ForeignBridge {
|
||||
using Authorities for address[];
|
||||
|
||||
struct SignaturesCollection {
|
||||
/// Signed message.
|
||||
bytes message;
|
||||
|
@ -182,16 +306,20 @@ contract ForeignBridge {
|
|||
event CollectedSignatures(address authority, bytes32 messageHash);
|
||||
|
||||
/// Constructor.
|
||||
function ForeignBridge(uint n, address[] a) {
|
||||
require(n != 0);
|
||||
require(n <= a.length);
|
||||
requiredSignatures = n;
|
||||
authorities = a;
|
||||
function ForeignBridge(
|
||||
uint requiredSignaturesParam,
|
||||
address[] authoritiesParam
|
||||
) public
|
||||
{
|
||||
require(requiredSignaturesParam != 0);
|
||||
require(requiredSignaturesParam <= authoritiesParam.length);
|
||||
requiredSignatures = requiredSignaturesParam;
|
||||
authorities = authoritiesParam;
|
||||
}
|
||||
|
||||
/// Multisig authority validation
|
||||
modifier onlyAuthority () {
|
||||
require(authorities.contains(msg.sender));
|
||||
modifier onlyAuthority() {
|
||||
require(Helpers.addressArrayContains(authorities, msg.sender));
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -215,12 +343,12 @@ contract ForeignBridge {
|
|||
/// deposit recipient (bytes20)
|
||||
/// deposit value (uint)
|
||||
/// mainnet transaction hash (bytes32) // to avoid transaction duplication
|
||||
function deposit (address recipient, uint value, bytes32 transactionHash) onlyAuthority() {
|
||||
function deposit(address recipient, uint value, bytes32 transactionHash) public onlyAuthority() {
|
||||
// Protection from misbehaing authority
|
||||
var hash = sha3(recipient, value, transactionHash);
|
||||
var hash = keccak256(recipient, value, transactionHash);
|
||||
|
||||
// Duplicated deposits
|
||||
require(!deposits[hash].contains(msg.sender));
|
||||
require(!Helpers.addressArrayContains(deposits[hash], msg.sender));
|
||||
|
||||
deposits[hash].push(msg.sender);
|
||||
// TODO: this may cause troubles if requriedSignatures len is changed
|
||||
|
@ -230,13 +358,36 @@ contract ForeignBridge {
|
|||
}
|
||||
}
|
||||
|
||||
/// Withdraw money
|
||||
function withdraw(address recipient, uint value) public {
|
||||
/// 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);
|
||||
|
||||
balances[msg.sender] -= value;
|
||||
Withdraw(recipient, value);
|
||||
}
|
||||
|
||||
/// Transfer `value` to `recipient` on this `foreign` chain.
|
||||
///
|
||||
/// does not affect `home` chain. does not do a relay.
|
||||
function transferLocal(address recipient, uint value) public {
|
||||
require(balances[msg.sender] >= value);
|
||||
// fails if value == 0, or if there is an overflow
|
||||
require(balances[recipient] + value > balances[recipient]);
|
||||
|
||||
balances[msg.sender] -= value;
|
||||
balances[recipient] += value;
|
||||
Transfer(msg.sender, recipient, value);
|
||||
}
|
||||
|
||||
/// Should be used as sync tool
|
||||
///
|
||||
/// Message is a message that should be relayed to main chain once authorities sign it.
|
||||
|
@ -245,33 +396,33 @@ contract ForeignBridge {
|
|||
/// withdrawal recipient (bytes20)
|
||||
/// withdrawal value (uint)
|
||||
/// foreign transaction hash (bytes32) // to avoid transaction duplication
|
||||
function submitSignature (bytes signature, bytes message) onlyAuthority() {
|
||||
function submitSignature(bytes signature, bytes message) public onlyAuthority() {
|
||||
// Validate submited signatures
|
||||
require(Signer.signer(signature, message) == msg.sender);
|
||||
require(MessageSigning.recoverAddressFromSignedMessage(signature, message) == msg.sender);
|
||||
|
||||
// Valid withdraw message must have 84 bytes
|
||||
require(message.length == 84);
|
||||
var hash = sha3(message);
|
||||
var hash = keccak256(message);
|
||||
|
||||
// Duplicated signatures
|
||||
require(!signatures[hash].signed.contains(msg.sender));
|
||||
require(!Helpers.addressArrayContains(signatures[hash].signed, msg.sender));
|
||||
signatures[hash].message = message;
|
||||
signatures[hash].signed.push(msg.sender);
|
||||
signatures[hash].signatures.push(signature);
|
||||
|
||||
// TODO: this may cause troubles if requriedSignatures len is changed
|
||||
// TODO: this may cause troubles if requiredSignatures len is changed
|
||||
if (signatures[hash].signed.length == requiredSignatures) {
|
||||
CollectedSignatures(msg.sender, hash);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get signature
|
||||
function signature (bytes32 hash, uint index) constant returns (bytes) {
|
||||
function signature(bytes32 hash, uint index) public view returns (bytes) {
|
||||
return signatures[hash].signatures[index];
|
||||
}
|
||||
|
||||
/// Get message
|
||||
function message (bytes32 hash) constant returns (bytes) {
|
||||
function message(bytes32 hash) public view returns (bytes) {
|
||||
return signatures[hash].message;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
estimated_gas_cost_of_withdraw = 100000
|
||||
|
||||
[home]
|
||||
account = "0x006e27b6a72e1f34c626762f3c4761547aff1421"
|
||||
ipc = "/Users/marek/Library/Application Support/io.parity.ethereum/jsonrpc.ipc"
|
||||
|
|
|
@ -135,7 +135,8 @@ macro_rules! test_app_stream {
|
|||
authorities: Authorities {
|
||||
accounts: $authorities_accs.iter().map(|a: &&str| a.parse().unwrap()).collect(),
|
||||
required_signatures: $signatures,
|
||||
}
|
||||
},
|
||||
estimated_gas_cost_of_withdraw: 100_000,
|
||||
};
|
||||
|
||||
let app = App {
|
||||
|
@ -153,6 +154,23 @@ macro_rules! test_app_stream {
|
|||
let app = Arc::new(app);
|
||||
let stream = $init_stream(app, &$db);
|
||||
let res = stream.collect().wait();
|
||||
|
||||
assert_eq!(
|
||||
home.expected_requests.len(),
|
||||
home.requests.get(),
|
||||
"home: expected {} requests but received only {}",
|
||||
home.expected_requests.len(),
|
||||
home.requests.get()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
foreign.expected_requests.len(),
|
||||
foreign.requests.get(),
|
||||
"foreign: expected {} requests but received only {}",
|
||||
foreign.expected_requests.len(),
|
||||
foreign.requests.get()
|
||||
);
|
||||
|
||||
assert_eq!($expected, res.unwrap());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,11 @@ extern crate tests;
|
|||
|
||||
use bridge::bridge::create_withdraw_relay;
|
||||
|
||||
// 1 signature required. relay polled twice.
|
||||
// no CollectedSignatures on ForeignBridge.
|
||||
// no relay.
|
||||
test_app_stream! {
|
||||
name => withdraw_relay_basic,
|
||||
name => withdraw_relay_no_log_no_relay,
|
||||
database => Database::default(),
|
||||
home =>
|
||||
account => "0x0000000000000000000000000000000000000001",
|
||||
|
@ -40,8 +43,12 @@ test_app_stream! {
|
|||
]
|
||||
}
|
||||
|
||||
// 2 signatures required. relay polled twice.
|
||||
// single CollectedSignatures log present. message value covers relay cost.
|
||||
// authority not responsible.
|
||||
// message is ignored.
|
||||
test_app_stream! {
|
||||
name => withdraw_relay_single_log_no_relay,
|
||||
name => withdraw_relay_single_log_authority_not_responsible_no_relay,
|
||||
database => Database::default(),
|
||||
home =>
|
||||
account => "0x0000000000000000000000000000000000000001",
|
||||
|
@ -69,8 +76,11 @@ test_app_stream! {
|
|||
]
|
||||
}
|
||||
|
||||
// 2 signatures required. relay polled twice.
|
||||
// single CollectedSignatures log present. message value covers relay cost.
|
||||
// message gets relayed.
|
||||
test_app_stream! {
|
||||
name => withdraw_relay_single_log_relay,
|
||||
name => withdraw_relay_single_log_sufficient_value_relay,
|
||||
database => Database::default(),
|
||||
home =>
|
||||
account => "0x0000000000000000000000000000000000000001",
|
||||
|
@ -88,6 +98,12 @@ test_app_stream! {
|
|||
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""#;
|
||||
|
@ -99,9 +115,11 @@ test_app_stream! {
|
|||
"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""#;
|
||||
|
@ -111,8 +129,60 @@ test_app_stream! {
|
|||
]
|
||||
}
|
||||
|
||||
// 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_check_gas,
|
||||
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",
|
||||
|
@ -136,6 +206,12 @@ test_app_stream! {
|
|||
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""#;
|
||||
|
@ -147,9 +223,11 @@ test_app_stream! {
|
|||
"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""#;
|
||||
|
@ -159,8 +237,10 @@ test_app_stream! {
|
|||
]
|
||||
}
|
||||
|
||||
// like `withdraw_relay_single_log_sufficient_value_relay`
|
||||
// but with explicit contract addresses
|
||||
test_app_stream! {
|
||||
name => withdraw_relay_single_contract_addresses,
|
||||
name => withdraw_relay_single_explicit_contract_addresses,
|
||||
database => Database {
|
||||
home_contract_address: "0x00000000000000000000000000000000000000dd".parse().unwrap(),
|
||||
foreign_contract_address: "0x00000000000000000000000000000000000000ee".parse().unwrap(),
|
||||
|
@ -182,6 +262,12 @@ test_app_stream! {
|
|||
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`
|
||||
"eth_sendTransaction" =>
|
||||
req => r#"[{"data":"0x9ce318f6000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000002111111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222220000000000000000000000000000000000000000000000000000000000000054333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333000000000000000000000000","from":"0x0000000000000000000000000000000000000001","gas":"0x0","gasPrice":"0x0","to":"0x00000000000000000000000000000000000000dd"}]"#,
|
||||
res => r#""0x1db8f385535c0d178b8f40016048f3a3cffee8f94e68978ea4b277f57b638f0b""#;
|
||||
|
@ -193,9 +279,11 @@ test_app_stream! {
|
|||
"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"}]"#;
|
||||
// call to `message`
|
||||
"eth_call" =>
|
||||
req => r#"[{"data":"0x490a32c600000000000000000000000000000000000000000000000000000000000000f0","to":"0x00000000000000000000000000000000000000ee"},"latest"]"#,
|
||||
res => r#""0x333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333""#;
|
||||
// calls to `signature`
|
||||
"eth_call" =>
|
||||
req => r#"[{"data":"0x1812d99600000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000000","to":"0x00000000000000000000000000000000000000ee"},"latest"]"#,
|
||||
res => r#""0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111""#;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# prints out estimated gas costs of contract functions.
|
||||
# runs the tests which estimate and print out gas costs. then greps test output for gas costs.
|
||||
|
||||
cd truffle
|
||||
yarn test | grep "estimated gas cost"
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "solium:all",
|
||||
"plugins": [
|
||||
"security"
|
||||
],
|
||||
"rules": {
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"indentation": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"arg-overflow": [
|
||||
"warning",
|
||||
4
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,23 +1,26 @@
|
|||
pragma solidity ^0.4.4;
|
||||
|
||||
|
||||
contract Migrations {
|
||||
address public owner;
|
||||
uint public last_completed_migration;
|
||||
address public owner;
|
||||
uint public last_completed_migration;
|
||||
|
||||
modifier restricted() {
|
||||
if (msg.sender == owner) _;
|
||||
}
|
||||
modifier restricted() {
|
||||
if (msg.sender == owner) {
|
||||
_;
|
||||
}
|
||||
}
|
||||
|
||||
function Migrations() {
|
||||
owner = msg.sender;
|
||||
}
|
||||
function Migrations() public {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
function setCompleted(uint completed) restricted {
|
||||
last_completed_migration = completed;
|
||||
}
|
||||
function setCompleted(uint completed) public restricted {
|
||||
last_completed_migration = completed;
|
||||
}
|
||||
|
||||
function upgrade(address new_address) restricted {
|
||||
Migrations upgraded = Migrations(new_address);
|
||||
upgraded.setCompleted(last_completed_migration);
|
||||
}
|
||||
function upgrade(address newAddress) public restricted {
|
||||
Migrations upgraded = Migrations(newAddress);
|
||||
upgraded.setCompleted(last_completed_migration);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "parity-bridge",
|
||||
"version": "1.0.0",
|
||||
"description": "Bridge between any two ethereum-based networks",
|
||||
"license": "GPL-3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/paritytech/parity-bridge.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/paritytech/parity-bridge/issues"
|
||||
},
|
||||
"homepage": "https://github.com/paritytech/parity-bridge",
|
||||
"devDependencies": {
|
||||
"concurrently": "^3.5.1",
|
||||
"coveralls": "^3.0.0",
|
||||
"ganache-cli": "^6.0.3",
|
||||
"solidity-coverage": "^0.4.8",
|
||||
"solium": "^1.1.2",
|
||||
"truffle": "^4.0.4"
|
||||
},
|
||||
"dependencies": {},
|
||||
"scripts": {
|
||||
"ci": "concurrently \"yarn run solium\" \"yarn run truffle-with-rpc\" \"yarn run solidity-coverage\"",
|
||||
"ganache": "ganache-cli --port 8547",
|
||||
"solidity-coverage": "solidity-coverage",
|
||||
"solium": "solium --dir contracts/",
|
||||
"test": "yarn run truffle-with-rpc",
|
||||
"truffle": "truffle test",
|
||||
"truffle-with-rpc": "concurrently --success first --kill-others \"yarn run ganache\" \"yarn run truffle\""
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
var ForeignBridge = artifacts.require("ForeignBridge");
|
||||
var helpers = require("./helpers/helpers");
|
||||
|
||||
contract('ForeignBridge', function(accounts) {
|
||||
it("should deploy contract", function() {
|
||||
|
@ -39,19 +40,19 @@ contract('ForeignBridge', function(accounts) {
|
|||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var user_account = accounts[2];
|
||||
var userAccount = accounts[2];
|
||||
var value = web3.toWei(1, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
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(user_account, result.logs[0].args.recipient, "Event recipient should be transaction sender");
|
||||
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");
|
||||
return meta.balances.call(user_account);
|
||||
return meta.balances.call(userAccount);
|
||||
}).then(function(result) {
|
||||
assert.equal(value, result, "Contract balance should change");
|
||||
})
|
||||
|
@ -61,105 +62,194 @@ contract('ForeignBridge', function(accounts) {
|
|||
var meta;
|
||||
var requiredSignatures = 2;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var user_account = accounts[2];
|
||||
var userAccount = accounts[2];
|
||||
var value = web3.toWei(1, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result.logs.length, "No event should be created");
|
||||
return meta.balances.call(user_account);
|
||||
return meta.balances.call(userAccount);
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(0, "ether"), result, "Contract balance should not change yet");
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[1] });
|
||||
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(user_account, result.logs[0].args.recipient, "Event recipient should be transaction sender");
|
||||
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");
|
||||
return meta.balances.call(user_account);
|
||||
return meta.balances.call(userAccount);
|
||||
}).then(function(result) {
|
||||
assert.equal(value, result, "Contract balance should change");
|
||||
})
|
||||
})
|
||||
|
||||
it("should not be possible to do same deposit twice for same authority", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
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) {
|
||||
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) {
|
||||
})
|
||||
})
|
||||
|
||||
it("should not allow non-authorities to execute deposit", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
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) {
|
||||
meta = instance;
|
||||
return meta.deposit(userAccount, value, hash, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert(false, "should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("should ignore misbehaving authority when confirming deposit", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 2;
|
||||
var authorities = [accounts[0], accounts[1], accounts[2]];
|
||||
var user_account = accounts[3];
|
||||
var invalid_value = web3.toWei(2, "ether");
|
||||
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) {
|
||||
meta = instance;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result.logs.length, "No event should be created yet");
|
||||
return meta.deposit(user_account, invalid_value, hash, { from: authorities[1] });
|
||||
return meta.deposit(userAccount, invalidValue, hash, { from: authorities[1] });
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result.logs.length, "Misbehaving authority should be ignored");
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[2] })
|
||||
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(user_account, result.logs[0].args.recipient, "Event recipient should be transaction sender");
|
||||
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");
|
||||
return meta.balances.call(user_account);
|
||||
return meta.balances.call(userAccount);
|
||||
}).then(function(result) {
|
||||
assert.equal(value, result, "Contract balance should change");
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow user to transfer value internally", function() {
|
||||
it("should allow user to transfer value locally", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var user_account = accounts[2];
|
||||
var user_account2 = accounts[3];
|
||||
var value = web3.toWei(3, "ether");
|
||||
var value2 = web3.toWei(1, "ether");
|
||||
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;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
// top up balance so we can transfer
|
||||
return meta.deposit(userAccount, user1InitialValue, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(user_account2, value2, false, { from: user_account });
|
||||
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(user_account, result.logs[0].args.from, "Event from should be transaction sender");
|
||||
assert.equal(user_account2, result.logs[0].args.to, "Event from should be transaction recipient");
|
||||
assert.equal(value2, result.logs[0].args.value, "Event value should match transaction value");
|
||||
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(user_account),
|
||||
meta.balances.call(user_account2)
|
||||
meta.balances.call(userAccount),
|
||||
meta.balances.call(userAccount2)
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(2, "ether"), result[0]);
|
||||
assert.equal(web3.toWei(1, "ether"), result[1]);
|
||||
assert.equal(transferedValue, result[1]);
|
||||
})
|
||||
})
|
||||
|
||||
it("should not allow user to transfer value", function() {
|
||||
it("should not allow user to transfer value they don't have either locally or to home", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var user_account = accounts[2];
|
||||
var user_account2 = accounts[3];
|
||||
var value = web3.toWei(3, "ether");
|
||||
var value2 = web3.toWei(4, "ether");
|
||||
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).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
return meta.deposit(userAccount, userValue, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(user_account2, value2, false, { from: user_account });
|
||||
return meta.transferLocal(recipientAccount, transferedValue, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert(false, "Transfer should fail");
|
||||
assert(false, "transferLocal should fail");
|
||||
}, function(err) {
|
||||
return meta.transferHomeViaRelay(recipientAccount, transferedValue, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert(false, "transferHomeViaRelay should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("should fail to transfer 0 value both locally and to home", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var userAccount = accounts[2];
|
||||
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) {
|
||||
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) {
|
||||
})
|
||||
})
|
||||
|
||||
it("should fail to transfer with value overflow both locally and to home", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var userValue = web3.toWei(3, "ether");
|
||||
var transferedvalue = web3.toWei("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "wei");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).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) {
|
||||
})
|
||||
})
|
||||
|
@ -168,24 +258,25 @@ contract('ForeignBridge', function(accounts) {
|
|||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var user_account = accounts[2];
|
||||
var user_account2 = accounts[3];
|
||||
var userAccount = accounts[2];
|
||||
var userAccount2 = accounts[3];
|
||||
var value = web3.toWei(3, "ether");
|
||||
var value2 = web3.toWei(1, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(user_account, value, hash, { from: authorities[0] });
|
||||
// top up balance so we can transfer
|
||||
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(user_account2, value2, true, { from: user_account });
|
||||
return meta.transferHomeViaRelay(userAccount2, value2, { 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(user_account2, result.logs[0].args.recipient, "Event recipient should be equal to transaction recipient");
|
||||
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");
|
||||
return Promise.all([
|
||||
meta.balances.call(user_account),
|
||||
meta.balances.call(user_account2)
|
||||
meta.balances.call(userAccount),
|
||||
meta.balances.call(userAccount2)
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(2, "ether"), result[0]);
|
||||
|
@ -193,29 +284,6 @@ contract('ForeignBridge', function(accounts) {
|
|||
})
|
||||
})
|
||||
|
||||
function sign(address, data) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
web3.eth.sign(address, data, function(err, result) {
|
||||
if (err !== null) {
|
||||
return reject(err);
|
||||
} else {
|
||||
return resolve(normalizeSignature(result));
|
||||
//return resolve(result);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// geth && testrpc has different output of eth_sign than parity
|
||||
// https://github.com/ethereumjs/testrpc/issues/243#issuecomment-326750236
|
||||
function normalizeSignature(signature) {
|
||||
// strip 0x
|
||||
signature = signature.substr(2);
|
||||
|
||||
// increase v by 27...
|
||||
return "0x" + signature.substr(0, 128) + (parseInt(signature.substr(128), 16) + 27).toString(16);
|
||||
}
|
||||
|
||||
it("should successfully submit signature and trigger CollectedSignatures event", function() {
|
||||
var meta;
|
||||
var signature;
|
||||
|
@ -224,7 +292,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
return helpers.sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
signature = result;
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
|
@ -249,7 +317,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
return helpers.sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
|
@ -268,10 +336,10 @@ contract('ForeignBridge', function(accounts) {
|
|||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return Promise.all([
|
||||
sign(authorities[0], message),
|
||||
sign(authorities[1], message),
|
||||
sign(authorities[0], message2),
|
||||
sign(authorities[1], message2),
|
||||
helpers.sign(authorities[0], message),
|
||||
helpers.sign(authorities[1], message),
|
||||
helpers.sign(authorities[0], message2),
|
||||
helpers.sign(authorities[1], message2),
|
||||
]);
|
||||
}).then(function(result) {
|
||||
signatures_for_message.push(result[0]);
|
||||
|
@ -322,7 +390,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
var message = "0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
return helpers.sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
|
@ -340,7 +408,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
var message2 = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
return helpers.sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message2, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
|
@ -357,7 +425,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
return helpers.sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[1] });
|
||||
}).then(function(result) {
|
||||
|
@ -369,21 +437,22 @@ contract('ForeignBridge', function(accounts) {
|
|||
|
||||
it("should not be possible to submit signature twice", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 0;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var signature;
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return sign(authorities[0], message);
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.submitSignature(result, message, { from: authorities[0] });
|
||||
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 (err) {
|
||||
}, function (_) {
|
||||
// nothing
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
// solidity Helpers library
|
||||
var Helpers = artifacts.require("HelpersTest");
|
||||
// testing helpers
|
||||
var helpers = require("./helpers/helpers");
|
||||
|
||||
contract("Helpers", function() {
|
||||
it("`addressArrayContains` should function correctly", function() {
|
||||
var addresses = [
|
||||
"0xd4f04f18d253f831e5b9bcfde7f20450562e03da",
|
||||
"0x46ee1abbcd7215364174f84c3cbc4770d45966e9",
|
||||
"0x5ef98710ff315ded660fe757bf7a861114287c1e",
|
||||
];
|
||||
var otherAddress = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
var library;
|
||||
return Helpers.new().then(function(instance) {
|
||||
library = instance;
|
||||
|
||||
return library.addressArrayContains.call([], otherAddress);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, false, "should return false for empty array");
|
||||
|
||||
return library.addressArrayContains.call([otherAddress], otherAddress);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true, "should return true for singleton array containing value");
|
||||
|
||||
return library.addressArrayContains.call([addresses[0]], addresses[1]);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, false, "should return false for singleton array not containing value");
|
||||
|
||||
return library.addressArrayContains.call(addresses, addresses[0]);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true);
|
||||
|
||||
return library.addressArrayContains.call(addresses, addresses[1]);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true);
|
||||
|
||||
return library.addressArrayContains.call(addresses, addresses[2]);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, true);
|
||||
|
||||
return library.addressArrayContains.call(addresses, otherAddress);
|
||||
}).then(function(result) {
|
||||
assert.equal(result, false);
|
||||
})
|
||||
})
|
||||
|
||||
it("`uintToString` should convert int to string", function() {
|
||||
var numbersFrom1To100 = helpers.range(1, 101);
|
||||
var library;
|
||||
return Helpers.new().then(function(instance) {
|
||||
library = instance;
|
||||
|
||||
return library.uintToString.call(0)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, "0");
|
||||
|
||||
return Promise.all(numbersFrom1To100.map(function(number) {
|
||||
return library.uintToString.call(number);
|
||||
}));
|
||||
}).then(function(result) {
|
||||
assert.deepEqual(result, numbersFrom1To100.map(function(number) {
|
||||
return number.toString();
|
||||
}), "should convert numbers from 1 to 100 correctly");
|
||||
|
||||
return library.uintToString.estimateGas(1);
|
||||
}).then(function(result) {
|
||||
console.log("estimated gas cost of Helpers.uintToString(1)", result);
|
||||
|
||||
return library.uintToString.call(1234)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, "1234");
|
||||
|
||||
return library.uintToString.call(12345678)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, "12345678");
|
||||
|
||||
return library.uintToString.estimateGas(12345678)
|
||||
}).then(function(result) {
|
||||
console.log("estimated gas cost of Helpers.uintToString(12345678)", result);
|
||||
|
||||
return library.uintToString.call(web3.toBigNumber("131242344353464564564574574567456"));
|
||||
}).then(function(result) {
|
||||
assert.equal(result, "131242344353464564564574574567456");
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,104 @@
|
|||
// returns a Promise that resolves with a hex string that is the signature of
|
||||
// `data` signed with the key of `address`
|
||||
function sign(address, data) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
web3.eth.sign(address, data, function(err, result) {
|
||||
if (err !== null) {
|
||||
return reject(err);
|
||||
} else {
|
||||
return resolve(normalizeSignature(result));
|
||||
//return resolve(result);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
module.exports.sign = sign;
|
||||
|
||||
// geth && testrpc has different output of eth_sign than parity
|
||||
// https://github.com/ethereumjs/testrpc/issues/243#issuecomment-326750236
|
||||
function normalizeSignature(signature) {
|
||||
signature = strip0x(signature);
|
||||
|
||||
// increase v by 27...
|
||||
return "0x" + signature.substr(0, 128) + (parseInt(signature.substr(128), 16) + 27).toString(16);
|
||||
}
|
||||
module.exports.normalizeSignature = normalizeSignature;
|
||||
|
||||
// strips leading "0x" if present
|
||||
function strip0x(input) {
|
||||
return input.replace(/^0x/, "");
|
||||
}
|
||||
module.exports.strip0x = strip0x;
|
||||
|
||||
// extracts and returns the `v`, `r` and `s` values from a `signature`.
|
||||
// all inputs and outputs are hex strings with leading '0x'.
|
||||
function signatureToVRS(signature) {
|
||||
assert.equal(signature.length, 2 + 32 * 2 + 32 * 2 + 2);
|
||||
signature = strip0x(signature);
|
||||
var v = parseInt(signature.substr(64 * 2), 16);
|
||||
var r = "0x" + signature.substr(0, 32 * 2);
|
||||
var s = "0x" + signature.substr(32 * 2, 32 * 2);
|
||||
return {v: v, r: r, s: s};
|
||||
}
|
||||
module.exports.signatureToVRS = signatureToVRS;
|
||||
|
||||
// returns BigNumber `num` converted to a little endian hex string
|
||||
// that is exactly 32 bytes long.
|
||||
// `num` must represent an unsigned integer
|
||||
function bigNumberToPaddedBytes32(num) {
|
||||
assert(web3._extend.utils.isBigNumber(num));
|
||||
assert(num.isInteger());
|
||||
assert(!num.isNegative());
|
||||
var result = strip0x(num.toString(16));
|
||||
while (result.length < 64) {
|
||||
result = "0" + result;
|
||||
}
|
||||
return "0x" + result;
|
||||
}
|
||||
module.exports.bigNumberToPaddedBytes32 = bigNumberToPaddedBytes32;
|
||||
|
||||
// returns an promise that resolves to an object
|
||||
// that maps `addresses` to their current balances
|
||||
function getBalances(addresses) {
|
||||
return Promise.all(addresses.map(function(address) {
|
||||
return web3.eth.getBalance(address);
|
||||
})).then(function(balancesArray) {
|
||||
let addressToBalance = {};
|
||||
addresses.forEach(function(address, index) {
|
||||
addressToBalance[address] = balancesArray[index];
|
||||
});
|
||||
return addressToBalance;
|
||||
})
|
||||
}
|
||||
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) {
|
||||
web3._extend.utils.isBigNumber(value);
|
||||
recipient = strip0x(recipient);
|
||||
assert.equal(recipient.length, 20 * 2);
|
||||
|
||||
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;
|
||||
assert.equal(message.length, expectedMessageLength);
|
||||
return message;
|
||||
}
|
||||
module.exports.createMessage = createMessage;
|
||||
|
||||
// returns array of integers progressing from `start` up to, but not including, `end`
|
||||
function range(start, end) {
|
||||
var result = [];
|
||||
for (var i = start; i < end; i++) {
|
||||
result.push(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
module.exports.range = range;
|
|
@ -1,12 +1,18 @@
|
|||
var HomeBridge = artifacts.require("HomeBridge");
|
||||
var helpers = require("./helpers/helpers");
|
||||
|
||||
contract('HomeBridge', function(accounts) {
|
||||
it("should deploy contract", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var estimatedGasCostOfWithdraw = 0;
|
||||
|
||||
return HomeBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.requiredSignatures.call();
|
||||
}).then(function(result) {
|
||||
|
@ -26,9 +32,9 @@ contract('HomeBridge', function(accounts) {
|
|||
})
|
||||
})
|
||||
|
||||
it("should fail to deploy contract with to many signatures", function() {
|
||||
it("should fail to deploy contract with too many signatures", function() {
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
return HomeBridge.new(3, authorities).then(function(_) {
|
||||
return HomeBridge.new(3, authorities, 0).then(function(_) {
|
||||
assert(false, "Contract should fail to deploy");
|
||||
}, function(err) {
|
||||
// do nothing
|
||||
|
@ -39,20 +45,538 @@ contract('HomeBridge', function(accounts) {
|
|||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
let user_account = accounts[2];
|
||||
var estimatedGasCostOfWithdraw = 0;
|
||||
let userAccount = accounts[2];
|
||||
let value = web3.toWei(1, "ether");
|
||||
|
||||
return HomeBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.sendTransaction({
|
||||
value: value,
|
||||
from: user_account
|
||||
from: userAccount
|
||||
})
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should have been created");
|
||||
assert.equal("Deposit", result.logs[0].event, "Event name should be Deposit");
|
||||
assert.equal(user_account, result.logs[0].args.recipient, "Event recipient should be transaction sender");
|
||||
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");
|
||||
})
|
||||
})
|
||||
|
||||
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;
|
||||
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 message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
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.estimateGas(
|
||||
[vrs.v],
|
||||
[vrs.r],
|
||||
[vrs.s],
|
||||
message,
|
||||
{from: authorities[0]}
|
||||
);
|
||||
}).then(function(result) {
|
||||
console.log("estimated gas cost of HomeBridge.withdraw =", result);
|
||||
|
||||
var vrs = helpers.signatureToVRS(signature);
|
||||
return homeBridge.withdraw(
|
||||
[vrs.v],
|
||||
[vrs.r],
|
||||
[vrs.s],
|
||||
message,
|
||||
// anyone can call withdraw (provided they have the message and required signatures)
|
||||
{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(recipientAccount, result.logs[0].args.recipient, "Event recipient should match recipient in message");
|
||||
assert(value.equals(result.logs[0].args.value), "Event value should match value in message");
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow correct withdraw with recipient paying caller for gas", function() {
|
||||
var homeBridge;
|
||||
var initialBalances;
|
||||
var signature;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var estimatedGasCostOfWithdraw = web3.toBigNumber(100000);
|
||||
var actualGasCostOfWithdraw;
|
||||
var gasPrice;
|
||||
var transactionResult;
|
||||
var relayCost;
|
||||
var relayerAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var chargerAccount = accounts[4];
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
|
||||
return helpers.getBalances(accounts);
|
||||
}).then(function(result) {
|
||||
initialBalances = result;
|
||||
|
||||
// "charge" HomeBridge so we can withdraw later
|
||||
return homeBridge.sendTransaction({
|
||||
value: value,
|
||||
from: chargerAccount,
|
||||
})
|
||||
}).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,
|
||||
// anyone can call withdraw (provided they have the message and required signatures)
|
||||
{ from: relayerAccount }
|
||||
);
|
||||
}).then(function(result) {
|
||||
transactionResult = result;
|
||||
actualGasCostOfWithdraw = web3.toBigNumber(result.receipt.gasUsed);
|
||||
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;
|
||||
relayCost = gasPrice.times(estimatedGasCostOfWithdraw);
|
||||
|
||||
assert.equal(1, transactionResult.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Withdraw", transactionResult.logs[0].event, "Event name should be Withdraw");
|
||||
assert.equal(recipientAccount, transactionResult.logs[0].args.recipient, "Event recipient should match recipient in message");
|
||||
assert(value.minus(relayCost).equals(transactionResult.logs[0].args.value), "Event value should match value in message minus relay cost");
|
||||
|
||||
return helpers.getBalances(accounts);
|
||||
}).then(function(balances) {
|
||||
let actualWeiCostOfWithdraw = actualGasCostOfWithdraw.times(gasPrice);
|
||||
assert(
|
||||
balances[recipientAccount].equals(
|
||||
initialBalances[recipientAccount].plus(value.minus(relayCost))),
|
||||
"Recipient received value minus relay cost");
|
||||
assert(
|
||||
balances[relayerAccount].equals(
|
||||
initialBalances[relayerAccount]
|
||||
.minus(actualWeiCostOfWithdraw)
|
||||
.plus(relayCost)),
|
||||
"Relayer received relay cost");
|
||||
})
|
||||
})
|
||||
|
||||
it("should revert withdraw if value is insufficient to cover costs", function() {
|
||||
var homeBridge;
|
||||
var initialBalances;
|
||||
var signature;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var estimatedGasCostOfWithdraw = web3.toBigNumber(100000);
|
||||
var relayerAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var chargerAccount = accounts[4];
|
||||
var value = estimatedGasCostOfWithdraw;
|
||||
var message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
|
||||
return helpers.getBalances(accounts);
|
||||
}).then(function(result) {
|
||||
initialBalances = result;
|
||||
|
||||
// "charge" HomeBridge so we can withdraw later
|
||||
return homeBridge.sendTransaction({
|
||||
value: value,
|
||||
from: chargerAccount,
|
||||
})
|
||||
}).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,
|
||||
// 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
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow second withdraw with different transactionHash but same recipient and value", function() {
|
||||
var homeBridge;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
let estimatedGasCostOfWithdraw = 0;
|
||||
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");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
// "charge" HomeBridge so we can withdraw later
|
||||
return homeBridge.sendTransaction({
|
||||
value: value.times(2),
|
||||
from: userAccount
|
||||
})
|
||||
}).then(function(result) {
|
||||
return helpers.sign(authorities[0], message1);
|
||||
}).then(function(signature) {
|
||||
var vrs = helpers.signatureToVRS(signature);
|
||||
return homeBridge.withdraw(
|
||||
[vrs.v],
|
||||
[vrs.r],
|
||||
[vrs.s],
|
||||
message1,
|
||||
{from: authorities[0]}
|
||||
);
|
||||
}).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(recipientAccount, result.logs[0].args.recipient, "Event recipient should match recipient in message");
|
||||
assert(value.equals(result.logs[0].args.value), "Event value should match value in message");
|
||||
|
||||
return helpers.sign(authorities[0], message2);
|
||||
}).then(function(signature) {
|
||||
var vrs = helpers.signatureToVRS(signature);
|
||||
return homeBridge.withdraw(
|
||||
[vrs.v],
|
||||
[vrs.r],
|
||||
[vrs.s],
|
||||
message2,
|
||||
{from: authorities[0]}
|
||||
);
|
||||
}).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(recipientAccount, result.logs[0].args.recipient, "Event recipient should match recipient in message");
|
||||
assert(value.equals(result.logs[0].args.value), "Event value should match value in message");
|
||||
})
|
||||
})
|
||||
|
||||
it("should not allow second withdraw (replay attack) with same transactionHash but different recipient and value", function() {
|
||||
var homeBridge;
|
||||
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 message1 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
var message2 = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
// "charge" HomeBridge so we can withdraw later
|
||||
return homeBridge.sendTransaction({
|
||||
value: value.times(2),
|
||||
from: userAccount
|
||||
})
|
||||
}).then(function(result) {
|
||||
return helpers.sign(authorities[0], message1);
|
||||
}).then(function(signature) {
|
||||
var vrs = helpers.signatureToVRS(signature);
|
||||
return homeBridge.withdraw(
|
||||
[vrs.v],
|
||||
[vrs.r],
|
||||
[vrs.s],
|
||||
message1,
|
||||
{from: authorities[0]}
|
||||
);
|
||||
}).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(recipientAccount, result.logs[0].args.recipient, "Event recipient should match recipient in message");
|
||||
assert(value.equals(result.logs[0].args.value), "Event value should match value in message");
|
||||
|
||||
return helpers.sign(authorities[0], message2);
|
||||
}).then(function(signature) {
|
||||
var vrs = helpers.signatureToVRS(signature);
|
||||
return homeBridge.withdraw(
|
||||
[vrs.v],
|
||||
[vrs.r],
|
||||
[vrs.s],
|
||||
message2,
|
||||
{from: authorities[0]}
|
||||
);
|
||||
}).then(function(result) {
|
||||
assert(false, "should fail");
|
||||
}, function (err) {
|
||||
// nothing
|
||||
})
|
||||
})
|
||||
|
||||
it("withdraw without funds on HomeBridge should fail", 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 message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
return HomeBridge.new(
|
||||
requiredSignatures,
|
||||
authorities,
|
||||
estimatedGasCostOfWithdraw
|
||||
).then(function(instance) {
|
||||
homeBridge = instance;
|
||||
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.substr(0, 83),
|
||||
{from: authorities[0]}
|
||||
);
|
||||
}).then(function(result) {
|
||||
assert(false, "should fail");
|
||||
}, function (err) {
|
||||
// nothing
|
||||
})
|
||||
})
|
||||
|
||||
it("should not allow withdraw with message.length != 84", 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 message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
// make message too short
|
||||
message = message.substr(0, 83);
|
||||
|
||||
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,
|
||||
// anyone can call withdraw (provided they have the message and required signatures)
|
||||
{from: userAccount}
|
||||
);
|
||||
}).then(function(result) {
|
||||
assert(false, "withdraw should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("withdraw should fail if not enough signatures are provided", function() {
|
||||
var homeBridge;
|
||||
var signature;
|
||||
var requiredSignatures = 2;
|
||||
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 message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
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,
|
||||
// anyone can call withdraw (provided they have the message and required signatures)
|
||||
{from: userAccount}
|
||||
);
|
||||
}).then(function(result) {
|
||||
assert(false, "should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("withdraw should fail if duplicate signature is provided", function() {
|
||||
var homeBridge;
|
||||
var signature;
|
||||
var requiredSignatures = 2;
|
||||
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 message = helpers.createMessage(recipientAccount, value, "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80");
|
||||
|
||||
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.v],
|
||||
[vrs.r, vrs.r],
|
||||
[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) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
var Message = artifacts.require("MessageTest");
|
||||
var helpers = require("./helpers/helpers");
|
||||
|
||||
contract("Message", function() {
|
||||
var recipientAccount = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
var value = web3.toBigNumber(web3.toWei(1, "ether"));
|
||||
var transactionHash = "0x1045bfe274b88120a6b1e5d01b5ec00ab5d01098346e90e7c7a3c9b8f0181c80";
|
||||
var message = helpers.createMessage(recipientAccount, value, transactionHash);
|
||||
|
||||
it("should extract value", function() {
|
||||
return Message.new().then(function(instance) {
|
||||
return instance.getValue.call(message)
|
||||
}).then(function(result) {
|
||||
assert(result.equals(value));
|
||||
})
|
||||
})
|
||||
|
||||
it("should extract recipient", function() {
|
||||
return Message.new().then(function(instance) {
|
||||
return instance.getRecipient.call(message)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, recipientAccount);
|
||||
})
|
||||
})
|
||||
|
||||
it("should extract transactionHash", function() {
|
||||
return Message.new().then(function(instance) {
|
||||
return instance.getTransactionHash.call(message)
|
||||
}).then(function(result) {
|
||||
assert.equal(result, transactionHash);
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,40 @@
|
|||
var MessageSigning = artifacts.require("MessageSigningTest");
|
||||
|
||||
contract("MessageSigning", function() {
|
||||
it("should recover address from signed message", function() {
|
||||
var signature = "0xb585c41f3cceb2ff9b5c033f2edbefe93415bde365489c989bad8cef3b18e38148a13e100608a29735d709fe708926d37adcecfffb32b1d598727028a16df5db1b";
|
||||
var message = "0xdeadbeaf";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return MessageSigning.new().then(function(instance) {
|
||||
return instance.recoverAddressFromSignedMessage.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
|
||||
it("should recover address from long signed message", function() {
|
||||
var signature = "0x3c9158597e22fa43fcc6636399c560441808e1d8496de0108e401a2ad71022b15d1191cf3c96e06759601c8e00ce7f03f350c12b19d0a8ba3ab3c07a71063f2b1c";
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return MessageSigning.new().then(function(instance) {
|
||||
return instance.recoverAddressFromSignedMessage.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
|
||||
it("should fail to recover address from signature that is too short", function() {
|
||||
var signature = "0x3c9158597e22fa43fcc6636399c560441808e1d8496de0108e401a2ad71022b15d1191cf3c96e06759601c8e00ce7f03f350c12b19d0a8ba3ab3c07a71063f2b";
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
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) {
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,27 +0,0 @@
|
|||
var Signer = artifacts.require("SignerTest");
|
||||
|
||||
contract("Signer", function() {
|
||||
it("should validate signature", function() {
|
||||
var signature = "0xb585c41f3cceb2ff9b5c033f2edbefe93415bde365489c989bad8cef3b18e38148a13e100608a29735d709fe708926d37adcecfffb32b1d598727028a16df5db1b";
|
||||
var message = "0xdeadbeaf";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return Signer.new().then(function(instance) {
|
||||
return instance.signer.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
|
||||
it("should validate signature for long message", function() {
|
||||
var signature = "0x3c9158597e22fa43fcc6636399c560441808e1d8496de0108e401a2ad71022b15d1191cf3c96e06759601c8e00ce7f03f350c12b19d0a8ba3ab3c07a71063f2b1c";
|
||||
var message = "0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
|
||||
var account = "0x006e27b6a72e1f34c626762f3c4761547aff1421";
|
||||
|
||||
return Signer.new().then(function(instance) {
|
||||
return instance.signer.call(signature, message)
|
||||
}).then(function(result) {
|
||||
assert.equal(account, result);
|
||||
})
|
||||
})
|
||||
})
|
|
@ -2,8 +2,8 @@ module.exports = {
|
|||
networks: {
|
||||
development: {
|
||||
host: "localhost",
|
||||
port: 8545,
|
||||
network_id: "*" // Match any network id
|
||||
port: 8547,
|
||||
network_id: "*", // Match any network id
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue