From ccff9fc98f61c049fda6b7ef60ab3fef7f60c66b Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Mon, 9 Aug 2021 01:59:07 +0300 Subject: [PATCH] feat: 1559/2930 txs (part 2) (#355) * feat: add eip2930 tx type * feat: add typed transaction enum * chore: rlp_opt take reference to option * feat: add eip-1559 tx type * feat: add eip-1559 to the typed tx enum * fix: references to rlp_opt / add access list setter * chore: ignore etherchain and ethgasstation Their APIs had a breaking change * fix(1559/2930): serialize properly 1. The AccessList struct should have used RlpEncodableWrapper, otherwise we get extra bytes 2. The 1559/2930 types do not use eip-155-style replay protection * feat: add helpers for operating on the txs enum * chore: cargo fmt / lints --- Cargo.lock | 4 +- ethers-core/src/types/mod.rs | 10 +- ethers-core/src/types/transaction/eip1559.rs | 172 +++++++++++++ ethers-core/src/types/transaction/eip2718.rs | 237 ++++++++++++++++++ ethers-core/src/types/transaction/eip2930.rs | 125 ++++++++- ethers-core/src/types/transaction/mod.rs | 6 +- ethers-core/src/types/transaction/request.rs | 18 +- ethers-core/src/types/transaction/response.rs | 11 +- ethers-middleware/tests/gas_oracle.rs | 4 +- 9 files changed, 561 insertions(+), 26 deletions(-) create mode 100644 ethers-core/src/types/transaction/eip1559.rs create mode 100644 ethers-core/src/types/transaction/eip2718.rs diff --git a/Cargo.lock b/Cargo.lock index 9d31619f..7e98c6b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2116,9 +2116,9 @@ dependencies = [ [[package]] name = "rlp" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54369147e3e7796c9b885c7304db87ca3d09a0a98f72843d532868675bbfba8" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" dependencies = [ "bytes", "rustc-hex", diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs index 597b7cb6..2cf9d5fc 100644 --- a/ethers-core/src/types/mod.rs +++ b/ethers-core/src/types/mod.rs @@ -7,9 +7,13 @@ pub use ethabi::ethereum_types::H256 as TxHash; pub use ethabi::ethereum_types::{Address, Bloom, H160, H256, U128, U256, U64}; -mod transaction; -pub use transaction::request::TransactionRequest; -pub use transaction::response::{Transaction, TransactionReceipt}; +pub mod transaction; +pub use transaction::{ + eip1559::Eip1559TransactionRequest, + eip2930::Eip2930TransactionRequest, + request::TransactionRequest, + response::{Transaction, TransactionReceipt}, +}; mod address_or_bytes; pub use address_or_bytes::AddressOrBytes; diff --git a/ethers-core/src/types/transaction/eip1559.rs b/ethers-core/src/types/transaction/eip1559.rs new file mode 100644 index 00000000..29d39da2 --- /dev/null +++ b/ethers-core/src/types/transaction/eip1559.rs @@ -0,0 +1,172 @@ +use super::{eip2930::AccessList, rlp_opt}; +use crate::{ + types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64}, + utils::keccak256, +}; +use rlp::RlpStream; + +/// EIP-1559 transactions have 9 fields +const NUM_TX_FIELDS: usize = 9; + +use serde::{Deserialize, Serialize}; +/// Parameters for sending a transaction +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)] +pub struct Eip1559TransactionRequest { + /// Sender address or ENS name + #[serde(skip_serializing_if = "Option::is_none")] + pub from: Option
, + + /// Recipient address (None for contract creation) + #[serde(skip_serializing_if = "Option::is_none")] + pub to: Option, + + /// Supplied gas (None for sensible default) + #[serde(skip_serializing_if = "Option::is_none")] + pub gas: Option, + + /// Transfered value (None for no transfer) + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option, + + /// The compiled code of a contract OR the first 4 bytes of the hash of the + /// invoked method signature and encoded parameters. For details see Ethereum Contract ABI + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, + + /// Transaction nonce (None for next available nonce) + #[serde(skip_serializing_if = "Option::is_none")] + pub nonce: Option, + + #[serde( + rename = "accessList", + default, + skip_serializing_if = "Option::is_none" + )] + pub access_list: Option, + + #[serde( + rename = "maxPriorityFeePerGas", + default, + skip_serializing_if = "Option::is_none" + )] + /// Represents the maximum tx fee that will go to the miner as part of the user's + /// fee payment. It serves 3 purposes: + /// 1. Compensates miners for the uncle/ommer risk + fixed costs of including transaction in a block; + /// 2. Allows users with high opportunity costs to pay a premium to miners; + /// 3. In times where demand exceeds the available block space (i.e. 100% full, 30mm gas), + /// this component allows first price auctions (i.e. the pre-1559 fee model) to happen on the priority fee. + /// + /// More context [here](https://hackmd.io/@q8X_WM2nTfu6nuvAzqXiTQ/1559-wallets) + pub max_priority_fee_per_gas: Option, + + #[serde( + rename = "maxFeePerGas", + default, + skip_serializing_if = "Option::is_none" + )] + /// Represents the maximum amount that a user is willing to pay for their tx (inclusive of baseFeePerGas and maxPriorityFeePerGas). + /// The difference between maxFeePerGas and baseFeePerGas + maxPriorityFeePerGas is “refunded” to the user. + pub max_fee_per_gas: Option, +} + +impl Eip1559TransactionRequest { + /// Creates an empty transaction request with all fields left empty + pub fn new() -> Self { + Self::default() + } + + // Builder pattern helpers + + /// Sets the `from` field in the transaction to the provided value + pub fn from>(mut self, from: T) -> Self { + self.from = Some(from.into()); + self + } + + /// Sets the `to` field in the transaction to the provided value + pub fn to>(mut self, to: T) -> Self { + self.to = Some(to.into()); + self + } + + /// Sets the `gas` field in the transaction to the provided value + pub fn gas>(mut self, gas: T) -> Self { + self.gas = Some(gas.into()); + self + } + + /// Sets the `max_priority_fee_per_gas` field in the transaction to the provided value + pub fn max_priority_fee_per_gas>(mut self, max_priority_fee_per_gas: T) -> Self { + self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas.into()); + self + } + + /// Sets the `max_fee_per_gas` field in the transaction to the provided value + pub fn max_fee_per_gas>(mut self, max_fee_per_gas: T) -> Self { + self.max_fee_per_gas = Some(max_fee_per_gas.into()); + self + } + + /// Sets the `value` field in the transaction to the provided value + pub fn value>(mut self, value: T) -> Self { + self.value = Some(value.into()); + self + } + + /// Sets the `data` field in the transaction to the provided value + pub fn data>(mut self, data: T) -> Self { + self.data = Some(data.into()); + self + } + + /// Sets the `access_list` field in the transaction to the provided value + pub fn access_list>(mut self, access_list: T) -> Self { + self.access_list = Some(access_list.into()); + self + } + + /// Sets the `nonce` field in the transaction to the provided value + pub fn nonce>(mut self, nonce: T) -> Self { + self.nonce = Some(nonce.into()); + self + } + + /// Hashes the transaction's data with the provided chain id + pub fn sighash>(&self, chain_id: T) -> H256 { + keccak256(self.rlp(chain_id).as_ref()).into() + } + + /// Gets the unsigned transaction's RLP encoding + pub fn rlp>(&self, chain_id: T) -> Bytes { + let mut rlp = RlpStream::new(); + rlp.begin_list(NUM_TX_FIELDS); + self.rlp_base(chain_id, &mut rlp); + rlp.out().freeze().into() + } + + /// Produces the RLP encoding of the transaction with the provided signature + pub fn rlp_signed>(&self, chain_id: T, signature: &Signature) -> Bytes { + let mut rlp = RlpStream::new(); + rlp.begin_unbounded_list(); + self.rlp_base(chain_id, &mut rlp); + + // append the signature + rlp.append(&signature.v); + rlp.append(&signature.r); + rlp.append(&signature.s); + rlp.finalize_unbounded_list(); + rlp.out().freeze().into() + } + + pub(crate) fn rlp_base>(&self, chain_id: T, rlp: &mut RlpStream) { + rlp.append(&chain_id.into()); + rlp_opt(rlp, &self.nonce); + rlp_opt(rlp, &self.max_priority_fee_per_gas); + rlp_opt(rlp, &self.max_fee_per_gas); + rlp_opt(rlp, &self.gas); + rlp_opt(rlp, &self.to.as_ref()); + rlp_opt(rlp, &self.value); + rlp_opt(rlp, &self.data.as_ref().map(|d| d.as_ref())); + rlp_opt(rlp, &self.access_list); + } +} diff --git a/ethers-core/src/types/transaction/eip2718.rs b/ethers-core/src/types/transaction/eip2718.rs new file mode 100644 index 00000000..fd23e178 --- /dev/null +++ b/ethers-core/src/types/transaction/eip2718.rs @@ -0,0 +1,237 @@ +use super::{eip1559::Eip1559TransactionRequest, eip2930::Eip2930TransactionRequest}; +use crate::{ + types::{Address, Bytes, NameOrAddress, Signature, TransactionRequest, H256, U256, U64}, + utils::keccak256, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[serde(tag = "type")] +pub enum TypedTransaction { + // 0x00 + #[serde(rename = "0x00")] + Legacy(TransactionRequest), + // 0x01 + #[serde(rename = "0x01")] + Eip2930(Eip2930TransactionRequest), + // 0x02 + #[serde(rename = "0x02")] + Eip1559(Eip1559TransactionRequest), +} + +impl TypedTransaction { + pub fn from(&self) -> Option<&Address> { + use TypedTransaction::*; + match self { + Legacy(inner) => inner.from.as_ref(), + Eip2930(inner) => inner.tx.from.as_ref(), + Eip1559(inner) => inner.from.as_ref(), + } + } + + pub fn set_from(&mut self, from: Address) { + use TypedTransaction::*; + match self { + Legacy(inner) => inner.from = Some(from), + Eip2930(inner) => inner.tx.from = Some(from), + Eip1559(inner) => inner.from = Some(from), + }; + } + + pub fn to(&self) -> Option<&NameOrAddress> { + use TypedTransaction::*; + match self { + Legacy(inner) => inner.to.as_ref(), + Eip2930(inner) => inner.tx.to.as_ref(), + Eip1559(inner) => inner.to.as_ref(), + } + } + + pub fn set_to>(&mut self, to: T) { + let to = to.into(); + use TypedTransaction::*; + match self { + Legacy(inner) => inner.to = Some(to), + Eip2930(inner) => inner.tx.to = Some(to), + Eip1559(inner) => inner.to = Some(to), + }; + } + + pub fn nonce(&self) -> Option<&U256> { + use TypedTransaction::*; + match self { + Legacy(inner) => inner.nonce.as_ref(), + Eip2930(inner) => inner.tx.nonce.as_ref(), + Eip1559(inner) => inner.nonce.as_ref(), + } + } + + pub fn set_nonce>(&mut self, nonce: T) { + let nonce = nonce.into(); + use TypedTransaction::*; + match self { + Legacy(inner) => inner.nonce = Some(nonce), + Eip2930(inner) => inner.tx.nonce = Some(nonce), + Eip1559(inner) => inner.nonce = Some(nonce), + }; + } + + pub fn value(&self) -> Option<&U256> { + use TypedTransaction::*; + match self { + Legacy(inner) => inner.value.as_ref(), + Eip2930(inner) => inner.tx.value.as_ref(), + Eip1559(inner) => inner.value.as_ref(), + } + } + + pub fn set_value>(&mut self, value: T) { + let value = value.into(); + use TypedTransaction::*; + match self { + Legacy(inner) => inner.value = Some(value), + Eip2930(inner) => inner.tx.value = Some(value), + Eip1559(inner) => inner.value = Some(value), + }; + } + + pub fn gas(&self) -> Option<&U256> { + use TypedTransaction::*; + match self { + Legacy(inner) => inner.gas.as_ref(), + Eip2930(inner) => inner.tx.gas.as_ref(), + Eip1559(inner) => inner.gas.as_ref(), + } + } + + pub fn set_gas>(&mut self, gas: T) { + let gas = gas.into(); + use TypedTransaction::*; + match self { + Legacy(inner) => inner.gas = Some(gas), + Eip2930(inner) => inner.tx.gas = Some(gas), + Eip1559(inner) => inner.gas = Some(gas), + }; + } + + pub fn set_gas_price>(&mut self, gas_price: T) { + let gas_price = gas_price.into(); + use TypedTransaction::*; + match self { + Legacy(inner) => inner.gas_price = Some(gas_price), + Eip2930(inner) => inner.tx.gas_price = Some(gas_price), + Eip1559(inner) => { + inner.max_fee_per_gas = Some(gas_price); + inner.max_priority_fee_per_gas = Some(gas_price); + } + }; + } + + pub fn data(&self) -> Option<&Bytes> { + use TypedTransaction::*; + match self { + Legacy(inner) => inner.data.as_ref(), + Eip2930(inner) => inner.tx.data.as_ref(), + Eip1559(inner) => inner.data.as_ref(), + } + } + + pub fn set_data(&mut self, data: Bytes) { + use TypedTransaction::*; + match self { + Legacy(inner) => inner.data = Some(data), + Eip2930(inner) => inner.tx.data = Some(data), + Eip1559(inner) => inner.data = Some(data), + }; + } + + pub fn rlp_signed>(&self, chain_id: T, signature: &Signature) -> Bytes { + use TypedTransaction::*; + let mut encoded = vec![]; + match self { + Legacy(inner) => { + encoded.extend_from_slice(&[0x0]); + encoded.extend_from_slice(inner.rlp_signed(signature).as_ref()); + } + Eip2930(inner) => { + encoded.extend_from_slice(&[0x1]); + encoded.extend_from_slice(inner.rlp_signed(chain_id, signature).as_ref()); + } + Eip1559(inner) => { + encoded.extend_from_slice(&[0x2]); + encoded.extend_from_slice(inner.rlp_signed(chain_id, signature).as_ref()); + } + }; + + rlp::encode(&encoded).freeze().into() + } + + pub fn rlp>(&self, chain_id: T) -> Bytes { + let chain_id = chain_id.into(); + let mut encoded = vec![]; + use TypedTransaction::*; + match self { + Legacy(inner) => { + encoded.extend_from_slice(&[0x0]); + encoded.extend_from_slice(inner.rlp(chain_id).as_ref()); + } + Eip2930(inner) => { + encoded.extend_from_slice(&[0x1]); + encoded.extend_from_slice(inner.rlp(chain_id).as_ref()); + } + Eip1559(inner) => { + encoded.extend_from_slice(&[0x2]); + encoded.extend_from_slice(inner.rlp(chain_id).as_ref()); + } + }; + + encoded.into() + } + + /// Hashes the transaction's data with the provided chain id + /// Does not double-RLP encode + pub fn sighash>(&self, chain_id: T) -> H256 { + let encoded = self.rlp(chain_id); + keccak256(encoded).into() + } +} + +impl From for TypedTransaction { + fn from(src: TransactionRequest) -> TypedTransaction { + TypedTransaction::Legacy(src) + } +} + +impl From for TypedTransaction { + fn from(src: Eip2930TransactionRequest) -> TypedTransaction { + TypedTransaction::Eip2930(src) + } +} + +impl From for TypedTransaction { + fn from(src: Eip1559TransactionRequest) -> TypedTransaction { + TypedTransaction::Eip1559(src) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::{Address, U256}; + + #[test] + fn serde_legacy_tx() { + let tx = TransactionRequest::new() + .to(Address::zero()) + .value(U256::from(100)); + let tx: TypedTransaction = tx.into(); + let serialized = serde_json::to_string(&tx).unwrap(); + + // deserializes to either the envelope type or the inner type + let de: TypedTransaction = serde_json::from_str(&serialized).unwrap(); + assert_eq!(tx, de); + + let de: TransactionRequest = serde_json::from_str(&serialized).unwrap(); + assert_eq!(tx, TypedTransaction::Legacy(de)); + } +} diff --git a/ethers-core/src/types/transaction/eip2930.rs b/ethers-core/src/types/transaction/eip2930.rs index ae0c7419..b8577d69 100644 --- a/ethers-core/src/types/transaction/eip2930.rs +++ b/ethers-core/src/types/transaction/eip2930.rs @@ -1,10 +1,16 @@ -use crate::types::{Address, H256}; +use super::request::TransactionRequest; +use crate::types::{Address, Bytes, Signature, H256, U64}; -use rlp_derive::RlpEncodable; +use rlp::RlpStream; +use rlp_derive::{RlpEncodable, RlpEncodableWrapper}; use serde::{Deserialize, Serialize}; +const NUM_EIP2930_FIELDS: usize = 8; + /// Access list -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, RlpEncodable)] +// NB: Need to use `RlpEncodableWrapper` else we get an extra [] in the output +// https://github.com/gakonst/ethers-rs/pull/353#discussion_r680683869 +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, RlpEncodableWrapper)] pub struct AccessList(pub Vec); impl From> for AccessList { @@ -13,8 +19,19 @@ impl From> for AccessList { } } +impl TransactionRequest { + /// Sets the `access_list` field in the transaction (converts the [`TransactionRequest`] to + /// an [`Eip2930TransactionRequest`]) + pub fn with_access_list>( + self, + access_list: T, + ) -> Eip2930TransactionRequest { + Eip2930TransactionRequest::new(self, access_list.into()) + } +} + /// Access list item -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, RlpEncodable)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, RlpEncodable)] #[serde(rename_all = "camelCase")] pub struct AccessListItem { /// Accessed address @@ -22,3 +39,103 @@ pub struct AccessListItem { /// Accessed storage keys pub storage_keys: Vec, } + +/// An EIP-2930 transaction is a legacy transaction including an [`AccessList`]. +#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)] +pub struct Eip2930TransactionRequest { + #[serde(flatten)] + pub tx: TransactionRequest, + pub access_list: AccessList, +} + +impl Eip2930TransactionRequest { + pub fn new(tx: TransactionRequest, access_list: AccessList) -> Self { + Self { tx, access_list } + } + + pub fn rlp>(&self, chain_id: T) -> Bytes { + let mut rlp = RlpStream::new(); + rlp.begin_list(NUM_EIP2930_FIELDS); + rlp.append(&chain_id.into()); + self.tx.rlp_base(&mut rlp); + // append the access list in addition to the base rlp encoding + rlp.append(&self.access_list); + + rlp.out().freeze().into() + } + + /// Produces the RLP encoding of the transaction with the provided signature + pub fn rlp_signed>(&self, chain_id: T, signature: &Signature) -> Bytes { + let mut rlp = RlpStream::new(); + rlp.begin_list(NUM_EIP2930_FIELDS + 3); + + rlp.append(&chain_id.into()); + self.tx.rlp_base(&mut rlp); + // append the access list in addition to the base rlp encoding + rlp.append(&self.access_list); + + // append the signature + rlp.append(&signature.v); + rlp.append(&signature.r); + rlp.append(&signature.s); + rlp.out().freeze().into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::{transaction::eip2718::TypedTransaction, U256}; + + #[test] + // https://github.com/ethereum/go-ethereum/blob/c503f98f6d5e80e079c1d8a3601d188af2a899da/core/types/transaction_test.go#L59-L67 + fn rlp() { + let tx: TypedTransaction = TransactionRequest::new() + .nonce(3) + .gas_price(1) + .gas(25000) + .to("b94f5374fce5edbc8e2a8697c15331677e6ebf0b" + .parse::
() + .unwrap()) + .value(10) + .data(vec![0x55, 0x44]) + .with_access_list(vec![]) + .into(); + + let hash = tx.sighash(1); + let sig: Signature = "c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101".parse().unwrap(); + let enc = tx.rlp_signed(1, &sig); + + assert_eq!( + hash, + "49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3" + .parse() + .unwrap() + ); + + let expected = "b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521"; + assert_eq!(hex::encode(enc.to_vec()), expected); + } + + #[test] + fn serde_eip2930_tx() { + let access_list = vec![AccessListItem { + address: Address::zero(), + storage_keys: vec![H256::zero()], + }]; + let tx = TransactionRequest::new() + .to(Address::zero()) + .value(U256::from(100)) + .with_access_list(access_list); + let tx: TypedTransaction = tx.into(); + let serialized = serde_json::to_string(&tx).unwrap(); + dbg!(&serialized); + + // deserializes to either the envelope type or the inner type + let de: TypedTransaction = serde_json::from_str(&serialized).unwrap(); + assert_eq!(tx, de); + + let de: Eip2930TransactionRequest = serde_json::from_str(&serialized).unwrap(); + assert_eq!(tx, TypedTransaction::Eip2930(de)); + } +} diff --git a/ethers-core/src/types/transaction/mod.rs b/ethers-core/src/types/transaction/mod.rs index 22f5e8c8..5e95d089 100644 --- a/ethers-core/src/types/transaction/mod.rs +++ b/ethers-core/src/types/transaction/mod.rs @@ -1,6 +1,8 @@ pub mod request; pub mod response; +pub mod eip1559; +pub mod eip2718; pub mod eip2930; pub(crate) const BASE_NUM_TX_FIELDS: usize = 9; @@ -12,8 +14,8 @@ pub(crate) const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS; #[cfg(feature = "celo")] pub(crate) const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS + 3; -pub(super) fn rlp_opt(rlp: &mut rlp::RlpStream, opt: Option) { - if let Some(ref inner) = opt { +pub(super) fn rlp_opt(rlp: &mut rlp::RlpStream, opt: &Option) { + if let Some(inner) = opt { rlp.append(inner); } else { rlp.append(&""); diff --git a/ethers-core/src/types/transaction/request.rs b/ethers-core/src/types/transaction/request.rs index 7dd1e1e2..e71f5fc9 100644 --- a/ethers-core/src/types/transaction/request.rs +++ b/ethers-core/src/types/transaction/request.rs @@ -153,16 +153,16 @@ impl TransactionRequest { } pub(crate) fn rlp_base(&self, rlp: &mut RlpStream) { - rlp_opt(rlp, self.nonce); - rlp_opt(rlp, self.gas_price); - rlp_opt(rlp, self.gas); + rlp_opt(rlp, &self.nonce); + rlp_opt(rlp, &self.gas_price); + rlp_opt(rlp, &self.gas); #[cfg(feature = "celo")] self.inject_celo_metadata(rlp); - rlp_opt(rlp, self.to.as_ref()); - rlp_opt(rlp, self.value); - rlp_opt(rlp, self.data.as_ref().map(|d| d.as_ref())); + rlp_opt(rlp, &self.to.as_ref()); + rlp_opt(rlp, &self.value); + rlp_opt(rlp, &self.data.as_ref().map(|d| d.as_ref())); } } @@ -171,9 +171,9 @@ impl TransactionRequest { impl TransactionRequest { // modifies the RLP stream with the Celo-specific information fn inject_celo_metadata(&self, rlp: &mut RlpStream) { - rlp_opt(rlp, self.fee_currency); - rlp_opt(rlp, self.gateway_fee_recipient); - rlp_opt(rlp, self.gateway_fee); + rlp_opt(rlp, &self.fee_currency); + rlp_opt(rlp, &self.gateway_fee_recipient); + rlp_opt(rlp, &self.gateway_fee); } /// Sets the `fee_currency` field in the transaction to the provided value diff --git a/ethers-core/src/types/transaction/response.rs b/ethers-core/src/types/transaction/response.rs index 85222d48..9c427acd 100644 --- a/ethers-core/src/types/transaction/response.rs +++ b/ethers-core/src/types/transaction/response.rs @@ -87,7 +87,8 @@ pub struct Transaction { pub gateway_fee: Option, // EIP2718 - /// Transaction type, Some(1) for AccessList transaction, None for Legacy + /// Transaction type, Some(2) for EIP-1559 transaction, + /// Some(1) for AccessList transaction, None for Legacy #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] pub transaction_type: Option, @@ -130,9 +131,9 @@ impl Transaction { // of this code duplication? #[cfg(feature = "celo")] fn inject_celo_metadata(&self, rlp: &mut RlpStream) { - rlp_opt(rlp, self.fee_currency); - rlp_opt(rlp, self.gateway_fee_recipient); - rlp_opt(rlp, self.gateway_fee); + rlp_opt(rlp, &self.fee_currency); + rlp_opt(rlp, &self.gateway_fee_recipient); + rlp_opt(rlp, &self.gateway_fee); } pub fn hash(&self) -> H256 { @@ -149,7 +150,7 @@ impl Transaction { #[cfg(feature = "celo")] self.inject_celo_metadata(&mut rlp); - rlp_opt(&mut rlp, self.to); + rlp_opt(&mut rlp, &self.to); rlp.append(&self.value); rlp.append(&self.input.as_ref()); rlp.append(&self.v); diff --git a/ethers-middleware/tests/gas_oracle.rs b/ethers-middleware/tests/gas_oracle.rs index 8366abfd..ae79f461 100644 --- a/ethers-middleware/tests/gas_oracle.rs +++ b/ethers-middleware/tests/gas_oracle.rs @@ -15,7 +15,7 @@ async fn using_gas_oracle() { let provider = Provider::::try_from(ganache.endpoint()).unwrap(); // assign a gas oracle to use - let gas_oracle = Etherchain::new().category(GasCategory::Fastest); + let gas_oracle = GasNow::new().category(GasCategory::Fastest); let expected_gas_price = gas_oracle.fetch().await.unwrap(); let provider = GasOracleMiddleware::new(provider, gas_oracle); @@ -32,6 +32,7 @@ async fn using_gas_oracle() { } #[tokio::test] +#[ignore] async fn eth_gas_station() { // initialize and fetch gas estimates from EthGasStation let eth_gas_station_oracle = EthGasStation::new(None); @@ -58,6 +59,7 @@ async fn etherscan() { } #[tokio::test] +#[ignore] async fn etherchain() { // initialize and fetch gas estimates from Etherchain let etherchain_oracle = Etherchain::new().category(GasCategory::Fast);