Add Celo support (#8)

* feat(types): add optional Celo support

* feat: add Celo feature flags to all crates

* test(provider): add get_transaction celo test

* test(signer): add send_transaction celo test

* test(contract): add deploy and call contract function celo test
This commit is contained in:
Georgios Konstantopoulos 2020-06-17 12:22:01 +03:00 committed by GitHub
parent 20493e0190
commit ba5ae5a894
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 521 additions and 207 deletions

View File

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

72
Cargo.lock generated
View File

@ -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"
@ -290,6 +299,7 @@ dependencies = [
name = "ethers-contract"
version = "0.1.0"
dependencies = [
"ethers",
"ethers-contract-abigen",
"ethers-contract-derive",
"ethers-core",
@ -300,6 +310,7 @@ dependencies = [
"rustc-hex",
"serde",
"serde_json",
"serial_test",
"thiserror",
"tokio",
]
@ -774,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"
@ -896,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"
@ -1155,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"
@ -1208,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"

View File

@ -1,6 +1,6 @@
# <h1 align="center"> ethers.rs </h1>
**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" }
</details>
### 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

View File

@ -19,8 +19,11 @@ once_cell = { version = "1.3.1", default-features = false }
futures = "0.3.5"
[dev-dependencies]
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-core/celo", "ethers-providers/celo", "ethers-signers/celo"]

View File

@ -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<P, S>,
tx: TransactionRequest,
confs: usize,
}

View File

@ -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();
#[cfg(not(feature = "celo"))]
mod eth_tests {
use super::*;
use ethers::{providers::StreamExt, types::Address, utils::Ganache};
use serial_test::serial;
// launch ganache
let _ganache = Ganache::new()
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
.spawn();
#[tokio::test]
#[serial]
async fn deploy_and_call_contract() {
let (abi, bytecode) = compile();
// 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");
// launch ganache
let _ganache = Ganache::new()
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
.spawn();
// create a factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, &client);
// 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");
// `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();
// create a factory which will be used to deploy instances of the contract
let factory = ContractFactory::new(abi, bytecode, &client);
let get_value = contract.method::<_, String>("getValue", ()).unwrap();
let last_sender = contract.method::<_, Address>("lastSender", ()).unwrap();
// `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();
// the initial value must be the one set in the constructor
let value = get_value.clone().call().await.unwrap();
assert_eq!(value, "initial value");
let get_value = contract.method::<_, String>("getValue", ()).unwrap();
let last_sender = contract.method::<_, Address>("lastSender", ()).unwrap();
// 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");
// the initial value must be the one set in the constructor
let value = get_value.clone().call().await.unwrap();
assert_eq!(value, "initial value");
// 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");
// 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<ValueChanged> = 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>("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::<Http>::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
// Funded with https://celo.org/developers/faucet
let client = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
.parse::<Wallet>()
.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");
}
}

View File

@ -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<ValueChanged> = 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);
}

View File

@ -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>("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());
}
}

View File

@ -31,4 +31,5 @@ bincode = "1.2.1"
[features]
default = ["abi"]
celo = [] # celo support extends the transaction format with extra fields
abi = ["ethabi", "arrayvec"]

View File

