Skip to content

Commit

Permalink
feat: Ethereum network
Browse files Browse the repository at this point in the history
  • Loading branch information
onbjerg committed Mar 11, 2024
1 parent 6e1b1ee commit 8c5c484
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 97 deletions.
8 changes: 6 additions & 2 deletions crates/network/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ repository.workspace = true
exclude.workspace = true

[dependencies]
alloy-consensus.workspace = true
alloy-eips = { workspace = true, features = ["serde"] }
alloy-json-rpc.workspace = true
alloy-primitives.workspace = true
alloy-rlp.workspace = true
alloy-rpc-types.workspace = true
alloy-signer.workspace = true
async-trait.workspace = true
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true

[features]
k256 = ["alloy-primitives/k256"]
k256 = ["alloy-primitives/k256", "alloy-consensus/k256"]
108 changes: 108 additions & 0 deletions crates/network/src/ethereum/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use crate::{BuilderResult, Network, NetworkSigner, TransactionBuilder};

mod receipt;
mod signer;
use alloy_primitives::{Address, TxKind, U256, U64};
pub use signer::EthereumSigner;

/// Types for a mainnet-like Ethereum network.
#[derive(Debug, Clone, Copy)]
pub struct Ethereum;

impl Network for Ethereum {
type TxEnvelope = alloy_consensus::TxEnvelope;

type UnsignedTx = alloy_consensus::TypedTransaction;

type ReceiptEnvelope = alloy_consensus::ReceiptEnvelope;

type Header = alloy_consensus::Header;

type TransactionRequest = alloy_rpc_types::transaction::TransactionRequest;

type TransactionResponse = alloy_rpc_types::Transaction;

type ReceiptResponse = alloy_rpc_types::TransactionReceipt;

type HeaderResponse = alloy_rpc_types::Header;
}

impl TransactionBuilder<Ethereum> for alloy_rpc_types::TransactionRequest {
fn chain_id(&self) -> Option<alloy_primitives::ChainId> {
self.chain_id
}

fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) {
self.chain_id = Some(chain_id);
}

fn nonce(&self) -> Option<U64> {
self.nonce
}

fn set_nonce(&mut self, nonce: U64) {
self.nonce = Some(nonce);
}

fn input(&self) -> Option<&alloy_primitives::Bytes> {
self.input.input()
}

fn set_input(&mut self, input: alloy_primitives::Bytes) {
self.input.input = Some(input);
}

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

fn from(&self) -> Option<Address> {
self.from
}

fn set_from(&mut self, from: Address) {
self.from = Some(from);
}

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

fn value(&self) -> Option<alloy_primitives::U256> {
self.value
}

fn set_value(&mut self, value: alloy_primitives::U256) {
self.value = Some(value)
}

fn gas_price(&self) -> Option<U256> {
todo!()
}

fn set_gas_price(&mut self, gas_price: U256) {
todo!()
}

fn gas_limit(&self) -> Option<U256> {
self.gas
}

fn set_gas_limit(&mut self, gas_limit: U256) {
self.gas = Some(gas_limit);
}

fn build_unsigned(self) -> BuilderResult<<Ethereum as Network>::UnsignedTx> {
todo!()
}

fn build<S: NetworkSigner<Ethereum>>(
self,
signer: &S,
) -> BuilderResult<<Ethereum as Network>::TxEnvelope> {
todo!()
}
}
43 changes: 43 additions & 0 deletions crates/network/src/ethereum/receipt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::Receipt;
use alloy_consensus::ReceiptWithBloom;
use alloy_primitives::{Bloom, Log};

impl Receipt for alloy_consensus::Receipt {
fn success(&self) -> bool {
self.success
}

fn bloom(&self) -> Bloom {
self.bloom_slow()
}

fn cumulative_gas_used(&self) -> u64 {
self.cumulative_gas_used
}

fn logs(&self) -> &[Log] {
&self.logs
}
}

impl Receipt for ReceiptWithBloom {
fn success(&self) -> bool {
self.receipt.success
}

fn bloom(&self) -> Bloom {
self.bloom
}

fn bloom_cheap(&self) -> Option<Bloom> {
Some(self.bloom)
}

fn cumulative_gas_used(&self) -> u64 {
self.receipt.cumulative_gas_used
}

fn logs(&self) -> &[Log] {
&self.receipt.logs
}
}
141 changes: 141 additions & 0 deletions crates/network/src/ethereum/signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use super::Ethereum;
use crate::{NetworkSigner, TxSigner};
use alloy_consensus::{SignableTransaction, TxEnvelope, TypedTransaction};
use alloy_signer::Signature;
use async_trait::async_trait;

/// A signer capable of signing any transaction for the Ethereum network.
pub struct EthereumSigner(Box<dyn TxSigner<Signature> + Sync>);

impl std::fmt::Debug for EthereumSigner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("EthereumSigner").finish()
}
}

impl<S> From<S> for EthereumSigner
where
S: TxSigner<Signature> + Sync + 'static,
{
fn from(signer: S) -> Self {
Self(Box::new(signer))
}
}

