Merge pull request #86 from paritytech/snd-issue85-erc20

#85 (ERC20 for ForeignBridge) and more
This commit is contained in:
snd 2018-01-29 11:58:57 +01:00 committed by GitHub
commit 16ced48292
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 538 additions and 190 deletions

1
Cargo.lock generated
View File

@ -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
View File

@ -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

View File

@ -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() {

View File

@ -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.

View File

@ -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.

View File

@ -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 }

View File

@ -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));

View File

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

View File

View File

@ -0,0 +1,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) {
})
})
})

View File

@ -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),