Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

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

Merged
merged 5 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ethers-contract/src/call.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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>,
}
Expand Down Expand Up @@ -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
}
Expand Down
12 changes: 10 additions & 2 deletions ethers-contract/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion ethers-contract/src/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;

Expand Down
134 changes: 132 additions & 2 deletions ethers-contract/tests/contract.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use ethers::{contract::ContractFactory, types::H256};
use ethers::{
contract::ContractFactory,
types::{BlockId, H256},
};

mod common;
pub use common::*;
Expand Down Expand Up @@ -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()
Expand All @@ -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]
Expand Down
88 changes: 76 additions & 12 deletions ethers-core/src/types/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -91,12 +140,21 @@ impl Serialize for Filter {
S: Serializer,
{
let mut s = serializer.serialize_struct("Filter", 5)?;
if let Some(ref from_block) = self.from_block {
s.serialize_field("fromBlock", 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 {
s.serialize_field("toBlock", 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 {
Expand Down Expand Up @@ -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
}

Expand Down
6 changes: 3 additions & 3 deletions ethers-middleware/src/gas_escalator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
}

Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion ethers-middleware/src/gas_oracle/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?);
Expand Down
4 changes: 2 additions & 2 deletions ethers-middleware/src/nonce_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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?);
Expand Down
Loading