Skip to content

Commit

Permalink
[Blocked] Update TransactionRequest's to field to TxKind (alloy-rs#553
Browse files Browse the repository at this point in the history
)

* feat: change the `to` field type to TxKind, update From<TxEip4844> accordingly

* refactor: update builder functions to include `to` checks.

* feat: add as_create(), with_call(), and deploy_code()

* refactor: remove error lines

* feat: with_call impl.

* feat: add the methods to the builder trait, update code that was broken.

* test: update tests.

* nits: reordering and default impls

* fix: some test compilation

* nit: fmt

* lint: clippy

* test: fix tests

* refactor: kind vs to

* feat: new_raw_deploy and generic input

* doc: add note re sol usage

* fix: type inference in test

* chore: update core

* docs

---------

Co-authored-by: James <james@prestwi.ch>
Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
  • Loading branch information
3 people authored and ben186 committed Jul 27, 2024
1 parent 890fc3f commit 8f24277
Show file tree
Hide file tree
Showing 15 changed files with 187 additions and 71 deletions.
11 changes: 5 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ alloy-transport-ws = { version = "0.1.0", default-features = false, path = "crat

test-utils = { version = "0.1.0", default-features = false, path = "crates/internal-test-utils", package = "alloy-internal-test-utils" }

alloy-core = { version = "0.7.0", default-features = false }
alloy-dyn-abi = { version = "0.7.0", default-features = false }
alloy-json-abi = { version = "0.7.0", default-features = false }
alloy-primitives = { version = "0.7.0", default-features = false }
alloy-sol-types = { version = "0.7.0", default-features = false }
alloy-chains = {version = "0.1.15", default-features = false}
alloy-core = { version = "0.7.1", default-features = false }
alloy-dyn-abi = { version = "0.7.1", default-features = false }
alloy-json-abi = { version = "0.7.1", default-features = false }
alloy-primitives = { version = "0.7.1", default-features = false }
alloy-sol-types = { version = "0.7.1", default-features = false }

alloy-rlp = { version = "0.3", default-features = false }

Expand Down
52 changes: 38 additions & 14 deletions crates/contract/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,12 @@ pub struct CallBuilder<T, P, D, N: Network = Ethereum> {

// See [`ContractInstance`].
impl<T: Transport + Clone, P: Provider<T, N>, N: Network> DynCallBuilder<T, P, N> {
pub(crate) fn new_dyn(
provider: P,
function: &Function,
args: &[DynSolValue],
address: &Address,
) -> Result<Self> {
Ok(Self::new_inner(provider, function.abi_encode_input(args)?.into(), function.clone())
.to(Some(*address)))
pub(crate) fn new_dyn(provider: P, function: &Function, args: &[DynSolValue]) -> Result<Self> {
Ok(Self::new_inner_call(
provider,
function.abi_encode_input(args)?.into(),
function.clone(),
))
}

/// Clears the decoder, returning a raw call builder.
Expand All @@ -232,7 +230,7 @@ impl<'a, T: Transport + Clone, P: Provider<T, N>, C: SolCall, N: Network>
// `sol!` macro constructor, see `#[sol(rpc)]`. Not public API.
// NOTE: please avoid changing this function due to its use in the `sol!` macro.
pub fn new_sol(provider: &'a P, address: &Address, call: &C) -> Self {
Self::new_inner(provider, call.abi_encode().into(), PhantomData::<C>).to(Some(*address))
Self::new_inner_call(provider, call.abi_encode().into(), PhantomData::<C>).to(*address)
}
}

Expand All @@ -258,12 +256,32 @@ impl<T: Transport + Clone, P: Provider<T, N>, N: Network> RawCallBuilder<T, P, N
/// same as [`call_raw`](Self::call_raw).
#[inline]
pub fn new_raw(provider: P, input: Bytes) -> Self {
Self::new_inner(provider, input, ())
Self::new_inner_call(provider, input, ())
}

/// Creates a new call builder with the provided provider and contract deploy code.
///
/// Will not decode the output of the call, meaning that [`call`](Self::call) will behave the
/// same as [`call_raw`](Self::call_raw).
// NOTE: please avoid changing this function due to its use in the `sol!` macro.
pub fn new_raw_deploy(provider: P, input: Bytes) -> Self {
Self::new_inner_deploy(provider, input, ())
}
}

impl<T: Transport + Clone, P: Provider<T, N>, D: CallDecoder, N: Network> CallBuilder<T, P, D, N> {
fn new_inner(provider: P, input: Bytes, decoder: D) -> Self {
fn new_inner_deploy(provider: P, input: Bytes, decoder: D) -> Self {
Self {
request: <N::TransactionRequest>::default().with_deploy_code(input),
decoder,
provider,
block: BlockId::default(),
state: None,
transport: PhantomData,
}
}

fn new_inner_call(provider: P, input: Bytes, decoder: D) -> Self {
Self {
request: <N::TransactionRequest>::default().with_input(input),
decoder,
Expand All @@ -280,9 +298,15 @@ impl<T: Transport + Clone, P: Provider<T, N>, D: CallDecoder, N: Network> CallBu
self
}

/// Sets the transaction request to the provided tx kind.
pub fn kind(mut self, to: TxKind) -> Self {
self.request.set_kind(to);
self
}

/// Sets the `to` field in the transaction to the provided address.
pub fn to(mut self, to: Option<Address>) -> Self {
self.request.set_to(to.into());
pub fn to(mut self, to: Address) -> Self {
self.request.set_to(to);
self
}

Expand Down Expand Up @@ -419,7 +443,7 @@ impl<T: Transport + Clone, P: Provider<T, N>, D: CallDecoder, N: Network> CallBu
/// Note that the deployment address can be pre-calculated if the `from` address and `nonce` are
/// known using [`calculate_create_address`](Self::calculate_create_address).
pub async fn deploy(&self) -> Result<Address> {
if !self.request.to().is_some_and(|to| to.is_create()) {
if !self.request.kind().is_some_and(|to| to.is_create()) {
return Err(Error::NotADeploymentTransaction);
}
let pending_tx = self.send().await?;
Expand Down
2 changes: 2 additions & 0 deletions crates/eips/src/eip6110.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
//!
//! Provides validator deposits as a list of deposit operations added to the Execution Layer block.
#![allow(unknown_lints, non_local_definitions)]

use alloy_primitives::{address, Address, FixedBytes, B256};
use alloy_rlp::{RlpDecodable, RlpEncodable};

Expand Down
1 change: 1 addition & 0 deletions crates/network/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ alloy-json-rpc.workspace = true
alloy-primitives.workspace = true
alloy-rpc-types.workspace = true
alloy-signer.workspace = true
alloy-sol-types.workspace = true

async-trait.workspace = true
futures-utils-wasm.workspace = true
Expand Down
15 changes: 10 additions & 5 deletions crates/network/src/any/builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::ops::{Deref, DerefMut};

use alloy_consensus::BlobTransactionSidecar;
use alloy_primitives::Bytes;
use alloy_rpc_types::{AccessList, TransactionRequest, WithOtherFields};

use crate::{any::AnyNetwork, BuildResult, Network, TransactionBuilder, TransactionBuilderError};
Expand All @@ -26,7 +27,7 @@ impl TransactionBuilder<AnyNetwork> for WithOtherFields<TransactionRequest> {
self.deref().input()
}

fn set_input(&mut self, input: alloy_primitives::Bytes) {
fn set_input<T: Into<Bytes>>(&mut self, input: T) {
self.deref_mut().set_input(input);
}

Expand All @@ -38,12 +39,16 @@ impl TransactionBuilder<AnyNetwork> for WithOtherFields<TransactionRequest> {
self.deref_mut().set_from(from);
}

fn to(&self) -> Option<alloy_primitives::TxKind> {
self.deref().to()
fn kind(&self) -> Option<alloy_primitives::TxKind> {
self.deref().kind()
}

fn set_to(&mut self, to: alloy_primitives::TxKind) {
self.deref_mut().set_to(to)
fn clear_kind(&mut self) {
self.deref_mut().clear_kind()
}

fn set_kind(&mut self, kind: alloy_primitives::TxKind) {
self.deref_mut().set_kind(kind)
}

fn value(&self) -> Option<alloy_primitives::U256> {
Expand Down
39 changes: 21 additions & 18 deletions crates/network/src/ethereum/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ impl TransactionBuilder<Ethereum> for TransactionRequest {
self.input.input()
}

fn set_input(&mut self, input: Bytes) {
self.input.input = Some(input);
fn set_input<T: Into<Bytes>>(&mut self, input: T) {
self.input.input = Some(input.into());
}

fn from(&self) -> Option<Address> {
Expand All @@ -38,15 +38,16 @@ impl TransactionBuilder<Ethereum> for TransactionRequest {
self.from = Some(from);
}

fn to(&self) -> Option<TxKind> {
self.to.map(TxKind::Call).or(Some(TxKind::Create))
fn kind(&self) -> Option<TxKind> {
self.to
}

fn set_to(&mut self, to: TxKind) {
match to {
TxKind::Create => self.to = None,
TxKind::Call(to) => self.to = Some(to),
}
fn set_kind(&mut self, kind: TxKind) {
self.to = Some(kind);
}

fn clear_kind(&mut self) {
self.to = None;
}

fn value(&self) -> Option<U256> {
Expand Down Expand Up @@ -193,7 +194,7 @@ mod tests {
.with_gas_limit(0)
.with_max_fee_per_gas(0)
.with_max_priority_fee_per_gas(0)
.with_to(Address::ZERO.into())
.with_to(Address::ZERO)
.with_blob_sidecar(BlobTransactionSidecar::default())
.with_max_fee_per_blob_gas(0);

Expand All @@ -213,7 +214,7 @@ mod tests {
.with_gas_limit(0)
.with_max_fee_per_gas(0)
.with_max_priority_fee_per_gas(0)
.with_to(Address::ZERO.into())
.with_to(Address::ZERO)
.with_gas_price(0)
.access_list(AccessList::default());

Expand All @@ -229,14 +230,13 @@ mod tests {
.with_gas_limit(0)
.with_max_fee_per_gas(0)
.with_max_priority_fee_per_gas(0)
.with_to(Address::ZERO.into());
.with_to(Address::ZERO);

let tx = request.clone().build_unsigned().unwrap();

assert!(matches!(tx, TypedTransaction::Eip1559(_)));

let request = request.with_gas_price(0);
dbg!(request.preferred_type());
let tx = request.build_unsigned().unwrap();
assert!(matches!(tx, TypedTransaction::Legacy(_)));
}
Expand All @@ -263,7 +263,8 @@ mod tests {
};

assert_eq!(tx_type, TxType::Legacy);
assert_eq!(errors.len(), 2);
assert_eq!(errors.len(), 3);
assert!(errors.contains(&"to"));
assert!(errors.contains(&"nonce"));
assert!(errors.contains(&"gas_limit"));
}
Expand All @@ -279,7 +280,8 @@ mod tests {
};

assert_eq!(tx_type, TxType::Eip1559);
assert_eq!(errors.len(), 4);
assert_eq!(errors.len(), 5);
assert!(errors.contains(&"to"));
assert!(errors.contains(&"nonce"));
assert!(errors.contains(&"gas_limit"));
assert!(errors.contains(&"max_priority_fee_per_gas"));
Expand All @@ -297,7 +299,8 @@ mod tests {
};

assert_eq!(tx_type, TxType::Eip2930);
assert_eq!(errors.len(), 3);
assert_eq!(errors.len(), 4);
assert!(errors.contains(&"to"));
assert!(errors.contains(&"nonce"));
assert!(errors.contains(&"gas_limit"));
assert!(errors.contains(&"gas_price"));
Expand All @@ -315,8 +318,8 @@ mod tests {
};

assert_eq!(tx_type, TxType::Eip4844);
dbg!(&errors);
assert_eq!(errors.len(), 6);
assert_eq!(errors.len(), 7);
assert!(errors.contains(&"to"));
assert!(errors.contains(&"nonce"));
assert!(errors.contains(&"gas_limit"));
assert!(errors.contains(&"max_priority_fee_per_gas"));
Expand Down
75 changes: 69 additions & 6 deletions crates/network/src/transaction/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::Network;
use alloy_consensus::BlobTransactionSidecar;
use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256};
use alloy_rpc_types::AccessList;
use alloy_sol_types::SolCall;
use futures_utils_wasm::impl_future;

/// Result type for transaction builders
Expand Down Expand Up @@ -78,10 +79,10 @@ pub trait TransactionBuilder<N: Network>: Default + Sized + Send + Sync + 'stati
fn input(&self) -> Option<&Bytes>;

/// Set the input data for the transaction.
fn set_input(&mut self, input: Bytes);
fn set_input<T: Into<Bytes>>(&mut self, input: T);

/// Builder-pattern method for setting the input data.
fn with_input(mut self, input: Bytes) -> Self {
fn with_input<T: Into<Bytes>>(mut self, input: T) -> Self {
self.set_input(input);
self
}
Expand All @@ -98,24 +99,86 @@ pub trait TransactionBuilder<N: Network>: Default + Sized + Send + Sync + 'stati
self
}

/// Get the kind of transaction.
fn kind(&self) -> Option<alloy_primitives::TxKind>;

/// Clear the kind of transaction.
fn clear_kind(&mut self);

/// Set the kind of transaction.
fn set_kind(&mut self, kind: alloy_primitives::TxKind);

/// Builder-pattern method for setting the kind of transaction.
fn with_kind(mut self, kind: alloy_primitives::TxKind) -> Self {
self.set_kind(kind);
self
}

/// Get the recipient for the transaction.
fn to(&self) -> Option<TxKind>;
fn to(&self) -> Option<Address> {
if let Some(TxKind::Call(addr)) = self.kind() {
return Some(addr);
}
None
}

/// Set the recipient for the transaction.
fn set_to(&mut self, to: TxKind);
fn set_to(&mut self, to: Address) {
self.set_kind(TxKind::Call(to));
}

/// Builder-pattern method for setting the recipient.
fn with_to(mut self, to: TxKind) -> Self {
fn with_to(mut self, to: Address) -> Self {
self.set_to(to);
self
}

/// Set the `to` field to a create call.
fn set_create(&mut self) {
self.set_kind(TxKind::Create);
}

/// Set the `to` field to a create call.
fn into_create(mut self) -> Self {
self.set_create();
self
}

/// Deploy the code by making a create call with data. This will set the
/// `to` field to [`TxKind::Create`].
fn set_deploy_code<T: Into<Bytes>>(&mut self, code: T) {
self.set_input(code.into());
self.set_create()
}

/// Deploy the code by making a create call with data. This will set the
/// `to` field to [`TxKind::Create`].
fn with_deploy_code<T: Into<Bytes>>(mut self, code: T) -> Self {
self.set_deploy_code(code);
self
}

/// Set the data field to a contract call. This will clear the `to` field
/// if it is set to [`TxKind::Create`].
fn set_call<T: SolCall>(&mut self, t: &T) {
self.set_input(t.abi_encode());
if matches!(self.kind(), Some(TxKind::Create)) {
self.clear_kind();
}
}

/// Make a contract call with data.
fn with_call<T: SolCall>(mut self, t: &T) -> Self {
self.set_call(t);
self
}

/// Calculates the address that will be created by the transaction, if any.
///
/// Returns `None` if the transaction is not a contract creation (the `to` field is set), or if
/// the `from` or `nonce` fields are not set.
fn calculate_create_address(&self) -> Option<Address> {
if !self.to().is_some_and(|to| to.is_create()) {
if !self.kind().is_some_and(|to| to.is_create()) {
return None;
}
let from = self.from()?;
Expand Down
2 changes: 1 addition & 1 deletion crates/provider/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ mod test {
let gas_price = provider.get_gas_price().await.unwrap();
let tx = TransactionRequest::default()
.from(from)
.with_input("0xdeadbeef".into())
.with_input("0xdeadbeef")
.max_fee_per_gas(gas_price + 1)
.max_priority_fee_per_gas(gas_price + 1);

Expand Down
Loading

0 comments on commit 8f24277

Please sign in to comment.