Skip to content

Commit

Permalink
anvil/eth: Use the raw v signature value instead of bool (#7918)
Browse files Browse the repository at this point in the history
* anvil/eth: Use the raw `v` signature value instead of `bool`

Instead of converting the boolean corresponding to the y parity byte to
a u64/U256, use the raw `v` value for the `v` field when available.

* anvil/eth: Use proper Parity signature in impersonate

The correct `v` value must be used when using a dummy signature,
depending on the transaction type (the Legacy transactions being the
ones needing a special case).

* anvil/eth: Use proper signature for creating txs

This fixes wrong hash being computed for transactions.

---------

Co-authored-by: zerosnacks <[email protected]>
  • Loading branch information
ngotchac and zerosnacks committed Jul 16, 2024
1 parent dafccd3 commit cb9dfae
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 47 deletions.
42 changes: 19 additions & 23 deletions crates/anvil/core/src/eth/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use alloy_consensus::{
TxEnvelope, TxLegacy, TxReceipt, TxType,
};
use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718};
use alloy_primitives::{Address, Bloom, Bytes, Log, Signature, TxHash, TxKind, B256, U256, U64};
use alloy_primitives::{
Address, Bloom, Bytes, Log, Parity, Signature, TxHash, TxKind, B256, U256, U64,
};
use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
use alloy_rpc_types::{
request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, AnyTransactionReceipt,
Expand All @@ -25,13 +27,6 @@ use std::ops::{Deref, Mul};

pub mod optimism;

/// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC
#[cfg(feature = "impersonated-tx")]
pub fn impersonated_signature() -> Signature {
Signature::from_scalars_and_parity(B256::with_last_byte(1), B256::with_last_byte(1), false)
.unwrap()
}

/// Converts a [TransactionRequest] into a [TypedTransactionRequest].
/// Should be removed once the call builder abstraction for providers is in place.
pub fn transaction_request_to_typed(
Expand Down Expand Up @@ -203,16 +198,23 @@ impl MaybeImpersonatedTransaction {
self.transaction.recover()
}

/// Returns whether the transaction is impersonated
///
/// Note: this is feature gated so it does not conflict with the `Deref`ed
/// [TypedTransaction::hash] function by default.
#[cfg(feature = "impersonated-tx")]
pub fn is_impersonated(&self) -> bool {
self.impersonated_sender.is_some()
}

/// Returns the hash of the transaction
///
/// Note: this is feature gated so it does not conflict with the `Deref`ed
/// [TypedTransaction::hash] function by default.
#[cfg(feature = "impersonated-tx")]
pub fn hash(&self) -> B256 {
if self.transaction.is_impersonated() {
if let Some(sender) = self.impersonated_sender {
return self.transaction.impersonated_hash(sender);
}
if let Some(sender) = self.impersonated_sender {
return self.transaction.impersonated_hash(sender)
}
self.transaction.hash()
}
Expand Down Expand Up @@ -288,7 +290,7 @@ pub fn to_alloy_transaction_with_hash_and_sender(
signature: Some(RpcSignature {
r: t.signature().r(),
s: t.signature().s(),
v: U256::from(t.signature().v().y_parity_byte()),
v: U256::from(t.signature().v().to_u64()),
y_parity: None,
}),
access_list: None,
Expand All @@ -315,7 +317,7 @@ pub fn to_alloy_transaction_with_hash_and_sender(
signature: Some(RpcSignature {
r: t.signature().r(),
s: t.signature().s(),
v: U256::from(t.signature().v().y_parity_byte()),
v: U256::from(t.signature().v().to_u64()),
y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())),
}),
access_list: Some(t.tx().access_list.clone()),
Expand All @@ -342,7 +344,7 @@ pub fn to_alloy_transaction_with_hash_and_sender(
signature: Some(RpcSignature {
r: t.signature().r(),
s: t.signature().s(),
v: U256::from(t.signature().v().y_parity_byte()),
v: U256::from(t.signature().v().to_u64()),
y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())),
}),
access_list: Some(t.tx().access_list.clone()),
Expand All @@ -369,7 +371,7 @@ pub fn to_alloy_transaction_with_hash_and_sender(
signature: Some(RpcSignature {
r: t.signature().r(),
s: t.signature().s(),
v: U256::from(t.signature().v().y_parity_byte()),
v: U256::from(t.signature().v().to_u64()),
y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())),
}),
access_list: Some(t.tx().tx().access_list.clone()),
Expand Down Expand Up @@ -832,12 +834,6 @@ impl TypedTransaction {
}
}

/// Returns true if the transaction was impersonated (using the impersonate Signature)
#[cfg(feature = "impersonated-tx")]
pub fn is_impersonated(&self) -> bool {
self.signature() == impersonated_signature()
}

