Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement auto-mining #4622

Merged
merged 34 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d62e97a
fix: eth_sendTransaction RPC type
Wodann Nov 24, 2023
9032106
refactor: rename EIP to Eip
Wodann Nov 24, 2023
469fdee
feat: add auto-mining for eth_sendTransaction
Wodann Nov 24, 2023
48f7131
Merge remote-tracking branch 'origin/edr/main' into edr/feat/send-tra…
Wodann Nov 27, 2023
96b165e
feat: expose helper functions from edr_evm
Wodann Nov 28, 2023
253e07d
feat: resolve default transaction fees
Wodann Nov 28, 2023
266aafc
Merge remote-tracking branch 'origin/edr/main' into edr/feat/send-tra…
Wodann Nov 28, 2023
132072f
fix: eth_sendTransaction request RPC type
Wodann Nov 28, 2023
29a2e90
feat: resolve default chain ID for eth_sendTransaction
Wodann Nov 28, 2023
a411ecc
feat: implement eth_call RPC method
Wodann Nov 29, 2023
47f1229
feat: revert to snapshot if auto-mining fails
Wodann Nov 29, 2023
58faf5a
feat: add names of unimplemented RPC methods
Wodann Nov 29, 2023
d65bd9d
Merge remote-tracking branch 'origin/edr/main' into edr/feat/send-tra…
Wodann Nov 29, 2023
52a9aee
feat: return RPC error from N-API provider
Wodann Nov 29, 2023
25884a5
fix: match EDR errors with Hardhat
Wodann Dec 1, 2023
75f7609
WIP: test: add test file
Wodann Dec 1, 2023
6aa2fb7
Merge branch 'edr/main' into edr/feat/send-transaction
agostbiro Dec 1, 2023
30d3ef6
Fix gas price too low errors
agostbiro Dec 1, 2023
5c26805
Fix failing tests with error expectations
agostbiro Dec 1, 2023
91be641
Fix nonce too low errors
agostbiro Dec 4, 2023
6b30550
Fix nonce too high errors
agostbiro Dec 4, 2023
5517d96
Add `eth_pendingTransactions` to provider
agostbiro Dec 4, 2023
8afaade
Add `hardhat_setMinGasPrice` to provider
agostbiro Dec 4, 2023
59ddb73
Fix error message when sender doesn't have funds for gas
agostbiro Dec 4, 2023
841507d
Derive debug implementation for provider config
agostbiro Dec 4, 2023
9def29f
Pass fork config to EDR provider
agostbiro Dec 4, 2023
2e6b809
Fix JS lints
agostbiro Dec 5, 2023
6963638
Review: add back todo
agostbiro Dec 5, 2023
4f2fa65
Fix lint
agostbiro Dec 5, 2023
fd8ce51
fix: overwrite genesis account nonces and code in fork mode (#4649)
agostbiro Dec 5, 2023
c50a9d7
fix: erroneous AutoMineMaxFeeTooLow
Wodann Dec 6, 2023
e3aac13
fix: 'should use the proper chain ID' test
Wodann Dec 6, 2023
d3e33a2
fix: do not revert to snapshot upon revert or halt
Wodann Dec 6, 2023
15e9ff0
refactor: remove original sendTransaction.ts
Wodann Dec 6, 2023
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
388 changes: 376 additions & 12 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 10 additions & 12 deletions crates/edr_eth/src/remote.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
use std::{
fmt,
fmt::{Display, Formatter},
};

mod cacheable_method_invocation;
/// an Ethereum JSON-RPC client
pub mod client;

/// ethereum objects as specifically used in the JSON-RPC interface
pub mod eth;

/// data types for use with filter-based RPC methods
pub mod filter;

/// data types specific to JSON-RPC but not specific to Ethereum.
pub mod jsonrpc;

/// RPC methods
pub mod methods;
mod r#override;

mod cacheable_method_invocation;

pub use client::{RpcClient, RpcClientError};
use std::{
fmt,
fmt::{Display, Formatter},
};

pub use self::{
client::{RpcClient, RpcClientError},
r#override::*,
};
use crate::B256;

/// for representing block specifications per EIP-1898
Expand Down
2 changes: 1 addition & 1 deletion crates/edr_eth/src/remote/cacheable_method_invocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ impl<'a> TryFrom<&'a MethodInvocation> for CacheableMethodInvocation<'a> {
// Explicit to make sure if a new method is added, it is not forgotten here.
MethodInvocation::Accounts(_)
| MethodInvocation::BlockNumber(_)
| MethodInvocation::Call(_, _)
| MethodInvocation::Call(_, _, _)
| MethodInvocation::Coinbase(_)
| MethodInvocation::EstimateGas(_, _)
| MethodInvocation::FeeHistory(_, _, _)
Expand Down
87 changes: 56 additions & 31 deletions crates/edr_eth/src/remote/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,33 +669,53 @@ impl RpcClient {
address: &Address,
block: Option<BlockSpec>,
) -> Result<AccountInfo, RpcClientError> {
let inputs = &[
MethodInvocation::GetBalance(*address, block.clone()),
MethodInvocation::GetTransactionCount(*address, block.clone()),
MethodInvocation::GetCode(*address, block),
];

let responses = self.batch_call(inputs).await?;
let (balance, nonce, code) = responses
.into_iter()
.collect_tuple()
.expect("batch call checks responses");

let balance = balance.parse::<U256>().await?;
let nonce: u64 = nonce.parse::<U256>().await?.to();
let code: Bytes = code.parse::<ZeroXPrefixedBytes>().await?.into();
let code = if code.is_empty() {
None
} else {
Some(Bytecode::new_raw(code))
};
Ok(self
.get_account_infos(&[*address], block)
.await?
.pop()
.expect("batch call returns as many results as inputs if there was no error"))
}

Ok(AccountInfo {
balance,
code_hash: code.as_ref().map_or(KECCAK_EMPTY, Bytecode::hash_slow),
code,
nonce,
})
/// Fetch account infos for multiple addresses in a batch call.
pub async fn get_account_infos(
&self,
addresses: &[Address],
block: Option<BlockSpec>,
) -> Result<Vec<AccountInfo>, RpcClientError> {
let inputs: Vec<MethodInvocation> = addresses
.iter()
.flat_map(|address| {
[
MethodInvocation::GetBalance(*address, block.clone()),
MethodInvocation::GetTransactionCount(*address, block.clone()),
MethodInvocation::GetCode(*address, block.clone()),
]
})
.collect();

let responses = self.batch_call(inputs.as_slice()).await?;
let mut results = Vec::with_capacity(inputs.len() / 3);
for (balance, nonce, code) in responses.into_iter().tuples() {
let balance = balance.parse::<U256>().await?;
let nonce: u64 = nonce.parse::<U256>().await?.to();
let code: Bytes = code.parse::<ZeroXPrefixedBytes>().await?.into();
let code = if code.is_empty() {
None
} else {
Some(Bytecode::new_raw(code))
};

let account_info = AccountInfo {
balance,
code_hash: code.as_ref().map_or(KECCAK_EMPTY, Bytecode::hash_slow),
code,
nonce,
};

results.push(account_info);
}

Ok(results)
}

/// Calls `eth_getBlockByHash` and returns the transaction's hash.
Expand Down Expand Up @@ -1295,19 +1315,24 @@ mod tests {
}

#[tokio::test]
async fn get_account_info_latest_contract() {
async fn get_account_infos() {
let alchemy_url = get_alchemy_url();

let dai_address = Address::from_str("0x6b175474e89094c44da98b954eedeac495271d0f")
.expect("failed to parse address");
let hardhat_default_address =
Address::from_str("0xbe862ad9abfe6f22bcb087716c7d89a26051f74c")
.expect("failed to parse address");

let account_info = TestRpcClient::new(&alchemy_url)
.get_account_info(&dai_address, Some(BlockSpec::latest()))
let account_infos = TestRpcClient::new(&alchemy_url)
.get_account_infos(
&[dai_address, hardhat_default_address],
Some(BlockSpec::latest()),
)
.await
.expect("should have succeeded");

assert_ne!(account_info.code_hash, KECCAK_EMPTY);
assert!(account_info.code.is_some());
assert_eq!(account_infos.len(), 2);
}

#[tokio::test]
Expand Down
13 changes: 13 additions & 0 deletions crates/edr_eth/src/remote/jsonrpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ impl<T> ResponseData<T> {
}
}

impl<SuccessT: Serialize, ErrorT: Into<Error>> From<Result<SuccessT, ErrorT>>
for ResponseData<SuccessT>
{
fn from(result: Result<SuccessT, ErrorT>) -> Self {
match result {
Ok(result) => ResponseData::Success { result },
Err(error) => ResponseData::Error {
error: error.into(),
},
}
}
}

/// Represents JSON-RPC request/response id.
///
/// An identifier established by the Client that MUST contain a String, Number,
Expand Down
35 changes: 23 additions & 12 deletions crates/edr_eth/src/remote/methods.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use revm_primitives::ruint::aliases::U64;

use super::StateOverrideOptions;
use crate::{
access_list::AccessListItem,
remote::{
eth::eip712,
filter::{FilterOptions, SubscriptionType},
Expand All @@ -14,23 +16,31 @@ use crate::{
/// for specifying input to methods requiring a transaction object, like
/// `eth_call`, `eth_sendTransaction` and `eth_estimateGas`
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct TransactionInput {
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct CallRequest {
/// the address from which the transaction should be sent
pub from: Option<Address>,
/// the address to which the transaction should be sent
pub to: Option<Address>,
#[cfg_attr(feature = "serde", serde(default, with = "crate::serde::optional_u64"))]
/// gas
pub gas: Option<U256>,
pub gas: Option<u64>,
/// gas price
#[serde(rename = "gasPrice")]
pub gas_price: Option<U256>,
/// max base fee per gas sender is willing to pay
pub max_fee_per_gas: Option<U256>,
/// miner tip
pub max_priority_fee_per_gas: Option<U256>,
/// transaction value
pub value: Option<U256>,
/// transaction data
pub data: Option<ZeroXPrefixedBytes>,
/// warm storage access pre-payment
pub access_list: Option<Vec<AccessListItem>>,
}

mod optional_block_spec_resolved {
mod optional_block_spec {
use super::BlockSpec;

pub fn latest() -> Option<BlockSpec> {
Expand All @@ -55,12 +65,13 @@ pub enum MethodInvocation {
/// eth_call
#[serde(rename = "eth_call")]
Call(
TransactionInput,
CallRequest,
#[serde(
skip_serializing_if = "Option::is_none",
default = "optional_block_spec_resolved::latest"
default = "optional_block_spec::latest"
)]
Option<BlockSpec>,
#[serde(default, skip_serializing_if = "Option::is_none")] Option<StateOverrideOptions>,
),
/// eth_chainId
#[serde(rename = "eth_chainId", with = "crate::serde::empty_params")]
Expand All @@ -71,10 +82,10 @@ pub enum MethodInvocation {
/// eth_estimateGas
#[serde(rename = "eth_estimateGas")]
EstimateGas(
TransactionInput,
CallRequest,
#[serde(
skip_serializing_if = "Option::is_none",
default = "optional_block_spec_resolved::pending"
default = "optional_block_spec::pending"
)]
Option<BlockSpec>,
),
Expand All @@ -97,7 +108,7 @@ pub enum MethodInvocation {
Address,
#[serde(
skip_serializing_if = "Option::is_none",
default = "optional_block_spec_resolved::latest"
default = "optional_block_spec::latest"
)]
Option<BlockSpec>,
),
Expand Down Expand Up @@ -134,7 +145,7 @@ pub enum MethodInvocation {
Address,
#[serde(
skip_serializing_if = "Option::is_none",
default = "optional_block_spec_resolved::latest"
default = "optional_block_spec::latest"
)]
Option<BlockSpec>,
),
Expand All @@ -155,7 +166,7 @@ pub enum MethodInvocation {
U256,
#[serde(
skip_serializing_if = "Option::is_none",
default = "optional_block_spec_resolved::latest"
default = "optional_block_spec::latest"
)]
Option<BlockSpec>,
),
Expand All @@ -176,7 +187,7 @@ pub enum MethodInvocation {
Address,
#[serde(
skip_serializing_if = "Option::is_none",
default = "optional_block_spec_resolved::latest"
default = "optional_block_spec::latest"
)]
Option<BlockSpec>,
),
Expand Down
29 changes: 29 additions & 0 deletions crates/edr_eth/src/remote/override.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use revm_primitives::{Address, HashMap, U256};

use crate::{serde::ZeroXPrefixedBytes, state::Storage};

/// Options for overriding account information.
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountOverrideOptions {
/// Account balance override.
pub balance: Option<U256>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "crate::serde::optional_u64"
)]
/// Account nonce override.
pub nonce: Option<u64>,
/// Account code override.
pub code: Option<ZeroXPrefixedBytes>,
/// Account storage override. Mutually exclusive with `storage_diff`.
#[serde(rename = "state")]
pub storage: Option<Storage>,
/// Account storage diff override. Mutually exclusive with `storage`.
#[serde(rename = "stateDiff")]
pub storage_diff: Option<Storage>,
}

/// Type representing a full set of overrides for account information.
pub type StateOverrideOptions = HashMap<Address, AccountOverrideOptions>;
2 changes: 1 addition & 1 deletion crates/edr_eth/src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use serde::{

/// Type for specifying a byte string that will have a 0x prefix when serialized
/// and deserialized
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ZeroXPrefixedBytes {
inner: Bytes,
}
Expand Down
Loading
Loading