@ -9,7 +9,11 @@ use serde::{Deserialize, Serialize};
use std::str::FromStr;
// Number of tx fields before signing
#[cfg(not(feature = "celo"))]
const UNSIGNED_TX_FIELDS: usize = 6;
// Celo has 3 additional fields
#[cfg(feature = "celo")]
const UNSIGNED_TX_FIELDS: usize = 9;
// Unsigned fields + signature [r s v]
const SIGNED_TX_FIELDS: usize = UNSIGNED_TX_FIELDS + 3;
@ -46,6 +50,22 @@ pub struct TransactionRequest {
/// Transaction nonce (None for next available nonce)
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<U256>,
///////////////// Celo-specific transaction fields /////////////////
/// The currency fees are paid in (None for native currency)
#[cfg(feature = "celo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_currency: Option<Address>,
/// Gateway fee recipient (None for no gateway fee paid)
#[cfg(feature = "celo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway_fee_recipient: Option<Address>,
/// Gateway fee amount (None for no gateway fee paid)
#[cfg(feature = "celo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway_fee: Option<U256>,
}
impl TransactionRequest {
@ -54,17 +74,12 @@ impl TransactionRequest {
Self::default()
}
/// Convenience function for sending a new payment transaction to the receiver. The
/// `gas`, `gas_price` and `nonce` fields are left empty, to be populated
/// Convenience function for sending a new payment transaction to the receiver.
pub fn pay<T: Into<NameOrAddress>, V: Into<U256>>(to: T, value: V) -> Self {
TransactionRequest {
from: None,
to: Some(to.into()),
gas: None,
gas_price: None,
value: Some(value.into()),
data: None,
nonce: None,
..Default::default()
}
}
@ -150,9 +165,12 @@ impl TransactionRequest {
/// Produces the RLP encoding of the transaction with the provided signature
pub fn rlp_signed(&self, signature: &Signature) -> Bytes {
let mut rlp = RlpStream::new();
// construct the RLP body
rlp.begin_list(SIGNED_TX_FIELDS);
self.rlp_base(&mut rlp);
// append the signature
rlp.append(&signature.v);
rlp.append(&signature.r);
rlp.append(&signature.s);
@ -164,12 +182,45 @@ impl TransactionRequest {
rlp_opt(rlp, self.nonce);
rlp_opt(rlp, self.gas_price);
rlp_opt(rlp, self.gas);
#[cfg(feature = "celo")]
self.inject_celo_metadata(rlp);
rlp_opt(rlp, self.to.as_ref());
rlp_opt(rlp, self.value);
rlp_opt(rlp, self.data.as_ref().map(|d| &d.0[..]));
}
}
// Separate impl block for the celo-specific fields
#[cfg(feature = "celo")]
impl TransactionRequest {
// modifies the RLP stream with the Celo-specific information
fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
rlp_opt(rlp, self.fee_currency);
rlp_opt(rlp, self.gateway_fee_recipient);
rlp_opt(rlp, self.gateway_fee);
}
/// Sets the `fee_currency` field in the transaction to the provided value
pub fn fee_currency<T: Into<Address>>(mut self, fee_currency: T) -> Self {
self.fee_currency = Some(fee_currency.into());
self
}
/// Sets the `gateway_fee` field in the transaction to the provided value
pub fn gateway_fee<T: Into<U256>>(mut self, gateway_fee: T) -> Self {
self.gateway_fee = Some(gateway_fee.into());
self
}
/// Sets the `gateway_fee_recipient` field in the transaction to the provided value
pub fn gateway_fee_recipient<T: Into<Address>>(mut self, gateway_fee_recipient: T) -> Self {
self.gateway_fee_recipient = Some(gateway_fee_recipient.into());
self
}
}
fn rlp_opt<T: rlp::Encodable>(rlp: &mut RlpStream, opt: Option<T>) {
if let Some(ref inner) = opt {
rlp.append(inner);
@ -230,9 +281,38 @@ pub struct Transaction {
/// ECDSA signature s
pub s: U256,
///////////////// Celo-specific transaction fields /////////////////
/// The currency fees are paid in (None for native currency)
#[cfg(feature = "celo")]
#[serde(skip_serializing_if = "Option::is_none", rename = "feeCurrency")]
pub fee_currency: Option<Address>,
/// Gateway fee recipient (None for no gateway fee paid)
#[cfg(feature = "celo")]
#[serde(
skip_serializing_if = "Option::is_none",
rename = "gatewayFeeRecipient"
)]
pub gateway_fee_recipient: Option<Address>,
/// Gateway fee amount (None for no gateway fee paid)
#[cfg(feature = "celo")]
#[serde(skip_serializing_if = "Option::is_none", rename = "gatewayFee")]
pub gateway_fee: Option<U256>,
}
impl Transaction {
// modifies the RLP stream with the Celo-specific information
// This is duplicated from TransactionRequest. Is there a good way to get rid
// of this code duplication?
#[cfg(feature = "celo")]
fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
rlp_opt(rlp, self.fee_currency);
rlp_opt(rlp, self.gateway_fee_recipient);
rlp_opt(rlp, self.gateway_fee);
}
pub fn hash(&self) -> H256 {
keccak256(&self.rlp().0).into()
}
@ -243,6 +323,10 @@ impl Transaction {
rlp.append(&self.nonce);
rlp.append(&self.gas_price);
rlp.append(&self.gas);
#[cfg(feature = "celo")]
self.inject_celo_metadata(&mut rlp);
rlp_opt(&mut rlp, self.to);
rlp.append(&self.value);
rlp.append(&self.input.0);
@ -292,6 +376,7 @@ pub struct TransactionReceipt {
}
#[cfg(test)]
#[cfg(not(feature = "celo"))]
mod tests {
use super::*;

View File

@ -157,6 +157,14 @@ impl PrivateKey {
block_hash: None,
block_number: None,
transaction_index: None,
// Celo support
#[cfg(feature = "celo")]
fee_currency: tx.fee_currency,
#[cfg(feature = "celo")]
gateway_fee: tx.gateway_fee,
#[cfg(feature = "celo")]
gateway_fee_recipient: tx.gateway_fee_recipient,
})
}
@ -318,9 +326,9 @@ mod tests {
}
#[test]
#[cfg(not(feature = "celo"))]
fn signs_tx() {
use crate::types::{Address, Bytes};
// retrieved test vector from:
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
let tx = TransactionRequest {

View File

@ -25,3 +25,6 @@ ethers = { version = "0.1.0", path = "../ethers" }
rustc-hex = "2.1.0"
tokio = { version = "0.2.21", default-features = false, features = ["rt-core", "macros"] }
[features]
celo = ["ethers-core/celo"]

View File

@ -1,12 +1,14 @@
use ethers::{
providers::{Http, Provider},
types::TransactionRequest,
utils::{parse_ether, Ganache},
};
use ethers::providers::{Http, Provider};
use std::convert::TryFrom;
#[tokio::test]
#[cfg(not(feature = "celo"))]
async fn pending_txs_with_confirmations_ganache() {
use ethers::{
types::TransactionRequest,
utils::{parse_ether, Ganache},
};
let _ganache = Ganache::new().block_time(2u64).spawn();
let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
let accounts = provider.get_accounts().await.unwrap();
@ -19,3 +21,25 @@ async fn pending_txs_with_confirmations_ganache() {
// got the correct receipt
assert_eq!(receipt.transaction_hash, hash);
}
#[cfg(feature = "celo")]
mod celo_tests {
use super::*;
use ethers::types::H256;
#[tokio::test]
// https://alfajores-blockscout.celo-testnet.org/tx/0x544ea96cddb16aeeaedaf90885c1e02be4905f3eb43d6db3f28cac4dbe76a625/internal_transactions
async fn get_transaction() {
let provider =
Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
let tx_hash = "544ea96cddb16aeeaedaf90885c1e02be4905f3eb43d6db3f28cac4dbe76a625"
.parse::<H256>()
.unwrap();
let tx = provider.get_transaction(tx_hash).await.unwrap();
assert!(tx.gateway_fee_recipient.is_none());
assert_eq!(tx.gateway_fee.unwrap(), 0.into());
assert_eq!(tx.hash, tx_hash);
assert_eq!(tx.block_number.unwrap(), 1100845.into())
}
}

View File

@ -15,3 +15,6 @@ serde = "1.0.112"
ethers = { version = "0.1.0", path = "../ethers" }
tokio = { version = "0.2.21", features = ["macros"] }
[features]
celo = ["ethers-core/celo", "ethers-providers/celo"]

View File

@ -1,66 +0,0 @@
use ethers::{
providers::{Http, Provider},
signers::Wallet,
types::TransactionRequest,
utils::{parse_ether, Ganache},
};
use std::convert::TryFrom;
#[tokio::test]
async fn pending_txs_with_confirmations_rinkeby_infura() {
let provider =
Provider::<Http>::try_from("https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27")
.unwrap();
// pls do not drain this key :)
// note: this works even if there's no EIP-155 configured!
let client = "FF7F80C6E9941865266ED1F481263D780169F1D98269C51167D20C630A5FDC8A"
.parse::<Wallet>()
.unwrap()
.connect(provider);
let tx = TransactionRequest::pay(client.address(), parse_ether(1u64).unwrap());
let pending_tx = client.send_transaction(tx, None).await.unwrap();
let hash = *pending_tx;
dbg!(hash);
let receipt = pending_tx.confirmations(3).await.unwrap();
// got the correct receipt
assert_eq!(receipt.transaction_hash, hash);
}
#[tokio::test]
async fn send_eth() {
let port = 8545u64;
let url = format!("http://localhost:{}", port).to_string();
let _ganache = Ganache::new()
.port(port)
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
.spawn();
// this private key belongs to the above mnemonic
let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
.parse()
.unwrap();
// connect to the network
let provider = Provider::<Http>::try_from(url.as_str()).unwrap();
// connect the wallet to the provider
let client = wallet.connect(provider);
// craft the transaction
let tx = TransactionRequest::new()
.send_to_str("986eE0C8B91A58e490Ee59718Cca41056Cf55f24")
.unwrap()
.value(10000);
let balance_before = client.get_balance(client.address(), None).await.unwrap();
// send it!
client.send_transaction(tx, None).await.unwrap();
let balance_after = client.get_balance(client.address(), None).await.unwrap();
assert!(balance_before > balance_after);
}

View File

@ -0,0 +1,98 @@
use ethers::{
providers::{Http, Provider},
signers::Wallet,
types::TransactionRequest,
};
use std::convert::TryFrom;
#[cfg(not(feature = "celo"))]
mod eth_tests {
use super::*;
use ethers::utils::{parse_ether, Ganache};
#[tokio::test]
async fn pending_txs_with_confirmations_rinkeby_infura() {
let provider = Provider::<Http>::try_from(
"https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27",
)
.unwrap();
// pls do not drain this key :)
// note: this works even if there's no EIP-155 configured!
let client = "FF7F80C6E9941865266ED1F481263D780169F1D98269C51167D20C630A5FDC8A"
.parse::<Wallet>()
.unwrap()
.connect(provider);
let tx = TransactionRequest::pay(client.address(), parse_ether(1u64).unwrap());
let pending_tx = client.send_transaction(tx, None).await.unwrap();
let hash = *pending_tx;
dbg!(hash);
let receipt = pending_tx.confirmations(3).await.unwrap();
// got the correct receipt
assert_eq!(receipt.transaction_hash, hash);
}
#[tokio::test]
async fn send_eth() {
let port = 8545u64;
let url = format!("http://localhost:{}", port).to_string();
let _ganache = Ganache::new()
.port(port)
.mnemonic("abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle")
.spawn();
// this private key belongs to the above mnemonic
let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
.parse()
.unwrap();
// connect to the network
let provider = Provider::<Http>::try_from(url.as_str()).unwrap();
// connect the wallet to the provider
let client = wallet.connect(provider);
// craft the transaction
let tx = TransactionRequest::new()
.send_to_str("986eE0C8B91A58e490Ee59718Cca41056Cf55f24")
.unwrap()
.value(10000);
let balance_before = client.get_balance(client.address(), None).await.unwrap();
// send it!
client.send_transaction(tx, None).await.unwrap();
let balance_after = client.get_balance(client.address(), None).await.unwrap();
assert!(balance_before > balance_after);
}
}
#[cfg(feature = "celo")]
mod celo_tests {
use super::*;
#[tokio::test]
async fn test_send_transaction() {
// Celo testnet
let provider =
Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
// Funded with https://celo.org/developers/faucet
// Please do not drain this account :)
let client = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
.parse::<Wallet>()
.unwrap()
.connect(provider);
let balance_before = client.get_balance(client.address(), None).await.unwrap();
let tx = TransactionRequest::pay(client.address(), 100);
let pending_tx = client.send_transaction(tx, None).await.unwrap();
let _receipt = pending_tx.confirmations(3).await.unwrap();
let balance_after = client.get_balance(client.address(), None).await.unwrap();
assert!(balance_before > balance_after);
}
}

View File

@ -28,6 +28,13 @@ full = [
"core",
]
celo = [
"ethers-core/celo",
"ethers-providers/celo",
"ethers-signers/celo",
"ethers-contract/celo",
]
core = ["ethers-core"]
contract = ["ethers-contract"]
providers = ["ethers-providers"]