tests: move to single binary
This commit is contained in:
parent
0d14b4c447
commit
5f2de7b99d
|
@ -1,17 +1,9 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
use ethers_contract::{Contract, ContractFactory, EthEvent};
|
||||||
#![allow(dead_code)]
|
use ethers_core::{
|
||||||
|
abi::Abi,
|
||||||
#[cfg(feature = "abigen")]
|
types::{Address, Bytes},
|
||||||
use ethers_core::types::Address;
|
utils::AnvilInstance,
|
||||||
|
};
|
||||||
#[cfg(feature = "abigen")]
|
|
||||||
use ethers_contract::EthEvent;
|
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
|
||||||
mod derive;
|
|
||||||
|
|
||||||
use ethers_contract::{Contract, ContractFactory};
|
|
||||||
use ethers_core::{abi::Abi, types::Bytes, utils::AnvilInstance};
|
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
use ethers_solc::Solc;
|
use ethers_solc::Solc;
|
||||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
|
@ -1,39 +1,33 @@
|
||||||
#![allow(unused)]
|
use super::common::*;
|
||||||
|
use ethers_contract::{
|
||||||
pub use crate::common::*;
|
abigen, ContractFactory, ContractInstance, EthAbiType, EthEvent, LogMeta, Multicall,
|
||||||
use ethers_contract::{abigen, ContractFactory, EthAbiType};
|
MulticallError, MulticallVersion,
|
||||||
use ethers_core::types::{Filter, ValueOrArray, H256};
|
};
|
||||||
|
use ethers_core::{
|
||||||
#[cfg(not(feature = "celo"))]
|
abi::{encode, AbiEncode, Token, Tokenizable},
|
||||||
mod eth_tests {
|
types::{
|
||||||
use super::*;
|
transaction::eip712::Eip712, Address, BlockId, Bytes, Filter, ValueOrArray, H160, H256,
|
||||||
use ethers_contract::{
|
I256, U256,
|
||||||
ContractInstance, EthEvent, LogMeta, Multicall, MulticallError, MulticallVersion,
|
},
|
||||||
};
|
|
||||||
use ethers_core::{
|
|
||||||
abi::{encode, AbiEncode, Detokenize, Token, Tokenizable},
|
|
||||||
types::{transaction::eip712::Eip712, Address, BlockId, Bytes, H160, I256, U256},
|
|
||||||
utils::{keccak256, Anvil},
|
utils::{keccak256, Anvil},
|
||||||
};
|
};
|
||||||
use ethers_derive_eip712::*;
|
use ethers_derive_eip712::*;
|
||||||
use ethers_providers::{
|
use ethers_providers::{Http, Middleware, MiddlewareError, Provider, StreamExt};
|
||||||
Http, Middleware, MiddlewareError, PendingTransaction, Provider, StreamExt,
|
use ethers_signers::{LocalWallet, Signer};
|
||||||
};
|
use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
|
||||||
use ethers_signers::{LocalWallet, Signer};
|
|
||||||
use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NonClone<M> {
|
pub struct NonClone<M> {
|
||||||
m: M,
|
m: M,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MwErr<M: Middleware>(M::Error);
|
pub struct MwErr<M: Middleware>(M::Error);
|
||||||
|
|
||||||
impl<M> MiddlewareError for MwErr<M>
|
impl<M> MiddlewareError for MwErr<M>
|
||||||
where
|
where
|
||||||
M: Middleware,
|
M: Middleware,
|
||||||
{
|
{
|
||||||
type Inner = M::Error;
|
type Inner = M::Error;
|
||||||
|
|
||||||
fn from_err(src: M::Error) -> Self {
|
fn from_err(src: M::Error) -> Self {
|
||||||
|
@ -43,16 +37,16 @@ mod eth_tests {
|
||||||
fn as_inner(&self) -> Option<&Self::Inner> {
|
fn as_inner(&self) -> Option<&Self::Inner> {
|
||||||
Some(&self.0)
|
Some(&self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: Middleware> std::fmt::Display for MwErr<M> {
|
impl<M: Middleware> std::fmt::Display for MwErr<M> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<M: Middleware> std::error::Error for MwErr<M> {}
|
impl<M: Middleware> std::error::Error for MwErr<M> {}
|
||||||
|
|
||||||
impl<M: Middleware> Middleware for NonClone<M> {
|
impl<M: Middleware> Middleware for NonClone<M> {
|
||||||
type Error = MwErr<M>;
|
type Error = MwErr<M>;
|
||||||
|
|
||||||
type Provider = M::Provider;
|
type Provider = M::Provider;
|
||||||
|
@ -62,12 +56,12 @@ mod eth_tests {
|
||||||
fn inner(&self) -> &Self::Inner {
|
fn inner(&self) -> &Self::Inner {
|
||||||
&self.m
|
&self.m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is not a test. It is a compile check. :)
|
// this is not a test. It is a compile check. :)
|
||||||
// It exists to ensure that trait bounds on contract internal behave as
|
// It exists to ensure that trait bounds on contract internal behave as
|
||||||
// expected. It should not be run
|
// expected. It should not be run
|
||||||
fn it_compiles() {
|
fn it_compiles() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
|
|
||||||
// launch anvil
|
// launch anvil
|
||||||
|
@ -102,10 +96,10 @@ mod eth_tests {
|
||||||
// ContractInternal::new(H160::default(), abi, non_clone_mware);
|
// ContractInternal::new(H160::default(), abi, non_clone_mware);
|
||||||
|
|
||||||
// let _ = c.method::<(), ()>("notARealMethod", ());
|
// let _ = c.method::<(), ()>("notARealMethod", ());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn deploy_and_call_contract() {
|
async fn deploy_and_call_contract() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
|
|
||||||
// launch anvil
|
// launch anvil
|
||||||
|
@ -141,10 +135,8 @@ mod eth_tests {
|
||||||
// need to declare the method first, and only then send it
|
// need to declare the method first, and only then send it
|
||||||
// this is because it internally clones an Arc which would otherwise
|
// this is because it internally clones an Arc which would otherwise
|
||||||
// get immediately dropped
|
// get immediately dropped
|
||||||
let contract_call = contract
|
let contract_call =
|
||||||
.connect(client2.clone())
|
contract.connect(client2.clone()).method::<_, H256>("setValue", "hi".to_owned()).unwrap();
|
||||||
.method::<_, H256>("setValue", "hi".to_owned())
|
|
||||||
.unwrap();
|
|
||||||
let calldata = contract_call.calldata().unwrap();
|
let calldata = contract_call.calldata().unwrap();
|
||||||
let gas_estimate = contract_call.estimate_gas().await.unwrap();
|
let gas_estimate = contract_call.estimate_gas().await.unwrap();
|
||||||
let contract_call = contract_call.legacy();
|
let contract_call = contract_call.legacy();
|
||||||
|
@ -176,11 +168,11 @@ mod eth_tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(feature = "abigen")]
|
#[cfg(feature = "abigen")]
|
||||||
async fn get_past_events() {
|
async fn get_past_events() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
let client = connect(&anvil, 0);
|
let client = connect(&anvil, 0);
|
||||||
|
@ -215,11 +207,11 @@ mod eth_tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(logs[0].new_value, "initial value");
|
assert_eq!(logs[0].new_value, "initial value");
|
||||||
assert_eq!(logs.len(), 1);
|
assert_eq!(logs.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(feature = "abigen")]
|
#[cfg(feature = "abigen")]
|
||||||
async fn get_events_with_meta() {
|
async fn get_events_with_meta() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
let client = connect(&anvil, 0);
|
let client = connect(&anvil, 0);
|
||||||
|
@ -248,10 +240,10 @@ mod eth_tests {
|
||||||
let tx = block.transactions[0];
|
let tx = block.transactions[0];
|
||||||
assert_eq!(meta.transaction_hash, tx);
|
assert_eq!(meta.transaction_hash, tx);
|
||||||
assert_eq!(meta.transaction_index, 0.into());
|
assert_eq!(meta.transaction_index, 0.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn call_past_state() {
|
async fn call_past_state() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
let client = connect(&anvil, 0);
|
let client = connect(&anvil, 0);
|
||||||
|
@ -300,11 +292,11 @@ mod eth_tests {
|
||||||
// .await
|
// .await
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// assert_eq!(value, "initial value");
|
// assert_eq!(value, "initial value");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
async fn call_past_hash_test() {
|
async fn call_past_hash_test() {
|
||||||
// geth --dev --http --http.api eth,web3
|
// geth --dev --http --http.api eth,web3
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
|
||||||
|
@ -336,11 +328,11 @@ mod eth_tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(value, "initial value");
|
assert_eq!(value, "initial value");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(feature = "abigen")]
|
#[cfg(feature = "abigen")]
|
||||||
async fn watch_events() {
|
async fn watch_events() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
let client = connect(&anvil, 0);
|
let client = connect(&anvil, 0);
|
||||||
|
@ -380,10 +372,10 @@ mod eth_tests {
|
||||||
let hash = client.get_block(num + i + 1).await.unwrap().unwrap().hash.unwrap();
|
let hash = client.get_block(num + i + 1).await.unwrap().unwrap().hash.unwrap();
|
||||||
assert_eq!(meta.block_hash, hash);
|
assert_eq!(meta.block_hash, hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn watch_subscription_events_multiple_addresses() {
|
async fn watch_subscription_events_multiple_addresses() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
let client = connect(&anvil, 0);
|
let client = connect(&anvil, 0);
|
||||||
|
@ -409,10 +401,10 @@ mod eth_tests {
|
||||||
let log_2 = stream.next().await.unwrap();
|
let log_2 = stream.next().await.unwrap();
|
||||||
assert_eq!(log_1.address, contract_1.address());
|
assert_eq!(log_1.address, contract_1.address());
|
||||||
assert_eq!(log_2.address, contract_2.address());
|
assert_eq!(log_2.address, contract_2.address());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn build_event_of_type() {
|
async fn build_event_of_type() {
|
||||||
abigen!(
|
abigen!(
|
||||||
AggregatorInterface,
|
AggregatorInterface,
|
||||||
r#"[
|
r#"[
|
||||||
|
@ -424,10 +416,10 @@ mod eth_tests {
|
||||||
let client = connect(&anvil, 0);
|
let client = connect(&anvil, 0);
|
||||||
let event = ethers_contract::Contract::event_of_type::<AnswerUpdatedFilter>(client);
|
let event = ethers_contract::Contract::event_of_type::<AnswerUpdatedFilter>(client);
|
||||||
assert_eq!(event.filter, Filter::new().event(&AnswerUpdatedFilter::abi_signature()));
|
assert_eq!(event.filter, Filter::new().event(&AnswerUpdatedFilter::abi_signature()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn signer_on_node() {
|
async fn signer_on_node() {
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
|
||||||
// spawn anvil
|
// spawn anvil
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
|
@ -453,13 +445,12 @@ mod eth_tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let value: String =
|
let value: String = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap();
|
||||||
contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap();
|
|
||||||
assert_eq!(value, "hi");
|
assert_eq!(value, "hi");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn multicall_aggregate() {
|
async fn multicall_aggregate() {
|
||||||
// get ABI and bytecode for the Multicall contract
|
// get ABI and bytecode for the Multicall contract
|
||||||
let (multicall_abi, multicall_bytecode) = compile_contract("Multicall3", "Multicall.sol");
|
let (multicall_abi, multicall_bytecode) = compile_contract("Multicall3", "Multicall.sol");
|
||||||
|
|
||||||
|
@ -488,23 +479,16 @@ mod eth_tests {
|
||||||
let client4 = connect(&anvil, 3);
|
let client4 = connect(&anvil, 3);
|
||||||
|
|
||||||
// create a factory which will be used to deploy instances of the contract
|
// create a factory which will be used to deploy instances of the contract
|
||||||
let multicall_factory =
|
let multicall_factory = ContractFactory::new(multicall_abi, multicall_bytecode, client.clone());
|
||||||
ContractFactory::new(multicall_abi, multicall_bytecode, client.clone());
|
|
||||||
let simple_factory = ContractFactory::new(abi.clone(), bytecode.clone(), client2.clone());
|
let simple_factory = ContractFactory::new(abi.clone(), bytecode.clone(), client2.clone());
|
||||||
let not_so_simple_factory =
|
let not_so_simple_factory =
|
||||||
ContractFactory::new(not_so_simple_abi, not_so_simple_bytecode, client3.clone());
|
ContractFactory::new(not_so_simple_abi, not_so_simple_bytecode, client3.clone());
|
||||||
|
|
||||||
let multicall_contract =
|
let multicall_contract = multicall_factory.deploy(()).unwrap().legacy().send().await.unwrap();
|
||||||
multicall_factory.deploy(()).unwrap().legacy().send().await.unwrap();
|
|
||||||
let addr = multicall_contract.address();
|
let addr = multicall_contract.address();
|
||||||
|
|
||||||
let simple_contract = simple_factory
|
let simple_contract =
|
||||||
.deploy("the first one".to_string())
|
simple_factory.deploy("the first one".to_string()).unwrap().legacy().send().await.unwrap();
|
||||||
.unwrap()
|
|
||||||
.legacy()
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let not_so_simple_contract = not_so_simple_factory
|
let not_so_simple_contract = not_so_simple_factory
|
||||||
.deploy("the second one".to_string())
|
.deploy("the second one".to_string())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -534,8 +518,7 @@ mod eth_tests {
|
||||||
|
|
||||||
// get the calls for `value` and `last_sender` for both SimpleStorage contracts
|
// get the calls for `value` and `last_sender` for both SimpleStorage contracts
|
||||||
let value = simple_contract.method::<_, String>("getValue", ()).unwrap();
|
let value = simple_contract.method::<_, String>("getValue", ()).unwrap();
|
||||||
let value2 =
|
let value2 = not_so_simple_contract.method::<_, (String, Address)>("getValues", ()).unwrap();
|
||||||
not_so_simple_contract.method::<_, (String, Address)>("getValues", ()).unwrap();
|
|
||||||
let last_sender = simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
let last_sender = simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
||||||
let last_sender2 = not_so_simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
let last_sender2 = not_so_simple_contract.method::<_, Address>("lastSender", ()).unwrap();
|
||||||
|
|
||||||
|
@ -639,8 +622,7 @@ mod eth_tests {
|
||||||
|
|
||||||
multicall.add_calls(
|
multicall.add_calls(
|
||||||
false,
|
false,
|
||||||
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap())
|
std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap()).take(17),
|
||||||
.take(17),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let tokens = multicall.call_raw().await.unwrap();
|
let tokens = multicall.call_raw().await.unwrap();
|
||||||
|
@ -682,14 +664,10 @@ mod eth_tests {
|
||||||
.connect(client3.clone())
|
.connect(client3.clone())
|
||||||
.method::<_, H256>("setValue", ("this reverted".to_owned(), true))
|
.method::<_, H256>("setValue", ("this reverted".to_owned(), true))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let get_value_call = reverting_contract
|
let get_value_call =
|
||||||
.connect(client2.clone())
|
reverting_contract.connect(client2.clone()).method::<_, String>("getValue", false).unwrap();
|
||||||
.method::<_, String>("getValue", false)
|
let get_value_reverting_call =
|
||||||
.unwrap();
|
reverting_contract.connect(client.clone()).method::<_, String>("getValue", true).unwrap();
|
||||||
let get_value_reverting_call = reverting_contract
|
|
||||||
.connect(client.clone())
|
|
||||||
.method::<_, String>("getValue", true)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// .send reverts
|
// .send reverts
|
||||||
// don't allow revert
|
// don't allow revert
|
||||||
|
@ -799,18 +777,17 @@ mod eth_tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
// custom error with data revert
|
// custom error with data revert
|
||||||
let custom_error_with_data = reverting_contract
|
let custom_error_with_data =
|
||||||
.method::<_, H256>("customErrorWithData", ("Data".to_string()))
|
reverting_contract.method::<_, H256>("customErrorWithData", ("Data".to_string())).unwrap();
|
||||||
.unwrap();
|
|
||||||
multicall.clear_calls().add_call(custom_error_with_data, true);
|
multicall.clear_calls().add_call(custom_error_with_data, true);
|
||||||
let err = multicall.call::<(Bytes,)>().await.unwrap_err();
|
let err = multicall.call::<(Bytes,)>().await.unwrap_err();
|
||||||
let bytes = err.as_revert().unwrap();
|
let bytes = err.as_revert().unwrap();
|
||||||
assert_eq!(bytes[..4], keccak256("CustomErrorWithData(string)")[..4]);
|
assert_eq!(bytes[..4], keccak256("CustomErrorWithData(string)")[..4]);
|
||||||
assert_eq!(bytes[4..], encode(&[Token::String("Data".to_string())]));
|
assert_eq!(bytes[4..], encode(&[Token::String("Data".to_string())]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_derive_eip712() {
|
async fn test_derive_eip712() {
|
||||||
// Generate Contract ABI Bindings
|
// Generate Contract ABI Bindings
|
||||||
abigen!(
|
abigen!(
|
||||||
DeriveEip712Test,
|
DeriveEip712Test,
|
||||||
|
@ -947,5 +924,4 @@ mod eth_tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(verify, "typed data signature failed!");
|
assert!(verify, "typed data signature failed!");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
// #![allow(clippy::extra_unused_type_parameters)]
|
#![allow(clippy::extra_unused_type_parameters)]
|
||||||
|
#![cfg(feature = "abigen")]
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
|
||||||
mod abigen;
|
mod abigen;
|
||||||
pub(crate) mod common;
|
|
||||||
|
|
||||||
#[cfg(feature = "abigen")]
|
mod derive;
|
||||||
mod contract;
|
|
||||||
|
|
||||||
mod contract_call;
|
mod contract_call;
|
||||||
|
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
||||||
|
mod contract;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![allow(clippy::extra_unused_type_parameters)]
|
||||||
|
|
||||||
use ethers_contract_derive::EthAbiType;
|
use ethers_contract_derive::EthAbiType;
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
types::{
|
types::{
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
|
|
||||||
use ethers_core::{rand::thread_rng, types::U64};
|
use ethers_core::{rand::thread_rng, types::U64};
|
||||||
use ethers_middleware::{
|
use ethers_middleware::{
|
||||||
builder::MiddlewareBuilder,
|
builder::MiddlewareBuilder,
|
|
@ -1,5 +1,3 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
|
|
||||||
use ethers_core::types::*;
|
use ethers_core::types::*;
|
||||||
use ethers_middleware::{
|
use ethers_middleware::{
|
||||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
|
@ -1,5 +1,3 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ethers_core::{types::*, utils::Anvil};
|
use ethers_core::{types::*, utils::Anvil};
|
||||||
use ethers_middleware::gas_oracle::{
|
use ethers_middleware::gas_oracle::{
|
|
@ -0,0 +1,19 @@
|
||||||
|
#![allow(clippy::extra_unused_type_parameters)]
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
|
||||||
|
mod gas_escalator;
|
||||||
|
|
||||||
|
mod gas_oracle;
|
||||||
|
|
||||||
|
mod signer;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod nonce_manager;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod stack;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod transformer;
|
|
@ -1,5 +1,3 @@
|
||||||
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
|
||||||
|
|
||||||
use ethers_core::{types::*, utils::Anvil};
|
use ethers_core::{types::*, utils::Anvil};
|
||||||
use ethers_middleware::MiddlewareBuilder;
|
use ethers_middleware::MiddlewareBuilder;
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider};
|
|
@ -0,0 +1,354 @@
|
||||||
|
use ethers_contract::ContractFactory;
|
||||||
|
use ethers_core::{abi::Abi, types::*, utils::parse_ether};
|
||||||
|
use ethers_middleware::signer::SignerMiddleware;
|
||||||
|
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider};
|
||||||
|
use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer};
|
||||||
|
use ethers_solc::Solc;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
|
sync::{atomic::AtomicU8, Arc},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
|
||||||
|
TestWallets {
|
||||||
|
mnemonic: MnemonicBuilder::default()
|
||||||
|
// Please don't drain this :)
|
||||||
|
.phrase("impose air often almost medal sudden finish quote dwarf devote theme layer"),
|
||||||
|
next: Default::default(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct TestWallets {
|
||||||
|
mnemonic: MnemonicBuilder<English>,
|
||||||
|
next: AtomicU8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl TestWallets {
|
||||||
|
/// Helper for funding the wallets with an instantiated provider
|
||||||
|
#[allow(unused)]
|
||||||
|
pub async fn fund<T: JsonRpcClient, U: Into<u32>>(&self, provider: &Provider<T>, n: U) {
|
||||||
|
let addrs = (0..n.into()).map(|i| self.get(i).address()).collect::<Vec<_>>();
|
||||||
|
// hardcoded funder address private key, GOERLI
|
||||||
|
let signer = "39aa18eeb5d12c071e5f19d8e9375a872e90cb1f2fa640384ffd8800a2f3e8f1"
|
||||||
|
.parse::<LocalWallet>()
|
||||||
|
.unwrap()
|
||||||
|
.with_chain_id(provider.get_chainid().await.unwrap().as_u64());
|
||||||
|
let provider = SignerMiddleware::new(provider, signer);
|
||||||
|
let addr = provider.address();
|
||||||
|
|
||||||
|
let mut nonce = provider.get_transaction_count(addr, None).await.unwrap();
|
||||||
|
let mut pending_txs = Vec::new();
|
||||||
|
for addr in addrs {
|
||||||
|
println!("Funding wallet {addr:?}");
|
||||||
|
let tx = TransactionRequest::new()
|
||||||
|
.nonce(nonce)
|
||||||
|
.to(addr)
|
||||||
|
// 0.1 eth per wallet
|
||||||
|
.value(parse_ether("1").unwrap());
|
||||||
|
pending_txs.push(
|
||||||
|
provider.send_transaction(tx, Some(BlockNumber::Pending.into())).await.unwrap(),
|
||||||
|
);
|
||||||
|
nonce += 1.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
futures_util::future::join_all(pending_txs).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> LocalWallet {
|
||||||
|
let idx = self.next.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||||
|
|
||||||
|
// println!("Got wallet {:?}", wallet.address());
|
||||||
|
self.get(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<T: Into<u32>>(&self, idx: T) -> LocalWallet {
|
||||||
|
self.mnemonic
|
||||||
|
.clone()
|
||||||
|
.index(idx)
|
||||||
|
.expect("index not found")
|
||||||
|
.build()
|
||||||
|
.expect("cannot build wallet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod eth_tests {
|
||||||
|
use super::*;
|
||||||
|
use ethers_core::utils::Anvil;
|
||||||
|
use ethers_providers::GOERLI;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn send_eth() {
|
||||||
|
let anvil = Anvil::new().spawn();
|
||||||
|
|
||||||
|
// this private key belongs to the above mnemonic
|
||||||
|
let wallet: LocalWallet = anvil.keys()[0].clone().into();
|
||||||
|
let wallet2: LocalWallet = anvil.keys()[1].clone().into();
|
||||||
|
|
||||||
|
// connect to the network
|
||||||
|
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
||||||
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(10u64));
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||||
|
let provider = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap();
|
||||||
|
|
||||||
|
// craft the transaction
|
||||||
|
let tx = TransactionRequest::new().to(wallet2.address()).value(10000).chain_id(chain_id);
|
||||||
|
|
||||||
|
let balance_before = provider.get_balance(provider.address(), None).await.unwrap();
|
||||||
|
|
||||||
|
// send it!
|
||||||
|
provider.send_transaction(tx, None).await.unwrap();
|
||||||
|
|
||||||
|
let balance_after = provider.get_balance(provider.address(), None).await.unwrap();
|
||||||
|
|
||||||
|
assert!(balance_before > balance_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
// hardhat compatibility test, to show hardhat rejects tx signed for other chains
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
|
async fn send_with_chain_id_hardhat() {
|
||||||
|
let wallet: LocalWallet =
|
||||||
|
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
|
||||||
|
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
||||||
|
let client = SignerMiddleware::new(provider, wallet);
|
||||||
|
|
||||||
|
let tx = TransactionRequest::new().to(Address::random()).value(100u64);
|
||||||
|
let res = client.send_transaction(tx, None).await;
|
||||||
|
|
||||||
|
let err = res.unwrap_err();
|
||||||
|
assert!(err.to_string().contains(
|
||||||
|
"Trying to send an incompatible EIP-155 transaction, signed for another chain."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[ignore]
|
||||||
|
async fn send_with_chain_id_anvil() {
|
||||||
|
let wallet: LocalWallet =
|
||||||
|
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
|
||||||
|
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
||||||
|
let client = SignerMiddleware::new(provider, wallet);
|
||||||
|
|
||||||
|
let tx = TransactionRequest::new().to(Address::random()).value(100u64);
|
||||||
|
let res = client.send_transaction(tx, None).await;
|
||||||
|
|
||||||
|
let _err = res.unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn pending_txs_with_confirmations_testnet() {
|
||||||
|
let provider = GOERLI.provider().interval(Duration::from_millis(3000));
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
|
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
||||||
|
let address = wallet.address();
|
||||||
|
let provider = SignerMiddleware::new(provider, wallet);
|
||||||
|
generic_pending_txs_test(provider, address).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// different keys to avoid nonce errors
|
||||||
|
#[tokio::test]
|
||||||
|
async fn websocket_pending_txs_with_confirmations_testnet() {
|
||||||
|
let provider = GOERLI.ws().await.interval(Duration::from_millis(3000));
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
|
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
||||||
|
let address = wallet.address();
|
||||||
|
let provider = SignerMiddleware::new(provider, wallet);
|
||||||
|
generic_pending_txs_test(provider, address).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generic_pending_txs_test<M: Middleware>(provider: M, who: Address) {
|
||||||
|
let tx = TransactionRequest::new().to(who).from(who);
|
||||||
|
let pending_tx = provider.send_transaction(tx, None).await.unwrap();
|
||||||
|
let tx_hash = *pending_tx;
|
||||||
|
let receipt = pending_tx.confirmations(1).await.unwrap().unwrap();
|
||||||
|
// got the correct receipt
|
||||||
|
assert_eq!(receipt.transaction_hash, tx_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn typed_txs() {
|
||||||
|
let provider = GOERLI.provider();
|
||||||
|
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap();
|
||||||
|
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
||||||
|
let address = wallet.address();
|
||||||
|
// our wallet
|
||||||
|
let provider = SignerMiddleware::new(provider, wallet);
|
||||||
|
|
||||||
|
// Uncomment the below and run this test to re-fund the wallets if they get drained.
|
||||||
|
// Would be ideal if we'd have a way to do this automatically, but this should be
|
||||||
|
// happening rarely enough that it doesn't matter.
|
||||||
|
// WALLETS.fund(provider.provider(), 10u32).await;
|
||||||
|
|
||||||
|
async fn check_tx<P: JsonRpcClient + Clone>(
|
||||||
|
pending_tx: ethers_providers::PendingTransaction<'_, P>,
|
||||||
|
expected: u64,
|
||||||
|
) {
|
||||||
|
let provider = pending_tx.provider();
|
||||||
|
let receipt = pending_tx.await.unwrap().unwrap();
|
||||||
|
let tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
||||||
|
assert_eq!(receipt.transaction_type, Some(expected.into()));
|
||||||
|
assert_eq!(tx.transaction_type, Some(expected.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonce = provider.get_transaction_count(address, None).await.unwrap();
|
||||||
|
let bn = Some(BlockNumber::Pending.into());
|
||||||
|
let gas_price = provider.get_gas_price().await.unwrap() * 125 / 100;
|
||||||
|
|
||||||
|
let tx =
|
||||||
|
TransactionRequest::new().from(address).to(address).nonce(nonce).gas_price(gas_price);
|
||||||
|
let tx1 = provider.send_transaction(tx.clone(), bn).await.unwrap();
|
||||||
|
|
||||||
|
let tx = tx.clone().from(address).to(address).nonce(nonce + 1).with_access_list(vec![]);
|
||||||
|
let tx2 = provider.send_transaction(tx, bn).await.unwrap();
|
||||||
|
|
||||||
|
let tx = Eip1559TransactionRequest::new()
|
||||||
|
.from(address)
|
||||||
|
.to(address)
|
||||||
|
.nonce(nonce + 2)
|
||||||
|
.max_fee_per_gas(gas_price)
|
||||||
|
.max_priority_fee_per_gas(gas_price);
|
||||||
|
let tx3 = provider.send_transaction(tx, bn).await.unwrap();
|
||||||
|
|
||||||
|
futures_util::join!(check_tx(tx1, 0), check_tx(tx2, 1), check_tx(tx3, 2),);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn send_transaction_handles_tx_from_field() {
|
||||||
|
// launch anvil
|
||||||
|
let anvil = Anvil::new().spawn();
|
||||||
|
|
||||||
|
// grab 2 wallets
|
||||||
|
let signer: LocalWallet = anvil.keys()[0].clone().into();
|
||||||
|
let other: LocalWallet = anvil.keys()[1].clone().into();
|
||||||
|
|
||||||
|
// connect to the network
|
||||||
|
let provider = Provider::try_from(anvil.endpoint()).unwrap();
|
||||||
|
let provider =
|
||||||
|
SignerMiddleware::new_with_provider_chain(provider, signer.clone()).await.unwrap();
|
||||||
|
|
||||||
|
// sending a TransactionRequest with a from field of None should result
|
||||||
|
// in a transaction from the signer address
|
||||||
|
let request_from_none = TransactionRequest::new();
|
||||||
|
let receipt = provider
|
||||||
|
.send_transaction(request_from_none, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sent_tx.from, signer.address());
|
||||||
|
|
||||||
|
// sending a TransactionRequest with the signer as the from address should
|
||||||
|
// result in a transaction from the signer address
|
||||||
|
let request_from_signer = TransactionRequest::new().from(signer.address());
|
||||||
|
let receipt = provider
|
||||||
|
.send_transaction(request_from_signer, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sent_tx.from, signer.address());
|
||||||
|
|
||||||
|
// sending a TransactionRequest with a from address that is not the signer
|
||||||
|
// should result in a transaction from the specified address
|
||||||
|
let request_from_other = TransactionRequest::new().from(other.address());
|
||||||
|
let receipt = provider
|
||||||
|
.send_transaction(request_from_other, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(sent_tx.from, other.address());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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()
|
||||||
|
.interval(Duration::from_millis(3000u64));
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||||
|
|
||||||
|
// Funded with https://celo.org/developers/faucet
|
||||||
|
// Please do not drain this account :)
|
||||||
|
let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
|
||||||
|
.parse::<LocalWallet>()
|
||||||
|
.unwrap()
|
||||||
|
.with_chain_id(chain_id);
|
||||||
|
let client = SignerMiddleware::new(provider, wallet);
|
||||||
|
|
||||||
|
let balance_before = client.get_balance(client.address(), None).await.unwrap();
|
||||||
|
let tx = TransactionRequest::pay(client.address(), 100);
|
||||||
|
let _receipt =
|
||||||
|
client.send_transaction(tx, None).await.unwrap().confirmations(3).await.unwrap();
|
||||||
|
let balance_after = client.get_balance(client.address(), None).await.unwrap();
|
||||||
|
assert!(balance_before > balance_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn deploy_and_call_contract() {
|
||||||
|
// compiles the given contract and returns the ABI and Bytecode
|
||||||
|
fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
|
||||||
|
let path = format!("./tests/solidity-contracts/{path}");
|
||||||
|
let compiled = Solc::default().compile_source(&path).unwrap();
|
||||||
|
let contract = compiled.get(&path, name).expect("could not find contract");
|
||||||
|
let (abi, bin, _) = contract.into_parts_or_default();
|
||||||
|
(abi, bin)
|
||||||
|
}
|
||||||
|
|
||||||
|
let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
|
||||||
|
|
||||||
|
// Celo testnet
|
||||||
|
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
||||||
|
.unwrap()
|
||||||
|
.interval(Duration::from_millis(6000));
|
||||||
|
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
||||||
|
|
||||||
|
// Funded with https://celo.org/developers/faucet
|
||||||
|
let wallet = "58ea5643a78c36926ad5128a6b0d8dfcc7fc705788a993b1c724be3469bc9697"
|
||||||
|
.parse::<LocalWallet>()
|
||||||
|
.unwrap()
|
||||||
|
.with_chain_id(chain_id);
|
||||||
|
let client = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap();
|
||||||
|
let client = Arc::new(client);
|
||||||
|
|
||||||
|
let factory = ContractFactory::new(abi, bytecode, client);
|
||||||
|
let deployer = factory.deploy(()).unwrap().legacy();
|
||||||
|
let contract = deployer.block(BlockNumber::Pending).send().await.unwrap();
|
||||||
|
|
||||||
|
let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap();
|
||||||
|
assert_eq!(value, 0.into());
|
||||||
|
|
||||||
|
// make a state mutating transaction
|
||||||
|
// gas estimation costs are sometimes under-reported on celo,
|
||||||
|
// so we manually set it to avoid failures
|
||||||
|
let call = contract.method::<_, H256>("setValue", U256::from(1)).unwrap().gas(100000);
|
||||||
|
let pending_tx = call.send().await.unwrap();
|
||||||
|
let _receipt = pending_tx.await.unwrap();
|
||||||
|
|
||||||
|
let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap();
|
||||||
|
assert_eq!(value, 1.into());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,3 @@
|
||||||
#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
|
|
||||||
|
|
||||||
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
|
use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
|
||||||
use ethers_middleware::{
|
use ethers_middleware::{
|
||||||
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
|
|
@ -1,339 +0,0 @@
|
||||||
#![allow(unused)]
|
|
||||||
|
|
||||||
use ethers_contract::ContractFactory;
|
|
||||||
use ethers_core::{
|
|
||||||
abi::Abi,
|
|
||||||
types::*,
|
|
||||||
utils::{parse_ether, parse_units, Anvil},
|
|
||||||
};
|
|
||||||
use ethers_middleware::signer::SignerMiddleware;
|
|
||||||
use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, GOERLI};
|
|
||||||
use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer};
|
|
||||||
use ethers_solc::Solc;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::{
|
|
||||||
convert::TryFrom,
|
|
||||||
iter::Cycle,
|
|
||||||
sync::{atomic::AtomicU8, Arc},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
|
|
||||||
TestWallets {
|
|
||||||
mnemonic: MnemonicBuilder::default()
|
|
||||||
// Please don't drain this :)
|
|
||||||
.phrase("impose air often almost medal sudden finish quote dwarf devote theme layer"),
|
|
||||||
next: Default::default(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
async fn send_eth() {
|
|
||||||
let anvil = Anvil::new().spawn();
|
|
||||||
|
|
||||||
// this private key belongs to the above mnemonic
|
|
||||||
let wallet: LocalWallet = anvil.keys()[0].clone().into();
|
|
||||||
let wallet2: LocalWallet = anvil.keys()[1].clone().into();
|
|
||||||
|
|
||||||
// connect to the network
|
|
||||||
let provider = Provider::<Http>::try_from(anvil.endpoint())
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(10u64));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
|
||||||
let provider = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap();
|
|
||||||
|
|
||||||
// craft the transaction
|
|
||||||
let tx = TransactionRequest::new().to(wallet2.address()).value(10000).chain_id(chain_id);
|
|
||||||
|
|
||||||
let balance_before = provider.get_balance(provider.address(), None).await.unwrap();
|
|
||||||
|
|
||||||
// send it!
|
|
||||||
provider.send_transaction(tx, None).await.unwrap();
|
|
||||||
|
|
||||||
let balance_after = provider.get_balance(provider.address(), None).await.unwrap();
|
|
||||||
|
|
||||||
assert!(balance_before > balance_after);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hardhat compatibility test, to show hardhat rejects tx signed for other chains
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
#[ignore]
|
|
||||||
async fn send_with_chain_id_hardhat() {
|
|
||||||
let wallet: LocalWallet =
|
|
||||||
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
|
|
||||||
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
|
||||||
let client = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
let tx = TransactionRequest::new().to(Address::random()).value(100u64);
|
|
||||||
let res = client.send_transaction(tx, None).await;
|
|
||||||
|
|
||||||
let err = res.unwrap_err();
|
|
||||||
assert!(err
|
|
||||||
.to_string()
|
|
||||||
.contains("Trying to send an incompatible EIP-155 transaction, signed for another chain."));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
#[ignore]
|
|
||||||
async fn send_with_chain_id_anvil() {
|
|
||||||
let wallet: LocalWallet =
|
|
||||||
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
|
|
||||||
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
|
||||||
let client = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
let tx = TransactionRequest::new().to(Address::random()).value(100u64);
|
|
||||||
let res = client.send_transaction(tx, None).await;
|
|
||||||
|
|
||||||
let _err = res.unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
async fn pending_txs_with_confirmations_testnet() {
|
|
||||||
let provider = GOERLI.provider().interval(Duration::from_millis(3000));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap();
|
|
||||||
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
|
||||||
let address = wallet.address();
|
|
||||||
let provider = SignerMiddleware::new(provider, wallet);
|
|
||||||
generic_pending_txs_test(provider, address).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// different keys to avoid nonce errors
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
async fn websocket_pending_txs_with_confirmations_testnet() {
|
|
||||||
let provider = GOERLI.ws().await.interval(Duration::from_millis(3000));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap();
|
|
||||||
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
|
||||||
let address = wallet.address();
|
|
||||||
let provider = SignerMiddleware::new(provider, wallet);
|
|
||||||
generic_pending_txs_test(provider, address).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
async fn generic_pending_txs_test<M: Middleware>(provider: M, who: Address) {
|
|
||||||
let tx = TransactionRequest::new().to(who).from(who);
|
|
||||||
let pending_tx = provider.send_transaction(tx, None).await.unwrap();
|
|
||||||
let tx_hash = *pending_tx;
|
|
||||||
let receipt = pending_tx.confirmations(1).await.unwrap().unwrap();
|
|
||||||
// got the correct receipt
|
|
||||||
assert_eq!(receipt.transaction_hash, tx_hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
async fn typed_txs() {
|
|
||||||
let provider = GOERLI.provider();
|
|
||||||
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap();
|
|
||||||
let wallet = WALLETS.next().with_chain_id(chain_id.as_u64());
|
|
||||||
let address = wallet.address();
|
|
||||||
// our wallet
|
|
||||||
let provider = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
// Uncomment the below and run this test to re-fund the wallets if they get drained.
|
|
||||||
// Would be ideal if we'd have a way to do this automatically, but this should be
|
|
||||||
// happening rarely enough that it doesn't matter.
|
|
||||||
// WALLETS.fund(provider.provider(), 10u32).await;
|
|
||||||
|
|
||||||
async fn check_tx<P: JsonRpcClient + Clone>(
|
|
||||||
pending_tx: ethers_providers::PendingTransaction<'_, P>,
|
|
||||||
expected: u64,
|
|
||||||
) {
|
|
||||||
let provider = pending_tx.provider();
|
|
||||||
let receipt = pending_tx.await.unwrap().unwrap();
|
|
||||||
let tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
|
||||||
assert_eq!(receipt.transaction_type, Some(expected.into()));
|
|
||||||
assert_eq!(tx.transaction_type, Some(expected.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let nonce = provider.get_transaction_count(address, None).await.unwrap();
|
|
||||||
let bn = Some(BlockNumber::Pending.into());
|
|
||||||
let gas_price = provider.get_gas_price().await.unwrap() * 125 / 100;
|
|
||||||
|
|
||||||
let tx = TransactionRequest::new().from(address).to(address).nonce(nonce).gas_price(gas_price);
|
|
||||||
let tx1 = provider.send_transaction(tx.clone(), bn).await.unwrap();
|
|
||||||
|
|
||||||
let tx = tx.clone().from(address).to(address).nonce(nonce + 1).with_access_list(vec![]);
|
|
||||||
let tx2 = provider.send_transaction(tx, bn).await.unwrap();
|
|
||||||
|
|
||||||
let tx = Eip1559TransactionRequest::new()
|
|
||||||
.from(address)
|
|
||||||
.to(address)
|
|
||||||
.nonce(nonce + 2)
|
|
||||||
.max_fee_per_gas(gas_price)
|
|
||||||
.max_priority_fee_per_gas(gas_price);
|
|
||||||
let tx3 = provider.send_transaction(tx, bn).await.unwrap();
|
|
||||||
|
|
||||||
futures_util::join!(check_tx(tx1, 0), check_tx(tx2, 1), check_tx(tx3, 2),);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(feature = "celo")]
|
|
||||||
async fn test_send_transaction() {
|
|
||||||
// Celo testnet
|
|
||||||
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(3000u64));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
|
||||||
|
|
||||||
// Funded with https://celo.org/developers/faucet
|
|
||||||
// Please do not drain this account :)
|
|
||||||
let wallet = "d652abb81e8c686edba621a895531b1f291289b63b5ef09a94f686a5ecdd5db1"
|
|
||||||
.parse::<LocalWallet>()
|
|
||||||
.unwrap()
|
|
||||||
.with_chain_id(chain_id);
|
|
||||||
let client = SignerMiddleware::new(provider, wallet);
|
|
||||||
|
|
||||||
let balance_before = client.get_balance(client.address(), None).await.unwrap();
|
|
||||||
let tx = TransactionRequest::pay(client.address(), 100);
|
|
||||||
let _receipt = client.send_transaction(tx, None).await.unwrap().confirmations(3).await.unwrap();
|
|
||||||
let balance_after = client.get_balance(client.address(), None).await.unwrap();
|
|
||||||
assert!(balance_before > balance_after);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(not(feature = "celo"))]
|
|
||||||
async fn send_transaction_handles_tx_from_field() {
|
|
||||||
// launch anvil
|
|
||||||
let anvil = Anvil::new().spawn();
|
|
||||||
|
|
||||||
// grab 2 wallets
|
|
||||||
let signer: LocalWallet = anvil.keys()[0].clone().into();
|
|
||||||
let other: LocalWallet = anvil.keys()[1].clone().into();
|
|
||||||
|
|
||||||
// connect to the network
|
|
||||||
let provider = Provider::try_from(anvil.endpoint()).unwrap();
|
|
||||||
let provider =
|
|
||||||
SignerMiddleware::new_with_provider_chain(provider, signer.clone()).await.unwrap();
|
|
||||||
|
|
||||||
// sending a TransactionRequest with a from field of None should result
|
|
||||||
// in a transaction from the signer address
|
|
||||||
let request_from_none = TransactionRequest::new();
|
|
||||||
let receipt =
|
|
||||||
provider.send_transaction(request_from_none, None).await.unwrap().await.unwrap().unwrap();
|
|
||||||
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(sent_tx.from, signer.address());
|
|
||||||
|
|
||||||
// sending a TransactionRequest with the signer as the from address should
|
|
||||||
// result in a transaction from the signer address
|
|
||||||
let request_from_signer = TransactionRequest::new().from(signer.address());
|
|
||||||
let receipt =
|
|
||||||
provider.send_transaction(request_from_signer, None).await.unwrap().await.unwrap().unwrap();
|
|
||||||
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(sent_tx.from, signer.address());
|
|
||||||
|
|
||||||
// sending a TransactionRequest with a from address that is not the signer
|
|
||||||
// should result in a transaction from the specified address
|
|
||||||
let request_from_other = TransactionRequest::new().from(other.address());
|
|
||||||
let receipt =
|
|
||||||
provider.send_transaction(request_from_other, None).await.unwrap().await.unwrap().unwrap();
|
|
||||||
let sent_tx = provider.get_transaction(receipt.transaction_hash).await.unwrap().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(sent_tx.from, other.address());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[cfg(feature = "celo")]
|
|
||||||
async fn deploy_and_call_contract() {
|
|
||||||
// compiles the given contract and returns the ABI and Bytecode
|
|
||||||
fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
|
|
||||||
let path = format!("./tests/solidity-contracts/{path}");
|
|
||||||
let compiled = Solc::default().compile_source(&path).unwrap();
|
|
||||||
let contract = compiled.get(&path, name).expect("could not find contract");
|
|
||||||
let (abi, bin, _) = contract.into_parts_or_default();
|
|
||||||
(abi, bin)
|
|
||||||
}
|
|
||||||
|
|
||||||
let (abi, bytecode) = compile_contract("SimpleStorage.sol", "SimpleStorage");
|
|
||||||
|
|
||||||
// Celo testnet
|
|
||||||
let provider = Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org")
|
|
||||||
.unwrap()
|
|
||||||
.interval(Duration::from_millis(6000));
|
|
||||||
let chain_id = provider.get_chainid().await.unwrap().as_u64();
|
|
||||||
|
|
||||||
// Funded with https://celo.org/developers/faucet
|
|
||||||
let wallet = "58ea5643a78c36926ad5128a6b0d8dfcc7fc705788a993b1c724be3469bc9697"
|
|
||||||
.parse::<LocalWallet>()
|
|
||||||
.unwrap()
|
|
||||||
.with_chain_id(chain_id);
|
|
||||||
let client = SignerMiddleware::new_with_provider_chain(provider, wallet).await.unwrap();
|
|
||||||
let client = Arc::new(client);
|
|
||||||
|
|
||||||
let factory = ContractFactory::new(abi, bytecode, client);
|
|
||||||
let deployer = factory.deploy(()).unwrap().legacy();
|
|
||||||
let contract = deployer.block(BlockNumber::Pending).send().await.unwrap();
|
|
||||||
|
|
||||||
let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap();
|
|
||||||
assert_eq!(value, 0.into());
|
|
||||||
|
|
||||||
// make a state mutating transaction
|
|
||||||
// gas estimation costs are sometimes under-reported on celo,
|
|
||||||
// so we manually set it to avoid failures
|
|
||||||
let call = contract.method::<_, H256>("setValue", U256::from(1)).unwrap().gas(100000);
|
|
||||||
let pending_tx = call.send().await.unwrap();
|
|
||||||
let _receipt = pending_tx.await.unwrap();
|
|
||||||
|
|
||||||
let value: U256 = contract.method("value", ()).unwrap().call().await.unwrap();
|
|
||||||
assert_eq!(value, 1.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct TestWallets {
|
|
||||||
mnemonic: MnemonicBuilder<English>,
|
|
||||||
next: AtomicU8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestWallets {
|
|
||||||
/// Helper for funding the wallets with an instantiated provider
|
|
||||||
#[allow(unused)]
|
|
||||||
pub async fn fund<T: JsonRpcClient, U: Into<u32>>(&self, provider: &Provider<T>, n: U) {
|
|
||||||
let addrs = (0..n.into()).map(|i| self.get(i).address()).collect::<Vec<_>>();
|
|
||||||
// hardcoded funder address private key, GOERLI
|
|
||||||
let signer = "39aa18eeb5d12c071e5f19d8e9375a872e90cb1f2fa640384ffd8800a2f3e8f1"
|
|
||||||
.parse::<LocalWallet>()
|
|
||||||
.unwrap()
|
|
||||||
.with_chain_id(provider.get_chainid().await.unwrap().as_u64());
|
|
||||||
let provider = SignerMiddleware::new(provider, signer);
|
|
||||||
let addr = provider.address();
|
|
||||||
|
|
||||||
let mut nonce = provider.get_transaction_count(addr, None).await.unwrap();
|
|
||||||
let mut pending_txs = Vec::new();
|
|
||||||
for addr in addrs {
|
|
||||||
println!("Funding wallet {addr:?}");
|
|
||||||
let tx = TransactionRequest::new()
|
|
||||||
.nonce(nonce)
|
|
||||||
.to(addr)
|
|
||||||
// 0.1 eth per wallet
|
|
||||||
.value(parse_ether("1").unwrap());
|
|
||||||
pending_txs.push(
|
|
||||||
provider.send_transaction(tx, Some(BlockNumber::Pending.into())).await.unwrap(),
|
|
||||||
);
|
|
||||||
nonce += 1.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
futures_util::future::join_all(pending_txs).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(&self) -> LocalWallet {
|
|
||||||
let idx = self.next.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
||||||
|
|
||||||
// println!("Got wallet {:?}", wallet.address());
|
|
||||||
self.get(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<T: Into<u32>>(&self, idx: T) -> LocalWallet {
|
|
||||||
self.mnemonic
|
|
||||||
.clone()
|
|
||||||
.index(idx)
|
|
||||||
.expect("index not found")
|
|
||||||
.build()
|
|
||||||
.expect("cannot build wallet")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
|
|
||||||
|
mod provider;
|
||||||
|
|
||||||
|
mod txpool;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "celo"))]
|
||||||
|
mod ws_errors;
|
|
@ -1,4 +1,3 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
use ethers_providers::{Http, Middleware, Provider};
|
use ethers_providers::{Http, Middleware, Provider};
|
||||||
use std::{convert::TryFrom, time::Duration};
|
use std::{convert::TryFrom, time::Duration};
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#![cfg(not(target_arch = "wasm32"))]
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
types::{TransactionRequest, U256},
|
types::{TransactionRequest, U256},
|
||||||
utils::Anvil,
|
utils::Anvil,
|
|
@ -1,5 +1,3 @@
|
||||||
#![cfg(not(feature = "celo"))]
|
|
||||||
|
|
||||||
use ethers_providers::{Middleware, Provider, StreamExt, Ws};
|
use ethers_providers::{Middleware, Provider, StreamExt, Ws};
|
||||||
use futures_util::SinkExt;
|
use futures_util::SinkExt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
Loading…
Reference in New Issue