From 8a0689326855ceae553a3b1346e0270648100d8c Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Wed, 17 Jun 2020 12:07:03 +0300 Subject: [PATCH] test(contract): add deploy and call contract function celo test --- .github/workflows/ci.yml | 5 + Cargo.lock | 71 +++++++ README.md | 21 +- ethers-contract/Cargo.toml | 3 +- ethers-contract/src/factory.rs | 3 +- ethers-contract/tests/contract.rs | 249 +++++++++++++++++------ ethers-contract/tests/get_past_events.rs | 32 --- ethers-contract/tests/watch_events.rs | 38 ---- ethers/Cargo.toml | 2 +- 9 files changed, 286 insertions(+), 138 deletions(-) delete mode 100644 ethers-contract/tests/get_past_events.rs delete mode 100644 ethers-contract/tests/watch_events.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbcd70210..49128872c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,11 @@ jobs: export PATH=$HOME/bin:$PATH cargo test + - name: cargo test (Celo) + run: | + export PATH=$HOME/bin:$PATH + cargo test --all-features + - name: cargo fmt run: cargo fmt --all -- --check diff --git a/Cargo.lock b/Cargo.lock index f0ce7e714..292efc2e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,15 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -301,6 +310,7 @@ dependencies = [ "rustc-hex", "serde", "serde_json", + "serial_test", "thiserror", "tokio", ] @@ -775,6 +785,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "lock_api" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.8" @@ -897,6 +916,30 @@ dependencies = [ "serde", ] +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +dependencies = [ + "cfg-if", + "cloudabi", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.8", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -1156,6 +1199,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "sct" version = "0.6.0" @@ -1209,6 +1258,28 @@ dependencies = [ "url", ] +[[package]] +name = "serial_test" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef5f7c7434b2f2c598adc6f9494648a1e41274a75c0ba4056f680ae0c117fd6" +dependencies = [ + "lazy_static", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08338d8024b227c62bd68a12c7c9883f5c66780abaef15c550dc56f46ee6515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.8.2" diff --git a/README.md b/README.md index facf414fc..c5e55c79c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ #

ethers.rs

