feat: add support for EIP-234 and EIP-1898 (#231)

* Add support for EIP-234

* Add support for EIP-1898

* Remove redundant field names

* Remove useless conversion

* Change `unwrap_or` to `unwrap_or_else`
This commit is contained in:
Gabriel Coutinho de Paula 2021-03-16 16:46:07 -03:00 committed by GitHub
parent 57010c1c60
commit 530bfe2b71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 252 additions and 51 deletions

View File

@ -1,7 +1,7 @@
use super::base::{decode_function_data, AbiError};
use ethers_core::{
abi::{Detokenize, Function, InvalidOutputType},
types::{Address, BlockNumber, Bytes, TransactionRequest, U256},
types::{Address, BlockId, Bytes, TransactionRequest, U256},
};
use ethers_providers::{Middleware, PendingTransaction, ProviderError};
@ -52,7 +52,7 @@ pub struct ContractCall<M, D> {
/// The ABI of the function being called
pub function: Function,
/// Optional block number to be used when calculating the transaction's gas and nonce
pub block: Option<BlockNumber>,
pub block: Option<BlockId>,
pub(crate) client: Arc<M>,
pub(crate) datatype: PhantomData<D>,
}
@ -83,7 +83,7 @@ impl<M, D: Detokenize> ContractCall<M, D> {
}
/// Sets the `block` field for sending the tx to the chain
pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
pub fn block<T: Into<BlockId>>(mut self, block: T) -> Self {
self.block = Some(block.into());
self
}

View File

@ -40,14 +40,22 @@ impl<M, D: Detokenize> Event<'_, '_, M, D> {
/// Sets the filter's `from` block
#[allow(clippy::wrong_self_convention)]
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.filter.from_block = Some(block.into());
self.filter = self.filter.from_block(block);
self
}
/// Sets the filter's `to` block
#[allow(clippy::wrong_self_convention)]
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.filter.to_block = Some(block.into());
self.filter = self.filter.to_block(block);
self
}
/// Sets the filter's `blockHash`. Setting this will override previously
/// set `from_block` and `to_block` fields.
#[allow(clippy::wrong_self_convention)]
pub fn at_block_hash<T: Into<H256>>(mut self, hash: T) -> Self {
self.filter = self.filter.at_block_hash(hash);
self
}

View File