/// Returns the hash if the transaction is impersonated (using a fake signature)
///
/// This appends the `address` before hashing it
Expand Down Expand Up @@ -886,7 +882,7 @@ impl TypedTransaction {
Self::Deposit(_) => Signature::from_scalars_and_parity(
B256::with_last_byte(1),
B256::with_last_byte(1),
false,
Parity::Parity(false),
)
.unwrap(),
}
Expand Down
31 changes: 27 additions & 4 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use alloy_consensus::{transaction::eip4844::TxEip4844Variant, TxEnvelope};
use alloy_dyn_abi::TypedData;
use alloy_eips::eip2718::Encodable2718;
use alloy_network::eip2718::Decodable2718;
use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, B64, U256, U64};
use alloy_primitives::{Address, Bytes, Parity, TxHash, TxKind, B256, B64, U256, U64};
use alloy_rpc_types::{
anvil::{
ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo,
Expand All @@ -52,6 +52,7 @@ use alloy_rpc_types::{
Transaction,
};
use alloy_serde::WithOtherFields;
use alloy_signer::Signature;
use alloy_transport::TransportErrorKind;
use anvil_core::{
eth::{
Expand Down Expand Up @@ -448,7 +449,7 @@ impl EthApi {
alloy_primitives::Signature::from_scalars_and_parity(
B256::with_last_byte(1),
B256::with_last_byte(1),
false,
Parity::Parity(false),
)
.unwrap();
return build_typed_transaction(request, nil_signature)
Expand Down Expand Up @@ -937,7 +938,7 @@ impl EthApi {

// if the sender is currently impersonated we need to "bypass" signing
let pending_transaction = if self.is_impersonated(from) {
let bypass_signature = self.backend.cheats().bypass_signature();
let bypass_signature = self.impersonated_signature(&request);
let transaction = sign::build_typed_transaction(request, bypass_signature)?;
self.ensure_typed_transaction_supported(&transaction)?;
trace!(target : "node", ?from, "eth_sendTransaction: impersonating");
Expand Down Expand Up @@ -2084,7 +2085,7 @@ impl EthApi {

let request = self.build_typed_tx_request(request, nonce)?;

let bypass_signature = self.backend.cheats().bypass_signature();
let bypass_signature = self.impersonated_signature(&request);
let transaction = sign::build_typed_transaction(request, bypass_signature)?;

self.ensure_typed_transaction_supported(&transaction)?;
Expand Down Expand Up @@ -2581,6 +2582,28 @@ impl EthApi {
self.backend.cheats().is_impersonated(addr)
}

/// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC
fn impersonated_signature(&self, request: &TypedTransactionRequest) -> Signature {
match request {
// Only the legacy transaction type requires v to be in {27, 28}, thus
// requiring the use of Parity::NonEip155
TypedTransactionRequest::Legacy(_) => Signature::from_scalars_and_parity(
B256::with_last_byte(1),
B256::with_last_byte(1),
Parity::NonEip155(false),
),
TypedTransactionRequest::EIP2930(_) |
TypedTransactionRequest::EIP1559(_) |
TypedTransactionRequest::EIP4844(_) |
TypedTransactionRequest::Deposit(_) => Signature::from_scalars_and_parity(
B256::with_last_byte(1),
B256::with_last_byte(1),
Parity::Parity(false),
),
}
.unwrap()
}

/// Returns the nonce of the `address` depending on the `block_number`
async fn get_transaction_count(
&self,
Expand Down
22 changes: 2 additions & 20 deletions crates/anvil/src/eth/backend/cheats.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Support for "cheat codes" / bypass functions

use alloy_primitives::{Address, Signature};
use anvil_core::eth::transaction::impersonated_signature;
use alloy_primitives::Address;
use parking_lot::RwLock;
use std::{collections::HashSet, sync::Arc};

Expand Down Expand Up @@ -48,11 +47,6 @@ impl CheatsManager {
}
}

/// Returns the signature to use to bypass transaction signing
pub fn bypass_signature(&self) -> Signature {
self.state.read().bypass_signature
}

/// Sets the auto impersonation flag which if set to true will make the `is_impersonated`
/// function always return true
pub fn set_auto_impersonate_account(&self, enabled: bool) {
Expand All @@ -67,22 +61,10 @@ impl CheatsManager {
}

/// Container type for all the state variables
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
pub struct CheatsState {
/// All accounts that are currently impersonated
pub impersonated_accounts: HashSet<Address>,
/// The signature used for the `eth_sendUnsignedTransaction` cheat code
pub bypass_signature: Signature,
/// If set to true will make the `is_impersonated` function always return true
pub auto_impersonate_accounts: bool,
}

impl Default for CheatsState {
fn default() -> Self {
Self {
impersonated_accounts: Default::default(),
bypass_signature: impersonated_signature(),
auto_impersonate_accounts: false,
}
}
}

0 comments on commit cb9dfae

Please sign in to comment.