Merge pull request #86 from paritytech/snd-issue85-erc20
#85 (ERC20 for ForeignBridge) and more
This commit is contained in:
commit
16ced48292
|
@ -329,7 +329,6 @@ dependencies = [
|
|||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jsonrpc-core 7.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pretty_assertions 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
115
README.md
115
README.md
|
@ -8,84 +8,118 @@
|
|||
[coveralls-image]: https://coveralls.io/repos/github/paritytech/parity-bridge/badge.svg?branch=master
|
||||
[coveralls-url]: https://coveralls.io/github/paritytech/parity-bridge?branch=master
|
||||
|
||||
bridge between two ethereum blockchains, `home` and `foreign`.
|
||||
the bridge is an
|
||||
[ERC20 token](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)
|
||||
contract that is backed by ether on **another** ethereum blockchain.
|
||||
|
||||
users can convert ether
|
||||
one one chain into the same amount of ERC20 tokens on the other and back.
|
||||
the bridge securely relays these conversions.
|
||||
|
||||
**the bridge can solve scaling issues:**
|
||||
by deploying a [proof-of-authority](https://paritytech.github.io/wiki/Proof-of-Authority-Chains.html)
|
||||
network and bridging it to mainnet users can convert their mainnet ether
|
||||
into ERC20 tokens on the PoA chain
|
||||
and there transfer them with much lower transaction fees,
|
||||
faster block times and unaffected by mainnet congestion.
|
||||
|
||||
the users can withdraw their tokens worth of ether on the mainnet at any point.
|
||||
|
||||
parity is using the bridge project to prototype
|
||||
the system that will eventually connect ethereum and other non-parachains to
|
||||
[polkadot](https://polkadot.io/).
|
||||
|
||||
### next steps
|
||||
|
||||
1. deploy to bridge **ethereum** and **kovan** with the kovan authorities being the fixed set of bridge authorities
|
||||
2. make the bridge work with contract-based dynamic validator sets
|
||||
3. after kovan hardfork 2: deploy to kovan again with dynamic validator set
|
||||
|
||||
### current functionality
|
||||
|
||||
the bridge allows users to deposit ether into a smart contract on `home` and get it on `foreign` in form of a token balance.
|
||||
it also allows users to withdraw their tokens on `foreign` and get the equivalent ether on `home`.
|
||||
on `foreign` users can freely transfer tokens between each other.
|
||||
the bridge connects two chains `home` and `foreign`.
|
||||
|
||||
when users deposit ether into the `HomeBridge` contract on `home`
|
||||
they get the same amount of ERC20 tokens on `foreign`.
|
||||
|
||||
[they can use `ForeignBridge` as they would use any ERC20 token.](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)
|
||||
|
||||
to convert their `foreign` ERC20 into ether on `home`
|
||||
users can always call `ForeignBridge.transferHomeViaRelay(homeRecipientAddress, value)`.
|
||||
|
||||
`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`
|
||||
### highlevel explanation of home ether -> foreign ERC20 relay
|
||||
|
||||
`sender` deposits `value` into `HomeBridge`.
|
||||
the `HomeBridge` fallback function emits `Deposit(sender, value)`.
|
||||
for each `Deposit` event on `HomeBridge` every authority makes a transaction
|
||||
|
||||
for each `Deposit` event on `HomeBridge` every authority executes
|
||||
`ForeignBridge.deposit(sender, value, transactionHash)`.
|
||||
|
||||
once there are `ForeignBridge.requiredSignatures` such transactions
|
||||
with identical arguments and from distinct authorities then
|
||||
`ForeignBridge.balances(sender)` is increased by `value` and
|
||||
`ForeignBridge.Deposit(sender, value)` is emitted.
|
||||
`ForeignBridge.balanceOf(sender)` is increased by `value`.
|
||||
|
||||
### withdraw balance on `ForeignBridge` and get it as ether on `home` chain
|
||||
### highlevel explanation of foreign ERC20 -> home ether relay
|
||||
|
||||
`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)`.
|
||||
signs the message and executes `ForeignBridge.submitSignature(signature, message)`.
|
||||
this collection of signatures on `foreign` is necessary because transactions are free
|
||||
for authorities on `foreign`, since they are the authorities of `foreign`, but not free on `home`.
|
||||
|
||||
once `ForeignBridge.requiredSignatures` signatures by distinct authorities are collected
|
||||
a `ForeignBridge.CollectedSignatures(authorityThatSubmittedLastSignature, messageHash)` event is emitted.
|
||||
|
||||
everyone (usually `authorityThatSubmittedLastSignature`) can then call `ForeignBridge.message(messageHash)` and
|
||||
`ForeignBridge.signature(messageHash, 0..requiredSignatures)`
|
||||
to look up the message and signatures and execute `HomeBridge.withdraw(vs, rs, ss, message)`
|
||||
and complete the withdraw.
|
||||
|
||||
### transfer on `foreign`
|
||||
`HomeBridge.withdraw(vs, rs, ss, message)` recovers the addresses from the signatures,
|
||||
checks that enough authorities in its authority list have signed and
|
||||
finally transfers `value` ether ([minus the relay gas costs](#recipient-pays-relay-cost-to-relaying-authority))
|
||||
to `recipient`.
|
||||
|
||||
`sender` executes `ForeignBridge.transferLocal(recipient, value)`
|
||||
which checks and reduces `ForeignBridge.balances(sender)` and increases `ForeignBridge.balances(recipient)`
|
||||
by `value`.
|
||||
### run truffle smart contract tests
|
||||
|
||||
requires `yarn` to be `$PATH`. [installation instructions](https://yarnpkg.com/lang/en/docs/install/)
|
||||
|
||||
```
|
||||
cd truffle
|
||||
yarn test
|
||||
```
|
||||
|
||||
### build
|
||||
|
||||
requires `solc` to be in `$PATH`. [installation instructions](https://solidity.readthedocs.io/en/develop/installing-solidity.html)
|
||||
requires `rust` and `cargo`: [installation instructions.](https://www.rust-lang.org/en-US/install.html)
|
||||
|
||||
requires `solc` to be in `$PATH`: [installation instructions.](https://solidity.readthedocs.io/en/develop/installing-solidity.html)
|
||||
|
||||
assuming you've cloned the bridge (`git clone git@github.com:paritytech/parity-bridge.git`)
|
||||
and are in the project directory (`cd parity-bridge`) run:
|
||||
|
||||
```
|
||||
cargo build -p bridge-cli --release
|
||||
```
|
||||
|
||||
### cli options
|
||||
to install copy `../target/release/bridge` into a folder that's in your `$PATH`.
|
||||
|
||||
### run
|
||||
|
||||
```
|
||||
Ethereum-Kovan bridge.
|
||||
Copyright 2017 Parity Technologies (UK) Limited
|
||||
|
||||
Usage:
|
||||
bridge --config <config> --database <database>
|
||||
bridge -h | --help
|
||||
|
||||
Options:
|
||||
-h, --help Display help message and exit.
|
||||
bridge --config config.toml --database db.toml
|
||||
```
|
||||
|
||||
- `--config` - location of the configuration file. configuration file must exist
|
||||
- `--database` - location of the database file. if there is no file at specified location, new bridge contracts will be deployed and new database will be created
|
||||
- `--database` - location of the database file.
|
||||
if there is no file at specified location, new bridge contracts will be deployed
|
||||
and new database will be created
|
||||
|
||||
### configuration [file example](./examples/config.toml)
|
||||
|
||||
|
@ -207,15 +241,6 @@ checked_withdraw_confirm = 121
|
|||
|
||||
![withdraw](./res/withdraw.png)
|
||||
|
||||
### truffle tests
|
||||
|
||||
requires `yarn` to be `$PATH`. [installation instructions](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
|
||||
|
|
|
@ -73,6 +73,7 @@ impl<T: Transport> Stream for DepositRelay<T> {
|
|||
let next_state = match self.state {
|
||||
DepositRelayState::Wait => {
|
||||
let item = try_stream!(self.logs.poll());
|
||||
info!("got {} new deposits to relay", item.logs.len());
|
||||
let deposits = item.logs
|
||||
.into_iter()
|
||||
.map(|log| deposit_relay_payload(&self.app.home_bridge, &self.app.foreign_bridge, log))
|
||||
|
@ -95,6 +96,7 @@ impl<T: Transport> Stream for DepositRelay<T> {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
info!("relaying {} deposits", deposits.len());
|
||||
DepositRelayState::RelayDeposits {
|
||||
future: join_all(deposits),
|
||||
block: item.to,
|
||||
|
@ -102,6 +104,7 @@ impl<T: Transport> Stream for DepositRelay<T> {
|
|||
},
|
||||
DepositRelayState::RelayDeposits { ref mut future, block } => {
|
||||
let _ = try_ready!(future.poll());
|
||||
info!("deposit relay completed");
|
||||
DepositRelayState::Yield(Some(block))
|
||||
},
|
||||
DepositRelayState::Yield(ref mut block) => match block.take() {
|
||||
|
|
|
@ -37,7 +37,7 @@ fn signatures_payload(foreign: &foreign::ForeignBridge, required_signatures: u32
|
|||
data: log.data.0,
|
||||
};
|
||||
let collected_signatures = foreign.events().collected_signatures().parse_log(raw_log)?;
|
||||
if collected_signatures.authority != my_address.0 {
|
||||
if collected_signatures.authority_responsible_for_relay != my_address.0 {
|
||||
info!("bridge not responsible for relaying transaction to home. tx hash: {}", log.transaction_hash.unwrap());
|
||||
// this authority is not responsible for relaying this transaction.
|
||||
// someone else will relay this transaction to home.
|
||||
|
|
|
@ -259,57 +259,127 @@ contract HomeBridge {
|
|||
|
||||
|
||||
contract ForeignBridge {
|
||||
// following is the part of ForeignBridge that implements an ERC20 token.
|
||||
// ERC20 spec: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20-token-standard.md
|
||||
|
||||
uint public totalSupply;
|
||||
|
||||
string public name = "ForeignBridge";
|
||||
|
||||
/// maps addresses to their token balances
|
||||
mapping (address => uint) public balances;
|
||||
|
||||
// owner of account approves the transfer of an amount by another account
|
||||
mapping(address => mapping (address => uint)) allowed;
|
||||
|
||||
/// Event created on money transfer
|
||||
event Transfer(address indexed from, address indexed to, uint tokens);
|
||||
|
||||
// returns the ERC20 token balance of the given address
|
||||
function balanceOf(address tokenOwner) public view returns (uint) {
|
||||
return balances[tokenOwner];
|
||||
}
|
||||
|
||||
/// Transfer `value` to `recipient` on this `foreign` chain.
|
||||
///
|
||||
/// does not affect `home` chain. does not do a relay.
|
||||
/// as specificed in ERC20 this doesn't fail if tokens == 0.
|
||||
function transfer(address recipient, uint tokens) public returns (bool) {
|
||||
require(balances[msg.sender] >= tokens);
|
||||
// fails if there is an overflow
|
||||
require(balances[recipient] + tokens >= balances[recipient]);
|
||||
|
||||
balances[msg.sender] -= tokens;
|
||||
balances[recipient] += tokens;
|
||||
Transfer(msg.sender, recipient, tokens);
|
||||
return true;
|
||||
}
|
||||
|
||||
// following is the part of ForeignBridge that is concerned
|
||||
// with the part of the ERC20 standard responsible for giving others spending rights
|
||||
// and spending others tokens
|
||||
|
||||
// created when `approve` is executed to mark that
|
||||
// `tokenOwner` has approved `spender` to spend `tokens` of his tokens
|
||||
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
|
||||
|
||||
// allow `spender` to withdraw from your account, multiple times, up to the `tokens` amount.
|
||||
// calling this function repeatedly overwrites the current allowance.
|
||||
function approve(address spender, uint tokens) public returns (bool) {
|
||||
allowed[msg.sender][spender] = tokens;
|
||||
Approval(msg.sender, spender, tokens);
|
||||
return true;
|
||||
}
|
||||
|
||||
// returns how much `spender` is allowed to spend of `owner`s tokens
|
||||
function allowance(address owner, address spender) public view returns (uint256) {
|
||||
return allowed[owner][spender];
|
||||
}
|
||||
|
||||
function transferFrom(address from, address to, uint tokens) public returns (bool) {
|
||||
// `from` has enough tokens
|
||||
require(balances[from] >= tokens);
|
||||
// `sender` is allowed to move `tokens` from `from`
|
||||
require(allowed[from][msg.sender] >= tokens);
|
||||
// fails if there is an overflow
|
||||
require(balances[to] + tokens >= balances[to]);
|
||||
|
||||
balances[to] += tokens;
|
||||
balances[from] -= tokens;
|
||||
allowed[from][msg.sender] -= tokens;
|
||||
|
||||
Transfer(from, to, tokens);
|
||||
return true;
|
||||
}
|
||||
|
||||
// following is the part of ForeignBridge that is
|
||||
// no longer part of ERC20 and is concerned with
|
||||
// with moving tokens from and to HomeBridge
|
||||
|
||||
struct SignaturesCollection {
|
||||
/// Signed message.
|
||||
bytes message;
|
||||
/// Authorities who signed the message.
|
||||
address[] signed;
|
||||
/// Signaturs
|
||||
/// Signatures
|
||||
bytes[] signatures;
|
||||
}
|
||||
|
||||
/// Number of authorities signatures required to withdraw the money.
|
||||
///
|
||||
/// Must be lesser than number of authorities.
|
||||
/// Must be less than number of authorities.
|
||||
uint public requiredSignatures;
|
||||
|
||||
/// Contract authorities.
|
||||
address[] public authorities;
|
||||
|
||||
/// Ether balances
|
||||
mapping (address => uint) public balances;
|
||||
|
||||
/// Pending deposits and authorities who confirmed them
|
||||
mapping (bytes32 => address[]) deposits;
|
||||
|
||||
/// Pending signatures and authorities who confirmed them
|
||||
mapping (bytes32 => SignaturesCollection) signatures;
|
||||
|
||||
/// Event created on money deposit.
|
||||
/// triggered when relay of deposit from HomeBridge is complete
|
||||
event Deposit(address recipient, uint value);
|
||||
|
||||
/// Event created on money withdraw.
|
||||
event Withdraw(address recipient, uint value);
|
||||
|
||||
/// Event created on money transfer
|
||||
event Transfer(address from, address to, uint value);
|
||||
|
||||
/// Collected signatures which should be relayed to home chain.
|
||||
event CollectedSignatures(address authority, bytes32 messageHash);
|
||||
event CollectedSignatures(address authorityResponsibleForRelay, bytes32 messageHash);
|
||||
|
||||
/// Constructor.
|
||||
function ForeignBridge(
|
||||
uint requiredSignaturesParam,
|
||||
address[] authoritiesParam
|
||||
uint _requiredSignatures,
|
||||
address[] _authorities
|
||||
) public
|
||||
{
|
||||
require(requiredSignaturesParam != 0);
|
||||
require(requiredSignaturesParam <= authoritiesParam.length);
|
||||
requiredSignatures = requiredSignaturesParam;
|
||||
authorities = authoritiesParam;
|
||||
require(_requiredSignatures != 0);
|
||||
require(_requiredSignatures <= _authorities.length);
|
||||
requiredSignatures = _requiredSignatures;
|
||||
authorities = _authorities;
|
||||
}
|
||||
|
||||
/// Multisig authority validation
|
||||
/// require that sender is an authority
|
||||
modifier onlyAuthority() {
|
||||
require(Helpers.addressArrayContains(authorities, msg.sender));
|
||||
_;
|
||||
|
@ -321,16 +391,22 @@ contract ForeignBridge {
|
|||
/// deposit value (uint)
|
||||
/// mainnet transaction hash (bytes32) // to avoid transaction duplication
|
||||
function deposit(address recipient, uint value, bytes32 transactionHash) public onlyAuthority() {
|
||||
// Protection from misbehaing authority
|
||||
// Protection from misbehaving authority
|
||||
var hash = keccak256(recipient, value, transactionHash);
|
||||
|
||||
// Duplicated deposits
|
||||
// don't allow authority to confirm deposit twice
|
||||
require(!Helpers.addressArrayContains(deposits[hash], msg.sender));
|
||||
|
||||
deposits[hash].push(msg.sender);
|
||||
// TODO: this may cause troubles if requriedSignatures len is changed
|
||||
// TODO: this may cause troubles if requiredSignatures len is changed
|
||||
if (deposits[hash].length == requiredSignatures) {
|
||||
balances[recipient] += value;
|
||||
// mints tokens
|
||||
totalSupply += value;
|
||||
// ERC20 specifies: a token contract which creates new tokens
|
||||
// SHOULD trigger a Transfer event with the _from address
|
||||
// set to 0x0 when tokens are created.
|
||||
Transfer(0x0, recipient, value);
|
||||
Deposit(recipient, value);
|
||||
}
|
||||
}
|
||||
|
@ -346,26 +422,19 @@ contract ForeignBridge {
|
|||
/// which transfers `value - relayCost` to `recipient` completing the transfer.
|
||||
function transferHomeViaRelay(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]);
|
||||
// don't allow 0 value transfers to home
|
||||
require(value > 0);
|
||||
|
||||
balances[msg.sender] -= value;
|
||||
// burns tokens
|
||||
totalSupply -= value;
|
||||
// in line with the transfer event from `0x0` on token creation
|
||||
// recommended by ERC20 (see implementation of `deposit` above)
|
||||
// we trigger a Transfer event to `0x0` on token destruction
|
||||
Transfer(msg.sender, 0x0, 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.
|
||||
|
|
|
@ -23,8 +23,8 @@ accounts = [
|
|||
required_signatures = 1
|
||||
|
||||
[transactions]
|
||||
home_deploy = { gas = 1000000 }
|
||||
home_deploy = { gas = 3000000 }
|
||||
foreign_deploy = { gas = 3000000 }
|
||||
deposit_relay = { gas = 100000 }
|
||||
deposit_relay = { gas = 3000000 }
|
||||
withdraw_relay = { gas = 3000000 }
|
||||
withdraw_confirm = { gas = 3000000 }
|
||||
|
|
|
@ -60,6 +60,10 @@ fn parity_foreign_command() -> Command {
|
|||
command
|
||||
}
|
||||
|
||||
fn address_from_str(string: &'static str) -> web3::types::Address {
|
||||
web3::types::Address::from(&Address::from(string).0[..])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_deposit_then_withdraw() {
|
||||
if Path::new(TMP_PATH).exists() {
|
||||
|
@ -74,7 +78,7 @@ fn test_basic_deposit_then_withdraw() {
|
|||
.arg("build")
|
||||
.status()
|
||||
.expect("failed to build bridge cli")
|
||||
.success());
|
||||
.success());
|
||||
|
||||
// start a parity node that represents the home chain
|
||||
let mut parity_home = parity_home_command()
|
||||
|
@ -89,6 +93,13 @@ fn test_basic_deposit_then_withdraw() {
|
|||
// give the clients time to start up
|
||||
thread::sleep(Duration::from_millis(3000));
|
||||
|
||||
// A address containing a lot of tokens (0x00a329c0648769a73afac7f9381e08fb43dbea72) should be
|
||||
// automatically added with a password being an empty string.
|
||||
// source: https://paritytech.github.io/wiki/Private-development-chain.html
|
||||
let user_address = "0x00a329c0648769a73afac7f9381e08fb43dbea72";
|
||||
|
||||
let authority_address = "0x00bd138abd70e2f00903268f3db08f2d25677c9e";
|
||||
|
||||
// create authority account on home
|
||||
let exit_status = Command::new("curl")
|
||||
.arg("--data").arg(r#"{"jsonrpc":"2.0","method":"parity_newAccountFromPhrase","params":["node0", ""],"id":0}"#)
|
||||
|
@ -98,6 +109,7 @@ fn test_basic_deposit_then_withdraw() {
|
|||
.status()
|
||||
.expect("failed to create authority account on home");
|
||||
assert!(exit_status.success());
|
||||
// TODO [snd] assert that created address matches authority_address
|
||||
|
||||
// create authority account on foreign
|
||||
let exit_status = Command::new("curl")
|
||||
|
@ -108,6 +120,7 @@ fn test_basic_deposit_then_withdraw() {
|
|||
.status()
|
||||
.expect("failed to create/unlock authority account on foreign");
|
||||
assert!(exit_status.success());
|
||||
// TODO [snd] assert that created address matches authority_address
|
||||
|
||||
// give the operations time to complete
|
||||
thread::sleep(Duration::from_millis(5000));
|
||||
|
@ -121,14 +134,14 @@ fn test_basic_deposit_then_withdraw() {
|
|||
|
||||
// start a parity node that represents the home chain with accounts unlocked
|
||||
let mut parity_home = parity_home_command()
|
||||
.arg("--unlock").arg("0x00a329c0648769a73afac7f9381e08fb43dbea72,0x00bd138abd70e2f00903268f3db08f2d25677c9e")
|
||||
.arg("--unlock").arg(format!("{},{}", user_address, authority_address))
|
||||
.arg("--password").arg("password.txt")
|
||||
.spawn()
|
||||
.expect("failed to spawn parity home node");
|
||||
|
||||
// start a parity node that represents the foreign chain with accounts unlocked
|
||||
let mut parity_foreign = parity_foreign_command()
|
||||
.arg("--unlock").arg("0x00a329c0648769a73afac7f9381e08fb43dbea72,0x00bd138abd70e2f00903268f3db08f2d25677c9e")
|
||||
.arg("--unlock").arg(format!("{},{}", user_address, authority_address))
|
||||
.arg("--password").arg("password.txt")
|
||||
.spawn()
|
||||
.expect("failed to spawn parity foreign node");
|
||||
|
@ -149,15 +162,18 @@ fn test_basic_deposit_then_withdraw() {
|
|||
// give the bridge time to start up and deploy the contracts
|
||||
thread::sleep(Duration::from_millis(10000));
|
||||
|
||||
println!("\ndeposit ether into HomeBridge\n");
|
||||
let home_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f";
|
||||
let foreign_contract_address = "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f";
|
||||
|
||||
// user deposits into HomeBridge
|
||||
println!("\nuser deposits ether into HomeBridge\n");
|
||||
|
||||
// TODO [snd] use rpc client here instead of curl
|
||||
let exit_status = Command::new("curl")
|
||||
.arg("--data").arg(r#"{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{
|
||||
"from": "0x00a329c0648769a73afac7f9381e08fb43dbea72",
|
||||
"to": "0xebd3944af37ccc6b67ff61239ac4fef229c8f69f",
|
||||
.arg("--data").arg(format!(r#"{{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{{
|
||||
"from": "{}",
|
||||
"to": "{}",
|
||||
"value": "0x186a0"
|
||||
}],"id":0}"#)
|
||||
}}],"id":0}}"#, user_address, home_contract_address))
|
||||
.arg("-H").arg("Content-Type: application/json")
|
||||
.arg("-X").arg("POST")
|
||||
.arg("localhost:8550")
|
||||
|
@ -168,7 +184,7 @@ fn test_basic_deposit_then_withdraw() {
|
|||
println!("\ndeposit into home sent. give it plenty of time to get mined and relayed\n");
|
||||
thread::sleep(Duration::from_millis(10000));
|
||||
|
||||
// connect for foreign and home via IPC
|
||||
// connect to foreign and home via IPC
|
||||
let mut event_loop = Core::new().unwrap();
|
||||
let foreign_transport = Ipc::with_event_loop("foreign.ipc", &event_loop.handle())
|
||||
.expect("failed to connect to foreign.ipc");
|
||||
|
@ -178,12 +194,30 @@ fn test_basic_deposit_then_withdraw() {
|
|||
.expect("failed to connect to home.ipc");
|
||||
let home_eth = web3::api::Eth::new(home_transport);
|
||||
|
||||
// balance on ForeignBridge should have increased
|
||||
let balance_payload = foreign.functions().balances().input(Address::from("0x00a329c0648769a73afac7f9381e08fb43dbea72"));
|
||||
// totalSupply on ForeignBridge should have increased
|
||||
let total_supply_payload = foreign.functions().total_supply().input();
|
||||
|
||||
let future = foreign_eth.call(web3::types::CallRequest{
|
||||
from: None,
|
||||
to: web3::types::Address::from(&Address::from("0xebd3944af37ccc6b67ff61239ac4fef229c8f69f").0[..]),
|
||||
to: address_from_str(foreign_contract_address),
|
||||
gas: None,
|
||||
gas_price: None,
|
||||
value: None,
|
||||
data: Some(web3::types::Bytes(total_supply_payload)),
|
||||
}, None);
|
||||
|
||||
let response = event_loop.run(future).unwrap();
|
||||
assert_eq!(
|
||||
U256::from(response.0.as_slice()),
|
||||
U256::from(100000),
|
||||
"totalSupply on ForeignBridge should have increased");
|
||||
|
||||
// balance on ForeignBridge should have increased
|
||||
let balance_payload = foreign.functions().balance_of().input(Address::from(user_address));
|
||||
|
||||
let future = foreign_eth.call(web3::types::CallRequest{
|
||||
from: None,
|
||||
to: address_from_str(foreign_contract_address),
|
||||
gas: None,
|
||||
gas_price: None,
|
||||
value: None,
|
||||
|
@ -193,21 +227,21 @@ fn test_basic_deposit_then_withdraw() {
|
|||
let response = event_loop.run(future).unwrap();
|
||||
let balance = U256::from(response.0.as_slice());
|
||||
assert_eq!(
|
||||
balance,
|
||||
U256::from(100000),
|
||||
"balance on ForeignBridge should have increased");
|
||||
balance,
|
||||
U256::from(100000),
|
||||
"balance on ForeignBridge should have increased");
|
||||
|
||||
println!("\nconfirmed that deposit reached foreign\n");
|
||||
|
||||
// withdraw
|
||||
println!("\nuser executes ForeignBridge.transferHomeViaRelay\n");
|
||||
let transfer_payload = foreign.functions()
|
||||
.transfer_home_via_relay()
|
||||
.input(
|
||||
Address::from("0x00aa39d30f0d20ff03a22ccfc30b7efbfca597c2"),
|
||||
Address::from(user_address),
|
||||
U256::from(100000));
|
||||
let future = foreign_eth.send_transaction(web3::types::TransactionRequest{
|
||||
from: web3::types::Address::from(&Address::from("0x00a329c0648769a73afac7f9381e08fb43dbea72").0[..]),
|
||||
to: Some(web3::types::Address::from(&Address::from("0xebd3944af37ccc6b67ff61239ac4fef229c8f69f").0[..])),
|
||||
from: address_from_str(user_address),
|
||||
to: Some(address_from_str(foreign_contract_address)),
|
||||
gas: None,
|
||||
gas_price: None,
|
||||
value: None,
|
||||
|
@ -221,7 +255,7 @@ fn test_basic_deposit_then_withdraw() {
|
|||
thread::sleep(Duration::from_millis(10000));
|
||||
|
||||
// test that withdraw completed
|
||||
let future = home_eth.balance(web3::types::Address::from(&Address::from("0x00aa39d30f0d20ff03a22ccfc30b7efbfca597c2").0[..]), None);
|
||||
let future = home_eth.balance(address_from_str(user_address), None);
|
||||
println!("waiting for future");
|
||||
let balance = event_loop.run(future).unwrap();
|
||||
assert!(balance > web3::types::U256::from(0));
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"err":null,"data":{"statusCode":200,"headers":{"access-control-allow-headers":"Origin, X-Requested-With, Content-Type, Accept","access-control-allow-origin":"*","access-control-allow-methods":"*","content-type":"application/json","date":"Fri, 26 Jan 2018 09:06:17 GMT","connection":"close","transfer-encoding":"chunked"},"text":"{\"id\":723,\"jsonrpc\":\"2.0\",\"result\":\"0x00000000000000056bc75e2d63100000\"}"}}
|
|
@ -0,0 +1,253 @@
|
|||
var ForeignBridge = artifacts.require("ForeignBridge");
|
||||
var helpers = require("./helpers/helpers");
|
||||
|
||||
contract('ForeignBridge', function(accounts) {
|
||||
it("totalSupply", function() {
|
||||
var contract;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var owner = accounts[2];
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
var value = web3.toWei(3, "ether");
|
||||
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
contract = instance;
|
||||
|
||||
return contract.totalSupply();
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result, "initial supply should be 0");
|
||||
|
||||
return contract.deposit(owner, value, hash, {from: authorities[0]});
|
||||
}).then(function(result) {
|
||||
|
||||
return contract.totalSupply();
|
||||
}).then(function(result) {
|
||||
console.log(result);
|
||||
assert(result.equals(value), "deposit should increase supply");
|
||||
|
||||
return contract.transferHomeViaRelay(owner, value, {from: owner});
|
||||
}).then(function() {
|
||||
|
||||
return contract.totalSupply();
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result, "home transfer should decrease supply");
|
||||
})
|
||||
})
|
||||
|
||||
it("should be able to approve others to spend tokens in their name", function() {
|
||||
var contract;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var owner = accounts[2];
|
||||
var spender = accounts[3];
|
||||
var receiver = accounts[4];
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
contract = instance;
|
||||
|
||||
// deposit something so we can transfer it
|
||||
return contract.deposit(owner, web3.toWei(3, "ether"), hash, {from: authorities[0]});
|
||||
}).then(function(result) {
|
||||
|
||||
return contract.allowance(owner, spender);
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result, "initial allowance should be 0");
|
||||
|
||||
return contract.transferFrom(owner, receiver, web3.toWei(1, "ether"), {from: spender});
|
||||
}).then(function(result) {
|
||||
assert(false, "transfer without allowance should fail");
|
||||
|
||||
// transfer 0 without allowance should work
|
||||
return contract.transferFrom(owner, receiver, 0, {from: spender});
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
|
||||
assert.equal(owner, result.logs[0].args.from);
|
||||
assert.equal(receiver, result.logs[0].args.to);
|
||||
assert.equal(0, result.logs[0].args.tokens);
|
||||
|
||||
}, function(err) {
|
||||
return contract.approve(spender, web3.toWei(4, "ether"), {from: owner});
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Approval", result.logs[0].event, "Event name should be Approval");
|
||||
assert.equal(owner, result.logs[0].args.tokenOwner);
|
||||
assert.equal(spender, result.logs[0].args.spender);
|
||||
assert.equal(web3.toWei(4, "ether"), result.logs[0].args.tokens);
|
||||
|
||||
return contract.allowance(owner, spender);
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(4, "ether"), result, "approval should set allowance");
|
||||
|
||||
return contract.transferFrom(owner, receiver, web3.toWei(4, "ether"), {from: spender});
|
||||
}).then(function(result) {
|
||||
assert(false, "transferring more than balance should fail");
|
||||
}, function(err) {
|
||||
return contract.approve(spender, web3.toWei(2, "ether"), {from: owner});
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Approval", result.logs[0].event, "Event name should be Approval");
|
||||
assert.equal(owner, result.logs[0].args.tokenOwner);
|
||||
assert.equal(spender, result.logs[0].args.spender);
|
||||
assert.equal(web3.toWei(2, "ether"), result.logs[0].args.tokens);
|
||||
|
||||
return contract.allowance(owner, spender);
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(2, "ether"), result, "approval should update allowance");
|
||||
|
||||
return contract.transferFrom(owner, receiver, web3.toWei(2, "ether") + 2, {from: spender});
|
||||
}).then(function(result) {
|
||||
assert(false, "transferring more than allowance should fail");
|
||||
}, function(err) {
|
||||
return contract.transferFrom(owner, receiver, web3.toWei(2, "ether"), {from: spender});
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
|
||||
assert.equal(owner, result.logs[0].args.from);
|
||||
assert.equal(receiver, result.logs[0].args.to);
|
||||
assert.equal(web3.toWei(2, "ether"), result.logs[0].args.tokens);
|
||||
|
||||
return contract.balanceOf(owner);
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(1, "ether"), result, "transferring should reduce owners balance");
|
||||
|
||||
return contract.balanceOf(receiver);
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(2, "ether"), result, "transferring should increase receivers balance");
|
||||
|
||||
return contract.balanceOf(spender);
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result, "transferring should not modify spenders balance");
|
||||
|
||||
return contract.allowance(owner, spender);
|
||||
}).then(function(result) {
|
||||
assert.equal(0, result, "transferring whole allowance should set allowance to 0");
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow user to transfer value locally", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var userAccount = accounts[2];
|
||||
var userAccount2 = accounts[3];
|
||||
var user1InitialValue = web3.toWei(3, "ether");
|
||||
var transferedValue = web3.toWei(1, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
// top up balance so we can transfer
|
||||
return meta.deposit(userAccount, user1InitialValue, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(userAccount2, transferedValue, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
|
||||
assert.equal(userAccount, result.logs[0].args.from, "Event from should be transaction sender");
|
||||
assert.equal(userAccount2, result.logs[0].args.to, "Event from should be transaction recipient");
|
||||
assert.equal(transferedValue, result.logs[0].args.tokens, "Event tokens should match transaction value");
|
||||
return Promise.all([
|
||||
meta.balances.call(userAccount),
|
||||
meta.balances.call(userAccount2)
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(2, "ether"), result[0]);
|
||||
assert.equal(transferedValue, result[1]);
|
||||
})
|
||||
})
|
||||
|
||||
it("should not allow user to transfer value they don't have", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var userValue = web3.toWei(3, "ether");
|
||||
var transferedValue = web3.toWei(4, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(userAccount, userValue, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(recipientAccount, transferedValue, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert(false, "transfer should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow transfer of 0 value according to ERC20", 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 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.transfer(recipientAccount, 0, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
|
||||
assert.equal(userAccount, result.logs[0].args.from, "Event from should be transaction sender");
|
||||
assert.equal(recipientAccount, result.logs[0].args.to, "Event from should be transaction recipient");
|
||||
assert.equal(0, result.logs[0].args.tokens, "Event tokens should match transaction value");
|
||||
return Promise.all([
|
||||
meta.balances.call(userAccount),
|
||||
meta.balances.call(recipientAccount)
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(userValue, result[0]);
|
||||
assert.equal(0, result[1]);
|
||||
})
|
||||
})
|
||||
|
||||
it("transfer that results in overflow should fail", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var userAccount = accounts[2];
|
||||
var recipientAccount = accounts[3];
|
||||
var maxValue = web3.toWei("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "wei");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(recipientAccount, maxValue, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.deposit(userAccount, 1, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transfer(recipientAccount, 1, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert(false, "transfer should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
|
||||
it("transferFrom that results in overflow should fail", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var userAccount = accounts[2];
|
||||
var spenderAccount = accounts[3];
|
||||
var recipientAccount = accounts[4];
|
||||
var maxValue = web3.toWei("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "wei");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
return meta.deposit(recipientAccount, maxValue, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.deposit(userAccount, 1, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.approve(spenderAccount, 1, {from: userAccount});
|
||||
}).then(function(result) {
|
||||
return meta.transferFrom(userAccount, recipientAccount, 1, { from: spenderAccount });
|
||||
}).then(function(result) {
|
||||
assert(false, "transfer should fail");
|
||||
}, function(err) {
|
||||
})
|
||||
})
|
||||
})
|
|
@ -48,10 +48,17 @@ contract('ForeignBridge', function(accounts) {
|
|||
meta = instance;
|
||||
return meta.deposit(userAccount, value, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Deposit", result.logs[0].event, "Event name should be Deposit");
|
||||
assert.equal(userAccount, result.logs[0].args.recipient, "Event recipient should be transaction sender");
|
||||
assert.equal(value, result.logs[0].args.value, "Event value should match deposited ether");
|
||||
assert.equal(2, result.logs.length)
|
||||
|
||||
assert.equal("Transfer", result.logs[0].event);
|
||||
assert.equal("0x0000000000000000000000000000000000000000", result.logs[0].args.from);
|
||||
assert.equal(userAccount, result.logs[0].args.to);
|
||||
assert.equal(value, result.logs[0].args.tokens);
|
||||
|
||||
assert.equal("Deposit", result.logs[1].event);
|
||||
assert.equal(userAccount, result.logs[1].args.recipient);
|
||||
assert.equal(value, result.logs[1].args.value);
|
||||
|
||||
return meta.balances.call(userAccount);
|
||||
}).then(function(result) {
|
||||
assert.equal(value, result, "Contract balance should change");
|
||||
|
@ -76,10 +83,16 @@ contract('ForeignBridge', function(accounts) {
|
|||
assert.equal(web3.toWei(0, "ether"), result, "Contract balance should not change yet");
|
||||
return meta.deposit(userAccount, value, hash, { from: authorities[1] });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Deposit", result.logs[0].event, "Event name should be Deposit");
|
||||
assert.equal(userAccount, result.logs[0].args.recipient, "Event recipient should be transaction sender");
|
||||
assert.equal(value, result.logs[0].args.value, "Event value should match deposited ether");
|
||||
assert.equal(2, result.logs.length)
|
||||
|
||||
assert.equal("Transfer", result.logs[0].event);
|
||||
assert.equal("0x0000000000000000000000000000000000000000", result.logs[0].args.from);
|
||||
assert.equal(userAccount, result.logs[0].args.to);
|
||||
assert.equal(value, result.logs[0].args.tokens);
|
||||
|
||||
assert.equal("Deposit", result.logs[1].event, "Event name should be Deposit");
|
||||
assert.equal(userAccount, result.logs[1].args.recipient, "Event recipient should be transaction sender");
|
||||
assert.equal(value, result.logs[1].args.value, "Event value should match deposited ether");
|
||||
return meta.balances.call(userAccount);
|
||||
}).then(function(result) {
|
||||
assert.equal(value, result, "Contract balance should change");
|
||||
|
@ -141,48 +154,24 @@ contract('ForeignBridge', function(accounts) {
|
|||
assert.equal(0, result.logs.length, "Misbehaving authority should be ignored");
|
||||
return meta.deposit(userAccount, value, hash, { from: authorities[2] })
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Deposit", result.logs[0].event, "Event name should be Deposit");
|
||||
assert.equal(userAccount, result.logs[0].args.recipient, "Event recipient should be transaction sender");
|
||||
assert.equal(value, result.logs[0].args.value, "Event value should match transaction value");
|
||||
assert.equal(2, result.logs.length)
|
||||
|
||||
assert.equal("Transfer", result.logs[0].event);
|
||||
assert.equal("0x0000000000000000000000000000000000000000", result.logs[0].args.from);
|
||||
assert.equal(userAccount, result.logs[0].args.to);
|
||||
assert.equal(value, result.logs[0].args.tokens);
|
||||
|
||||
assert.equal("Deposit", result.logs[1].event, "Event name should be Deposit");
|
||||
assert.equal(userAccount, result.logs[1].args.recipient, "Event recipient should be transaction sender");
|
||||
assert.equal(value, result.logs[1].args.value, "Event value should match transaction value");
|
||||
|
||||
return meta.balances.call(userAccount);
|
||||
}).then(function(result) {
|
||||
assert.equal(value, result, "Contract balance should change");
|
||||
})
|
||||
})
|
||||
|
||||
it("should allow user to transfer value locally", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
var userAccount = accounts[2];
|
||||
var userAccount2 = accounts[3];
|
||||
var user1InitialValue = web3.toWei(3, "ether");
|
||||
var transferedValue = web3.toWei(1, "ether");
|
||||
var hash = "0xe55bb43c36cdf79e23b4adc149cdded921f0d482e613c50c6540977c213bc408";
|
||||
return ForeignBridge.new(requiredSignatures, authorities).then(function(instance) {
|
||||
meta = instance;
|
||||
// top up balance so we can transfer
|
||||
return meta.deposit(userAccount, user1InitialValue, hash, { from: authorities[0] });
|
||||
}).then(function(result) {
|
||||
return meta.transferLocal(userAccount2, transferedValue, { from: userAccount });
|
||||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("Transfer", result.logs[0].event, "Event name should be Transfer");
|
||||
assert.equal(userAccount, result.logs[0].args.from, "Event from should be transaction sender");
|
||||
assert.equal(userAccount2, result.logs[0].args.to, "Event from should be transaction recipient");
|
||||
assert.equal(transferedValue, result.logs[0].args.value, "Event value should match transaction value");
|
||||
return Promise.all([
|
||||
meta.balances.call(userAccount),
|
||||
meta.balances.call(userAccount2)
|
||||
])
|
||||
}).then(function(result) {
|
||||
assert.equal(web3.toWei(2, "ether"), result[0]);
|
||||
assert.equal(transferedValue, result[1]);
|
||||
})
|
||||
})
|
||||
|
||||
it("should not allow user to transfer value they don't have either locally or to home", function() {
|
||||
it("should not allow user to transfer value they don't have to home", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
|
@ -195,10 +184,6 @@ contract('ForeignBridge', function(accounts) {
|
|||
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");
|
||||
|
@ -206,7 +191,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
})
|
||||
})
|
||||
|
||||
it("should fail to transfer 0 value both locally and to home", function() {
|
||||
it("should fail to transfer 0 value to home", function() {
|
||||
var meta;
|
||||
var requiredSignatures = 1;
|
||||
var authorities = [accounts[0], accounts[1]];
|
||||
|
@ -219,34 +204,6 @@ contract('ForeignBridge', function(accounts) {
|
|||
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");
|
||||
|
@ -270,10 +227,17 @@ contract('ForeignBridge', function(accounts) {
|
|||
}).then(function(result) {
|
||||
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(userAccount2, result.logs[0].args.recipient, "Event recipient should be equal to transaction recipient");
|
||||
assert.equal(value2, result.logs[0].args.value, "Event value should match transaction value");
|
||||
assert.equal(2, result.logs.length)
|
||||
|
||||
assert.equal("Transfer", result.logs[0].event);
|
||||
assert.equal(userAccount, result.logs[0].args.from);
|
||||
assert.equal("0x0000000000000000000000000000000000000000", result.logs[0].args.to);
|
||||
assert.equal(value2, result.logs[0].args.tokens);
|
||||
|
||||
assert.equal("Withdraw", result.logs[1].event, "Event name should be Withdraw");
|
||||
assert.equal(userAccount2, result.logs[1].args.recipient, "Event recipient should be equal to transaction recipient");
|
||||
assert.equal(value2, result.logs[1].args.value, "Event value should match transaction value");
|
||||
|
||||
return Promise.all([
|
||||
meta.balances.call(userAccount),
|
||||
meta.balances.call(userAccount2)
|
||||
|
@ -299,7 +263,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
|
||||
assert.equal(authorities[0], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
|
||||
assert.equal(authorities[0], result.logs[0].args.authorityResponsibleForRelay, "Event authority should be equal to transaction sender");
|
||||
return Promise.all([
|
||||
meta.signature.call(result.logs[0].args.messageHash, 0),
|
||||
meta.message(result.logs[0].args.messageHash),
|
||||
|
@ -356,7 +320,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
|
||||
assert.equal(authorities[0], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
|
||||
assert.equal(authorities[0], result.logs[0].args.authorityResponsibleForRelay, "Event authority should be equal to transaction sender");
|
||||
return Promise.all([
|
||||
meta.signature.call(result.logs[0].args.messageHash, 0),
|
||||
meta.signature.call(result.logs[0].args.messageHash, 1),
|
||||
|
@ -370,7 +334,7 @@ contract('ForeignBridge', function(accounts) {
|
|||
}).then(function(result) {
|
||||
assert.equal(1, result.logs.length, "Exactly one event should be created");
|
||||
assert.equal("CollectedSignatures", result.logs[0].event, "Event name should be CollectedSignatures");
|
||||
assert.equal(authorities[1], result.logs[0].args.authority, "Event authority should be equal to transaction sender");
|
||||
assert.equal(authorities[1], result.logs[0].args.authorityResponsibleForRelay, "Event authority should be equal to transaction sender");
|
||||
return Promise.all([
|
||||
meta.signature.call(result.logs[0].args.messageHash, 0),
|
||||
meta.signature.call(result.logs[0].args.messageHash, 1),
|
||||
|
|
Loading…
Reference in New Issue