@ -37,7 +37,7 @@ impl<M: Middleware> Deployer<M> {
pub async fn send(self) -> Result<Contract<M>, ContractError<M>> {
let pending_tx = self
.client
.send_transaction(self.tx, Some(self.block))
.send_transaction(self.tx, Some(self.block.into()))
.await
.map_err(ContractError::MiddlewareError)?;

View File

@ -1,4 +1,7 @@
use ethers::{contract::ContractFactory, types::H256};
use ethers::{
contract::ContractFactory,
types::{BlockId, H256},
};
mod common;
pub use common::*;
@ -96,7 +99,7 @@ mod eth_tests {
let client = connect(&ganache, 0);
let contract = deploy(client.clone(), abi, bytecode).await;
// make a call with `client2`
// make a call with `client`
let _tx_hash = *contract
.method::<_, H256>("setValue", "hi".to_owned())
.unwrap()
@ -116,6 +119,133 @@ mod eth_tests {
assert_eq!(logs[0].new_value, "initial value");
assert_eq!(logs[1].new_value, "hi");
assert_eq!(logs.len(), 2);
// and we can fetch the events at a block hash
let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap();
let logs: Vec<ValueChanged> = contract
.event("ValueChanged")
.unwrap()
.at_block_hash(hash)
.topic1(client.address()) // Corresponds to the first indexed parameter
.query()
.await
.unwrap();
assert_eq!(logs[0].new_value, "initial value");
assert_eq!(logs.len(), 1);
}
#[tokio::test]
async fn call_past_state() {
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
let ganache = Ganache::new().spawn();
let client = connect(&ganache, 0);
let contract = deploy(client.clone(), abi, bytecode).await;
let deployed_block = client.get_block_number().await.unwrap();
// assert initial state
let value = contract
.method::<_, String>("getValue", ())
.unwrap()
.call()
.await
.unwrap();
assert_eq!(value, "initial value");
// make a call with `client`
let _tx_hash = *contract
.method::<_, H256>("setValue", "hi".to_owned())
.unwrap()
.send()
.await
.unwrap();
// assert new value
let value = contract
.method::<_, String>("getValue", ())
.unwrap()
.call()
.await
.unwrap();
assert_eq!(value, "hi");
// assert previous value
let value = contract
.method::<_, String>("getValue", ())
.unwrap()
.block(BlockId::Number(deployed_block.into()))
.call()
.await
.unwrap();
assert_eq!(value, "initial value");
// Here would be the place to test EIP-1898, specifying the `BlockId` of `call` as the
// first block hash. However, Ganache does not implement this :/
// let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap();
// let value = contract
// .method::<_, String>("getValue", ())
// .unwrap()
// .block(BlockId::Hash(hash))
// .call()
// .await
// .unwrap();
// assert_eq!(value, "initial value");
}
#[tokio::test]
#[ignore]
async fn call_past_hash_test() {
// geth --dev --http --http.api eth,web3
let (abi, bytecode) = compile_contract("SimpleStorage", "SimpleStorage.sol");
let provider = Provider::<Http>::try_from("http://localhost:8545").unwrap();
let deployer = provider.get_accounts().await.unwrap()[0];
let client = Arc::new(provider.with_sender(deployer));
let contract = deploy(client.clone(), abi, bytecode).await;
let deployed_block = client.get_block_number().await.unwrap();
// assert initial state
let value = contract
.method::<_, String>("getValue", ())
.unwrap()
.call()
.await
.unwrap();
assert_eq!(value, "initial value");
// make a call with `client`
let _tx_hash = *contract
.method::<_, H256>("setValue", "hi".to_owned())
.unwrap()
.send()
.await
.unwrap();
// assert new value
let value = contract
.method::<_, String>("getValue", ())
.unwrap()
.call()
.await
.unwrap();
assert_eq!(value, "hi");
// assert previous value using block hash
let hash = client
.get_block(deployed_block)
.await
.unwrap()
.unwrap()
.hash
.unwrap();
let value = contract
.method::<_, String>("getValue", ())
.unwrap()
.block(BlockId::Hash(hash))
.call()
.await
.unwrap();
assert_eq!(value, "initial value");
}
#[tokio::test]

View File

@ -62,14 +62,63 @@ pub struct Log {
pub removed: Option<bool>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum FilterBlockOption {
Range {
from_block: Option<BlockNumber>,
to_block: Option<BlockNumber>,
},
AtBlockHash(H256),
}
impl Default for FilterBlockOption {
fn default() -> Self {
FilterBlockOption::Range {
from_block: None,
to_block: None,
}
}
}
impl FilterBlockOption {
pub fn set_from_block(&self, block: BlockNumber) -> Self {
let to_block = if let FilterBlockOption::Range { to_block, .. } = self {
*to_block
} else {
None
};
FilterBlockOption::Range {
from_block: Some(block),
to_block,
}
}
pub fn set_to_block(&self, block: BlockNumber) -> Self {
let from_block = if let FilterBlockOption::Range { from_block, .. } = self {
*from_block
} else {
None
};
FilterBlockOption::Range {
from_block,
to_block: Some(block),
}
}
pub fn set_hash(&self, hash: H256) -> Self {
FilterBlockOption::AtBlockHash(hash)
}
}
/// Filter for
#[derive(Default, Debug, PartialEq, Clone)]
pub struct Filter {
/// From Block
pub from_block: Option<BlockNumber>,
/// To Block
pub to_block: Option<BlockNumber>,
/// Filter block options, specifying on which blocks the filter should
/// match.
// https://eips.ethereum.org/EIPS/eip-234
pub block_option: FilterBlockOption,
/// Address
// TODO: The spec says that this can also be an array, do we really want to
@ -91,13 +140,22 @@ impl Serialize for Filter {
S: Serializer,
{
let mut s = serializer.serialize_struct("Filter", 5)?;
if let Some(ref from_block) = self.from_block {
match self.block_option {
FilterBlockOption::Range {
from_block,
to_block,
} => {
if let Some(ref from_block) = from_block {
s.serialize_field("fromBlock", from_block)?;
}
if let Some(ref to_block) = self.to_block {
if let Some(ref to_block) = to_block {
s.serialize_field("toBlock", to_block)?;
}
}
FilterBlockOption::AtBlockHash(ref h) => s.serialize_field("blockHash", h)?,
}
if let Some(ref address) = self.address {
s.serialize_field("address", address)?;
@ -131,13 +189,19 @@ impl Filter {
#[allow(clippy::wrong_self_convention)]
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.from_block = Some(block.into());
self.block_option = self.block_option.set_from_block(block.into());
self
}
#[allow(clippy::wrong_self_convention)]
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
self.to_block = Some(block.into());
self.block_option = self.block_option.set_to_block(block.into());
self
}
#[allow(clippy::wrong_self_convention)]
pub fn at_block_hash<T: Into<H256>>(mut self, hash: T) -> Self {
self.block_option = self.block_option.set_hash(hash.into());
self
}

View File

@ -5,7 +5,7 @@ mod linear;
pub use linear::LinearGasPrice;
use async_trait::async_trait;
use ethers_core::types::{BlockNumber, TransactionRequest, TxHash, U256};
use ethers_core::types::{BlockId, TransactionRequest, TxHash, U256};
use ethers_providers::{interval, FromErr, Middleware, PendingTransaction, StreamExt};
use futures_util::lock::Mutex;
use std::sync::Arc;
@ -64,7 +64,7 @@ pub struct GasEscalatorMiddleware<M, E> {
pub(crate) escalator: E,
/// The transactions which are currently being monitored for escalation
#[allow(clippy::type_complexity)]
pub txs: Arc<Mutex<Vec<(TxHash, TransactionRequest, Instant, Option<BlockNumber>)>>>,
pub txs: Arc<Mutex<Vec<(TxHash, TransactionRequest, Instant, Option<BlockId>)>>>,
frequency: Frequency,
}
@ -85,7 +85,7 @@ where
async fn send_transaction(
&self,
tx: TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
let pending_tx = self
.inner()

View File

@ -59,7 +59,7 @@ where
async fn send_transaction(
&self,
mut tx: TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
if tx.gas_price.is_none() {
tx.gas_price = Some(self.get_gas_price().await?);

View File

@ -37,7 +37,7 @@ where
async fn get_transaction_count_with_manager(
&self,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<U256, NonceManagerError<M>> {
// initialize the nonce the first time the manager is called
if !self.initialized.load(Ordering::SeqCst) {
@ -87,7 +87,7 @@ where
async fn send_transaction(
&self,
mut tx: TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
if tx.nonce.is_none() {
tx.nonce = Some(self.get_transaction_count_with_manager(block).await?);

View File

@ -1,7 +1,6 @@
use ethers_core::{
types::{
Address, BlockNumber, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest,
U256,
Address, BlockId, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest, U256,
},
utils::keccak256,
};
@ -176,7 +175,7 @@ where
async fn fill_transaction(
&self,
tx: &mut TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<(), SignerMiddlewareError<M, S>> {
// set the `from` field
if tx.from.is_none() {
@ -246,7 +245,7 @@ where
async fn send_transaction(
&self,
mut tx: TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
if let Some(NameOrAddress::Name(ens_name)) = tx.to {
let addr = self

View File

@ -56,7 +56,7 @@ where
async fn send_transaction(
&self,
mut tx: TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
// resolve the to field if that's an ENS name.
if let Some(NameOrAddress::Name(ens_name)) = tx.to {

View File

@ -25,7 +25,7 @@ async fn nonce_manager() {
let provider = NonceManagerMiddleware::new(provider, address);
let nonce = provider
.get_transaction_count(address, Some(BlockNumber::Pending))
.get_transaction_count(address, Some(BlockNumber::Pending.into()))
.await
.unwrap()
.as_u64();

View File

@ -188,7 +188,7 @@ pub trait Middleware: Sync + Send + Debug {
async fn send_transaction(
&self,
tx: TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
self.inner()
.send_transaction(tx, block)
@ -233,7 +233,7 @@ pub trait Middleware: Sync + Send + Debug {
async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
self.inner()
.get_transaction_count(from, block)
@ -248,7 +248,7 @@ pub trait Middleware: Sync + Send + Debug {
async fn call(
&self,
tx: &TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<Bytes, Self::Error> {
self.inner().call(tx, block).await.map_err(FromErr::from)
}
@ -260,7 +260,7 @@ pub trait Middleware: Sync + Send + Debug {
async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<U256, Self::Error> {
self.inner()
.get_balance(from, block)
@ -375,7 +375,7 @@ pub trait Middleware: Sync + Send + Debug {
async fn get_code<T: Into<NameOrAddress> + Send + Sync>(
&self,
at: T,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<Bytes, Self::Error> {
self.inner()
.get_code(at, block)
@ -387,7 +387,7 @@ pub trait Middleware: Sync + Send + Debug {
&self,
from: T,
location: H256,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<H256, Self::Error> {
self.inner()
.get_storage_at(from, location, block)

View File

@ -221,7 +221,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
async fn get_transaction_count<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<U256, ProviderError> {
let from = match from.into() {
NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?,
@ -229,7 +229,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
};
let from = utils::serialize(&from);
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into()));
self.request("eth_getTransactionCount", [from, block]).await
}
@ -237,7 +237,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
async fn get_balance<T: Into<NameOrAddress> + Send + Sync>(
&self,
from: T,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<U256, ProviderError> {
let from = match from.into() {
NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?,
@ -245,7 +245,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
};
let from = utils::serialize(&from);
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into()));
self.request("eth_getBalance", [from, block]).await
}
@ -264,10 +264,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
async fn call(
&self,
tx: &TransactionRequest,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<Bytes, ProviderError> {
let tx = utils::serialize(tx);
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into()));
self.request("eth_call", [tx, block]).await
}
@ -283,7 +283,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
async fn send_transaction(
&self,
mut tx: TransactionRequest,
_: Option<BlockNumber>,
_: Option<BlockId>,
) -> Result<PendingTransaction<'_, P>, ProviderError> {
if tx.from.is_none() {
tx.from = self.3;
@ -426,7 +426,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
&self,
from: T,
location: H256,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<H256, ProviderError> {
let from = match from.into() {
NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?,
@ -435,7 +435,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
let from = utils::serialize(&from);
let location = utils::serialize(&location);
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into()));
// get the hex encoded value.
let value: String = self
@ -450,7 +450,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
async fn get_code<T: Into<NameOrAddress> + Send + Sync>(
&self,
at: T,
block: Option<BlockNumber>,
block: Option<BlockId>,
) -> Result<Bytes, ProviderError> {
let at = match at.into() {
NameOrAddress::Name(ens_name) => self.resolve_name(&ens_name).await?,
@ -458,7 +458,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
};
let at = utils::serialize(&at);
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
let block = utils::serialize(&block.unwrap_or_else(|| BlockNumber::Latest.into()));
self.request("eth_getCode", [at, block]).await
}

View File

@ -23,11 +23,11 @@ async fn main() -> Result<()> {
println!("{}", serde_json::to_string(&tx)?);
let nonce1 = provider
.get_transaction_count(from, Some(BlockNumber::Latest))
.get_transaction_count(from, Some(BlockNumber::Latest.into()))
.await?;
let nonce2 = provider
.get_transaction_count(from, Some(BlockNumber::Number(0.into())))
.get_transaction_count(from, Some(BlockNumber::Number(0.into()).into()))
.await?;
assert!(nonce2 < nonce1);