impl EthereumSigner {
async fn sign_transaction(
&self,
tx: &mut dyn SignableTransaction<Signature>,
) -> alloy_signer::Result<Signature> {
self.0.sign_transaction(tx).await
}
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl NetworkSigner<Ethereum> for EthereumSigner {
async fn sign(&self, tx: TypedTransaction) -> alloy_signer::Result<TxEnvelope> {
match tx {
TypedTransaction::Legacy(mut t) => {
let sig = self.sign_transaction(&mut t).await?;
Ok(t.into_signed(sig).into())
}
TypedTransaction::Eip2930(mut t) => {
let sig = self.sign_transaction(&mut t).await?;
Ok(t.into_signed(sig).into())
}
TypedTransaction::Eip1559(mut t) => {
let sig = self.sign_transaction(&mut t).await?;
Ok(t.into_signed(sig).into())
}
TypedTransaction::Eip4844(mut t) => {
let sig = self.sign_transaction(&mut t).await?;
Ok(t.into_signed(sig).into())
}
}
}
}

#[cfg(test)]
mod test {
use alloy_consensus::{SignableTransaction, TxLegacy};
use alloy_primitives::{address, ChainId, Signature, U256};
use alloy_signer::{k256, Result, Signer, TxSigner, TxSignerSync};

#[tokio::test]
async fn signs_tx() {
async fn sign_tx_test(tx: &mut TxLegacy, chain_id: Option<ChainId>) -> Result<Signature> {
let mut before = tx.clone();
let sig = sign_dyn_tx_test(tx, chain_id).await?;
if let Some(chain_id) = chain_id {
assert_eq!(tx.chain_id, Some(chain_id), "chain ID was not set");
before.chain_id = Some(chain_id);
}
assert_eq!(*tx, before);
Ok(sig)
}

async fn sign_dyn_tx_test(
tx: &mut dyn SignableTransaction<Signature>,
chain_id: Option<ChainId>,
) -> Result<Signature> {
let mut wallet: alloy_signer::Wallet<k256::ecdsa::SigningKey> =
"4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318".parse().unwrap();
wallet.set_chain_id(chain_id);

let sig = wallet.sign_transaction_sync(tx)?;
let sighash = tx.signature_hash();
assert_eq!(sig.recover_address_from_prehash(&sighash).unwrap(), wallet.address());

let sig_async = wallet.sign_transaction(tx).await.unwrap();
assert_eq!(sig_async, sig);

Ok(sig)
}

// retrieved test vector from:
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
let mut tx = TxLegacy {
to: alloy_primitives::TxKind::Call(address!(
"F0109fC8DF283027b6285cc889F5aA624EaC1F55"
)),
value: U256::from(1_000_000_000),
gas_limit: 2_000_000,
nonce: 0,
gas_price: 21_000_000_000,
input: Default::default(),
chain_id: None,
};
let sig_none = sign_tx_test(&mut tx, None).await.unwrap();

tx.chain_id = Some(1);
let sig_1 = sign_tx_test(&mut tx, None).await.unwrap();
let expected = "c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa6825".parse().unwrap();
assert_eq!(sig_1, expected);
assert_ne!(sig_1, sig_none);

tx.chain_id = Some(2);
let sig_2 = sign_tx_test(&mut tx, None).await.unwrap();
assert_ne!(sig_2, sig_1);
assert_ne!(sig_2, sig_none);

// Sets chain ID.
tx.chain_id = None;
let sig_none_none = sign_tx_test(&mut tx, None).await.unwrap();
assert_eq!(sig_none_none, sig_none);

tx.chain_id = None;
let sig_none_1 = sign_tx_test(&mut tx, Some(1)).await.unwrap();
assert_eq!(sig_none_1, sig_1);

tx.chain_id = None;
let sig_none_2 = sign_tx_test(&mut tx, Some(2)).await.unwrap();
assert_eq!(sig_none_2, sig_2);

// Errors on mismatch.
tx.chain_id = Some(2);
let error = sign_tx_test(&mut tx, Some(1)).await.unwrap_err();
let expected_error = alloy_signer::Error::TransactionChainIdMismatch { signer: 1, tx: 2 };
assert_eq!(error.to_string(), expected_error.to_string());
}
}
14 changes: 10 additions & 4 deletions crates/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ use alloy_eips::eip2718::Eip2718Envelope;
use alloy_json_rpc::RpcObject;
use alloy_primitives::B256;

mod sealed;
pub use sealed::{Sealable, Sealed};

mod transaction;
pub use transaction::{
BuilderResult, NetworkSigner, TransactionBuilder, TransactionBuilderError, TxSigner,
Expand All @@ -33,6 +30,9 @@ pub use receipt::Receipt;

pub use alloy_eips::eip2718;

mod ethereum;
pub use ethereum::{Ethereum, EthereumSigner};

/// A list of transactions, either hydrated or hashes.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(untagged)]
Expand All @@ -54,6 +54,8 @@ pub struct BlockResponse<N: Network> {
}

/// Captures type info for network-specific RPC requests/responses.
// todo: block responses are ethereum only, so we need to include this in here too, or make `Block`
// generic over tx/header type
pub trait Network: Sized + Send + Sync + 'static {
#[doc(hidden)]
/// Asserts that this trait can only be implemented on a ZST.
Expand All @@ -65,6 +67,10 @@ pub trait Network: Sized + Send + Sync + 'static {

/// The network transaction envelope type.
type TxEnvelope: Eip2718Envelope;

/// An enum over the various transaction types.
type UnsignedTx;

/// The network receipt envelope type.
type ReceiptEnvelope: Eip2718Envelope;
/// The network header type.
Expand All @@ -73,7 +79,7 @@ pub trait Network: Sized + Send + Sync + 'static {
// -- JSON RPC types --

/// The JSON body of a transaction request.
type TransactionRequest: RpcObject + Transaction; // + TransactionBuilder
type TransactionRequest: RpcObject + TransactionBuilder<Self> + std::fmt::Debug;
/// The JSON body of a transaction response.
type TransactionResponse: RpcObject;
/// The JSON body of a transaction receipt.
Expand Down
Loading

0 comments on commit 8c5c484

Please sign in to comment.