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
This commit is contained in:
Georgios Konstantopoulos 2021-08-09 01:59:07 +03:00 committed by GitHub
parent 09ff480f19
commit ccff9fc98f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 561 additions and 26 deletions

4
Cargo.lock generated
View File

@ -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",

View File

@ -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;

View File

@ -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<Address>,
/// Recipient address (None for contract creation)
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<NameOrAddress>,
/// Supplied gas (None for sensible default)
#[serde(skip_serializing_if = "Option::is_none")]
pub gas: Option<U256>,
/// Transfered value (None for no transfer)
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<U256>,
/// 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<Bytes>,
/// Transaction nonce (None for next available nonce)
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<U256>,
#[serde(
rename = "accessList",
default,
skip_serializing_if = "Option::is_none"
)]
pub access_list: Option<AccessList>,
#[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<U256>,
#[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<U256>,
}
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<T: Into<Address>>(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<T: Into<NameOrAddress>>(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<T: Into<U256>>(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<T: Into<U256>>(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<T: Into<U256>>(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<T: Into<U256>>(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<T: Into<Bytes>>(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<T: Into<AccessList>>(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<T: Into<U256>>(mut self, nonce: T) -> Self {
self.nonce = Some(nonce.into());
self
}
/// Hashes the transaction's data with the provided chain id
pub fn sighash<T: Into<U64>>(&self, chain_id: T) -> H256 {
keccak256(self.rlp(chain_id).as_ref()).into()
}
/// Gets the unsigned transaction's RLP encoding
pub fn rlp<T: Into<U64>>(&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<T: Into<U64>>(&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<T: Into<U64>>(&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);
}
}

View File

@ -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<T: Into<NameOrAddress>>(&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<T: Into<U256>>(&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<T: Into<U256>>(&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<T: Into<U256>>(&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<T: Into<U256>>(&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<T: Into<U64>>(&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<T: Into<U64>>(&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<T: Into<U64>>(&self, chain_id: T) -> H256 {
let encoded = self.rlp(chain_id);
keccak256(encoded).into()
}
}
impl From<TransactionRequest> for TypedTransaction {
fn from(src: TransactionRequest) -> TypedTransaction {
TypedTransaction::Legacy(src)
}
}
impl From<Eip2930TransactionRequest> for TypedTransaction {
fn from(src: Eip2930TransactionRequest) -> TypedTransaction {
TypedTransaction::Eip2930(src)
}
}
impl From<Eip1559TransactionRequest> 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));
}
}

View File

@ -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<AccessListItem>);
impl From<Vec<AccessListItem>> for AccessList {
@ -13,8 +19,19 @@ impl From<Vec<AccessListItem>> for AccessList {
}
}
impl TransactionRequest {
/// Sets the `access_list` field in the transaction (converts the [`TransactionRequest`] to
/// an [`Eip2930TransactionRequest`])
pub fn with_access_list<T: Into<AccessList>>(
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<H256>,
}
/// 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<T: Into<U64>>(&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<T: Into<U64>>(&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::<Address>()
.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));
}
}

View File

@ -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<T: rlp::Encodable>(rlp: &mut rlp::RlpStream, opt: Option<T>) {
if let Some(ref inner) = opt {
pub(super) fn rlp_opt<T: rlp::Encodable>(rlp: &mut rlp::RlpStream, opt: &Option<T>) {
if let Some(inner) = opt {
rlp.append(inner);
} else {
rlp.append(&"");

View File

@ -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

View File

@ -87,7 +87,8 @@ pub struct Transaction {
pub gateway_fee: Option<U256>,
// 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<U64>,
@ -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);

View File

@ -15,7 +15,7 @@ async fn using_gas_oracle() {
let provider = Provider::<Http>::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);