-**Complete Ethereum wallet implementation and utilities in Rust** +**Complete Ethereum and Celo wallet implementation and utilities in Rust** [![CircleCI](https://circleci.com/gh/circleci/circleci-docs.svg?style=svg)](https://circleci.com/gh/circleci/circleci-docs) @@ -20,6 +20,24 @@ ethers = { git = "github.com/gakonst/ethers-rs" } +### Celo Support + +[Celo](http://celo.org/) support is turned on via the feature-flag `celo`: + +```toml +[dependencies] + +ethers = { git = "github.com/gakonst/ethers-rs", features = ["celo"] } +``` + +Celo's transactions differ from Ethereum transactions by including 3 new fields: +- `fee_currency`: The currency fees are paid in (None for CELO, otherwise it's an Address) +- `gateway_fee_recipient`: The address of the fee recipient (None for no gateway fee paid) +- `gateway_fee`: Gateway fee amount (None for no gateway fee paid) + +The feature flag enables these additional fields in the transaction request builders and +in the transactions which are fetched over JSON-RPC. + ## Features - [x] Ethereum JSON-RPC Client @@ -28,6 +46,7 @@ ethers = { git = "github.com/gakonst/ethers-rs" } - [x] Querying past events - [x] Event monitoring as `Stream`s - [x] ENS as a first class citizen +- [x] Celo support - [ ] Websockets / `eth_subscribe` - [ ] Hardware Wallet Support - [ ] WASM Bindings diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml index 97ec36758..0677f9bc9 100644 --- a/ethers-contract/Cargo.toml +++ b/ethers-contract/Cargo.toml @@ -22,7 +22,8 @@ futures = "0.3.5" ethers = { version = "0.1.0", path = "../ethers" } tokio = { version = "0.2.21", default-features = false, features = ["macros"] } serde_json = "1.0.55" +serial_test = "0.4.0" [features] abigen = ["ethers-contract-abigen", "ethers-contract-derive"] -celo = ["ethers-core/celo", "ethers-providers/celo", "ethers-signers/celo", "ethers/celo"] +celo = ["ethers-core/celo", "ethers-core/celo", "ethers-providers/celo", "ethers-signers/celo"] diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs index 9ac362692..0413dc412 100644 --- a/ethers-contract/src/factory.rs +++ b/ethers-contract/src/factory.rs @@ -10,9 +10,10 @@ use ethers_signers::{Client, Signer}; #[derive(Debug, Clone)] /// Helper which manages the deployment transaction of a smart contract pub struct Deployer<'a, P, S> { + /// The deployer's transaction, exposed for overriding the defaults + pub tx: TransactionRequest, abi: Abi, client: &'a Client, - tx: TransactionRequest, confs: usize, } diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs index 8e97e5b5f..871740539 100644 --- a/ethers-contract/tests/contract.rs +++ b/ethers-contract/tests/contract.rs @@ -1,69 +1,190 @@ -use ethers_contract::ContractFactory; -use ethers_core::{ - types::{Address, H256}, - utils::Ganache, -}; +use ethers::{contract::ContractFactory, types::H256}; mod common; pub use common::*; -#[tokio::test] -async fn deploy_and_call_contract() { - let (abi, bytecode) = compile(); - - // launch ganache - let _ganache = Ganache::new() - .mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle") - .spawn(); - - // Instantiate the clients. We assume that clients consume the provider and the wallet - // (which makes sense), so for multi-client tests, you must clone the provider. - let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"); - let client2 = connect("cc96601bc52293b53c4736a12af9130abf347669b3813f9ec4cafdf6991b087e"); - - // create a factory which will be used to deploy instances of the contract - let factory = ContractFactory::new(abi, bytecode, &client); - - // `send` consumes the deployer so it must be cloned for later re-use - // (practically it's not expected that you'll need to deploy multiple instances of - // the _same_ deployer, so it's fine to clone here from a dev UX vs perf tradeoff) - let deployer = factory.deploy("initial value".to_string()).unwrap(); - let contract = deployer.clone().send().await.unwrap(); - - let get_value = contract.method::<_, String>("getValue", ()).unwrap(); - let last_sender = contract.method::<_, Address>("lastSender", ()).unwrap(); - - // the initial value must be the one set in the constructor - let value = get_value.clone().call().await.unwrap(); - assert_eq!(value, "initial value"); - - // make a call with `client2` - let _tx_hash = contract - .connect(&client2) - .method::<_, H256>("setValue", "hi".to_owned()) - .unwrap() - .send() - .await - .unwrap(); - assert_eq!(last_sender.clone().call().await.unwrap(), client2.address()); - assert_eq!(get_value.clone().call().await.unwrap(), "hi"); - - // we can also call contract methods at other addresses with the `at` call - // (useful when interacting with multiple ERC20s for example) - let contract2_addr = deployer.clone().send().await.unwrap().address(); - let contract2 = contract.at(contract2_addr); - let init_value: String = contract2 - .method::<_, String>("getValue", ()) - .unwrap() - .call() - .await - .unwrap(); - let init_address = contract2 - .method::<_, Address>("lastSender", ()) - .unwrap() - .call() - .await - .unwrap(); - assert_eq!(init_address, Address::zero()); - assert_eq!(init_value, "initial value"); +#[cfg(not(feature = "celo"))] +mod eth_tests { + use super::*; + use ethers::{providers::StreamExt, types::Address, utils::Ganache}; + use serial_test::serial; + + #[tokio::test] + #[serial] + async fn deploy_and_call_contract() { + let (abi, bytecode) = compile(); + + // launch ganache + let _ganache = Ganache::new() + .mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle") + .spawn(); + + // Instantiate the clients. We assume that clients consume the provider and the wallet + // (which makes sense), so for multi-client tests, you must clone the provider. + let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"); + let client2 = connect("cc96601bc52293b53c4736a12af9130abf347669b3813f9ec4cafdf6991b087e"); + + // create a factory which will be used to deploy instances of the contract + let factory = ContractFactory::new(abi, bytecode, &client); + + // `send` consumes the deployer so it must be cloned for later re-use + // (practically it's not expected that you'll need to deploy multiple instances of + // the _same_ deployer, so it's fine to clone here from a dev UX vs perf tradeoff) + let deployer = factory.deploy("initial value".to_string()).unwrap(); + let contract = deployer.clone().send().await.unwrap(); + + let get_value = contract.method::<_, String>("getValue", ()).unwrap(); + let last_sender = contract.method::<_, Address>("lastSender", ()).unwrap(); + + // the initial value must be the one set in the constructor + let value = get_value.clone().call().await.unwrap(); + assert_eq!(value, "initial value"); + + // make a call with `client2` + let _tx_hash = contract + .connect(&client2) + .method::<_, H256>("setValue", "hi".to_owned()) + .unwrap() + .send() + .await + .unwrap(); + assert_eq!(last_sender.clone().call().await.unwrap(), client2.address()); + assert_eq!(get_value.clone().call().await.unwrap(), "hi"); + + // we can also call contract methods at other addresses with the `at` call + // (useful when interacting with multiple ERC20s for example) + let contract2_addr = deployer.clone().send().await.unwrap().address(); + let contract2 = contract.at(contract2_addr); + let init_value: String = contract2 + .method::<_, String>("getValue", ()) + .unwrap() + .call() + .await + .unwrap(); + let init_address = contract2 + .method::<_, Address>("lastSender", ()) + .unwrap() + .call() + .await + .unwrap(); + assert_eq!(init_address, Address::zero()); + assert_eq!(init_value, "initial value"); + } + + #[tokio::test] + #[serial] + async fn get_past_events() { + let (abi, bytecode) = compile(); + let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"); + let (_ganache, contract) = deploy(&client, abi, bytecode).await; + + // make a call with `client2` + let _tx_hash = contract + .method::<_, H256>("setValue", "hi".to_owned()) + .unwrap() + .send() + .await + .unwrap(); + + // and we can fetch the events + let logs: Vec = contract + .event("ValueChanged") + .unwrap() + .from_block(0u64) + .topic1(client.address()) // Corresponds to the first indexed parameter + .query() + .await + .unwrap(); + assert_eq!(logs[0].new_value, "initial value"); + assert_eq!(logs[1].new_value, "hi"); + assert_eq!(logs.len(), 2); + } + + #[tokio::test] + #[serial] + async fn watch_events() { + let (abi, bytecode) = compile(); + let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"); + let (_ganache, contract) = deploy(&client, abi, bytecode).await; + + // We spawn the event listener: + let mut stream = contract + .event::("ValueChanged") + .unwrap() + .stream() + .await + .unwrap(); + + let num_calls = 3u64; + + // and we make a few calls + for i in 0..num_calls { + let _tx_hash = contract + .method::<_, H256>("setValue", i.to_string()) + .unwrap() + .send() + .await + .unwrap(); + } + + for i in 0..num_calls { + // unwrap the option of the stream, then unwrap the decoding result + let log = stream.next().await.unwrap().unwrap(); + assert_eq!(log.new_value, i.to_string()); + } + } +} + +#[cfg(feature = "celo")] +mod celo_tests { + use super::*; + use ethers::{ + providers::{Http, Provider}, + signers::Wallet, + }; + use std::convert::TryFrom; + + #[tokio::test] + async fn deploy_and_call_contract() { + let (abi, bytecode) = compile(); + + // Celo testnet + let provider = + Provider::::try_from("https://alfajores-forno.celo-testnet.org").unwrap(); + + // Funded with https://celo.org/developers/faucet + let client = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1" + .parse::() + .unwrap() + .connect(provider); + + let factory = ContractFactory::new(abi, bytecode, &client); + let deployer = factory.deploy("initial value".to_string()).unwrap(); + let contract = deployer.send().await.unwrap(); + + let value: String = contract + .method("getValue", ()) + .unwrap() + .call() + .await + .unwrap(); + assert_eq!(value, "initial value"); + + // make a state mutating transaction + let pending_tx = contract + .method::<_, H256>("setValue", "hi".to_owned()) + .unwrap() + .send() + .await + .unwrap(); + let _receipt = pending_tx.await.unwrap(); + + let value: String = contract + .method("getValue", ()) + .unwrap() + .call() + .await + .unwrap(); + assert_eq!(value, "hi"); + } } diff --git a/ethers-contract/tests/get_past_events.rs b/ethers-contract/tests/get_past_events.rs deleted file mode 100644 index dcd51ed14..000000000 --- a/ethers-contract/tests/get_past_events.rs +++ /dev/null @@ -1,32 +0,0 @@ -use ethers_core::types::H256; - -mod common; -use common::{compile, connect, deploy, ValueChanged}; - -#[tokio::test] -async fn get_past_events() { - let (abi, bytecode) = compile(); - let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"); - let (_ganache, contract) = deploy(&client, abi, bytecode).await; - - // make a call with `client2` - let _tx_hash = contract - .method::<_, H256>("setValue", "hi".to_owned()) - .unwrap() - .send() - .await - .unwrap(); - - // and we can fetch the events - let logs: Vec = contract - .event("ValueChanged") - .unwrap() - .from_block(0u64) - .topic1(client.address()) // Corresponds to the first indexed parameter - .query() - .await - .unwrap(); - assert_eq!(logs[0].new_value, "initial value"); - assert_eq!(logs[1].new_value, "hi"); - assert_eq!(logs.len(), 2); -} diff --git a/ethers-contract/tests/watch_events.rs b/ethers-contract/tests/watch_events.rs deleted file mode 100644 index d8b9ccaa3..000000000 --- a/ethers-contract/tests/watch_events.rs +++ /dev/null @@ -1,38 +0,0 @@ -use ethers_core::types::H256; -use ethers_providers::StreamExt; - -mod common; -use common::{compile, connect, deploy, ValueChanged}; - -#[tokio::test] -async fn watch_events() { - let (abi, bytecode) = compile(); - let client = connect("380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"); - let (_ganache, contract) = deploy(&client, abi, bytecode).await; - - // We spawn the event listener: - let mut stream = contract - .event::("ValueChanged") - .unwrap() - .stream() - .await - .unwrap(); - - let num_calls = 3u64; - - // and we make a few calls - for i in 0..num_calls { - let _tx_hash = contract - .method::<_, H256>("setValue", i.to_string()) - .unwrap() - .send() - .await - .unwrap(); - } - - for i in 0..num_calls { - // unwrap the option of the stream, then unwrap the decoding result - let log = stream.next().await.unwrap().unwrap(); - assert_eq!(log.new_value, i.to_string()); - } -} diff --git a/ethers/Cargo.toml b/ethers/Cargo.toml index 2e5a72f99..50d887312 100644 --- a/ethers/Cargo.toml +++ b/ethers/Cargo.toml @@ -32,7 +32,7 @@ celo = [ "ethers-core/celo", "ethers-providers/celo", "ethers-signers/celo", - "ethers-contract/celo" + "ethers-contract/celo", ] core = ["ethers-core"]