feat: eip2930/1559 response type adjustments (part 1) (#353)

* refactor: move tx types to subdirectory

* feat(receipt): add tx-type and effective gas price for eip1559

* feat: add base_fee_per_gas to Block

* feat(tx): add eip1559/2930/2718 ret values

* fix(middleware): return signed raw rlp from tx signer / adjust tests

* chore: remove unused vars

* chore(core): remove funty lock from Cargo.toml

* chore: rename json var / cargo fmt

* add more context on eip1559 fees
This commit is contained in:
Georgios Konstantopoulos 2021-08-09 01:49:23 +03:00 committed by GitHub
parent f165c13009
commit 09ff480f19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 438 additions and 301 deletions

13
Cargo.lock generated
View File

@ -875,7 +875,6 @@ dependencies = [
"elliptic-curve", "elliptic-curve",
"ethabi", "ethabi",
"ethers", "ethers",
"funty",
"futures-util", "futures-util",
"generic-array 0.14.4", "generic-array 0.14.4",
"glob", "glob",
@ -885,6 +884,7 @@ dependencies = [
"once_cell", "once_cell",
"rand 0.8.4", "rand 0.8.4",
"rlp", "rlp",
"rlp-derive",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -2124,6 +2124,17 @@ dependencies = [
"rustc-hex", "rustc-hex",
] ]
[[package]]
name = "rlp-derive"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "rusb" name = "rusb"
version = "0.8.1" version = "0.8.1"

View File

@ -13,6 +13,7 @@ keywords = ["ethereum", "web3", "celo", "ethers"]
rlp = { version = "0.5.0", default-features = false } rlp = { version = "0.5.0", default-features = false }
ethabi = { version = "14.1.0", default-features = false } ethabi = { version = "14.1.0", default-features = false }
arrayvec = { version = "0.7.1", default-features = false } arrayvec = { version = "0.7.1", default-features = false }
rlp-derive = { version = "0.1.0", default-features = false }
# crypto # crypto
ecdsa = { version = "0.12.3", default-features = false, features = ["std"] } ecdsa = { version = "0.12.3", default-features = false, features = ["std"] }
@ -30,10 +31,6 @@ glob = { version = "0.3.0", default-features = false }
bytes = { version = "1.0.1", features = ["serde"] } bytes = { version = "1.0.1", features = ["serde"] }
hex = { version = "0.4.3", default-features = false, features = ["std"] } hex = { version = "0.4.3", default-features = false, features = ["std"] }
# bitvec compilation issue
# https://github.com/bitvecto-rs/bitvec/issues/105#issuecomment-778570981
funty = "=1.1.0"
# async # async
tokio = { version = "1.5", default-features = false, optional = true} tokio = { version = "1.5", default-features = false, optional = true}
futures-util = { version = "0.3.16", default-features = false, optional = true} futures-util = { version = "0.3.16", default-features = false, optional = true}

View File

@ -71,6 +71,9 @@ pub struct Block<TX> {
/// Nonce /// Nonce
#[cfg(not(feature = "celo"))] #[cfg(not(feature = "celo"))]
pub nonce: Option<U64>, pub nonce: Option<U64>,
/// Base fee per unit of gas (if past London)
#[serde(rename = "baseFeePerGas")]
pub base_fee_per_gas: Option<U256>,
#[cfg(feature = "celo")] #[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))] #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
@ -206,6 +209,44 @@ mod tests {
let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#;
let _block: Block<Transaction> = serde_json::from_str(block).unwrap(); let _block: Block<Transaction> = serde_json::from_str(block).unwrap();
} }
#[test]
// https://github.com/tomusdrw/rust-web3/commit/3a32ee962c0f2f8d50a5e25be9f2dfec7ae0750d
fn post_london_block() {
let json = serde_json::json!(
{
"baseFeePerGas": "0x7",
"miner": "0x0000000000000000000000000000000000000001",
"number": "0x1b4",
"hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
"parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5",
"mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010",
"nonce": "0x0000000000000000",
"sealFields": [
"0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2",
"0x0000000000000042"
],
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"logsBloom": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
"transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff",
"difficulty": "0x27f07",
"totalDifficulty": "0x27f07",
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
"size": "0x27f07",
"gasLimit": "0x9f759",
"minGasPrice": "0x9f759",
"gasUsed": "0x9f759",
"timestamp": "0x54e34e8e",
"transactions": [],
"uncles": []
}
);
let block: Block<()> = serde_json::from_value(json).unwrap();
assert_eq!(block.base_fee_per_gas, Some(U256::from(7)));
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -8,7 +8,8 @@ pub use ethabi::ethereum_types::H256 as TxHash;
pub use ethabi::ethereum_types::{Address, Bloom, H160, H256, U128, U256, U64}; pub use ethabi::ethereum_types::{Address, Bloom, H160, H256, U128, U256, U64};
mod transaction; mod transaction;
pub use transaction::{Transaction, TransactionReceipt, TransactionRequest}; pub use transaction::request::TransactionRequest;
pub use transaction::response::{Transaction, TransactionReceipt};
mod address_or_bytes; mod address_or_bytes;
pub use address_or_bytes::AddressOrBytes; pub use address_or_bytes::AddressOrBytes;

View File

@ -0,0 +1,24 @@
use crate::types::{Address, H256};
use rlp_derive::RlpEncodable;
use serde::{Deserialize, Serialize};
/// Access list
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, RlpEncodable)]
pub struct AccessList(pub Vec<AccessListItem>);
impl From<Vec<AccessListItem>> for AccessList {
fn from(src: Vec<AccessListItem>) -> AccessList {
AccessList(src)
}
}
/// Access list item
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, RlpEncodable)]
#[serde(rename_all = "camelCase")]
pub struct AccessListItem {
/// Accessed address
pub address: Address,
/// Accessed storage keys
pub storage_keys: Vec<H256>,
}

View File

@ -0,0 +1,21 @@
pub mod request;
pub mod response;
pub mod eip2930;
pub(crate) const BASE_NUM_TX_FIELDS: usize = 9;
// Number of tx fields before signing
#[cfg(not(feature = "celo"))]
pub(crate) const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS;
// Celo has 3 additional 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 {
rlp.append(inner);
} else {
rlp.append(&"");
}
}

View File

@ -1,21 +1,13 @@
//! Transaction types //! Transaction types
use super::{rlp_opt, NUM_TX_FIELDS};
use crate::{ use crate::{
types::{Address, Bloom, Bytes, Log, NameOrAddress, Signature, H256, U256, U64}, types::{Address, Bytes, NameOrAddress, Signature, H256, U256, U64},
utils::keccak256, utils::keccak256,
}; };
use rlp::RlpStream; use rlp::RlpStream;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
const BASE_NUM_TX_FIELDS: usize = 9;
// Number of tx fields before signing
#[cfg(not(feature = "celo"))]
const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS;
// Celo has 3 additional fields
#[cfg(feature = "celo")]
const NUM_TX_FIELDS: usize = BASE_NUM_TX_FIELDS + 3;
/// Parameters for sending a transaction /// Parameters for sending a transaction
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)] #[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
pub struct TransactionRequest { pub struct TransactionRequest {
@ -160,7 +152,7 @@ impl TransactionRequest {
rlp.out().freeze().into() rlp.out().freeze().into()
} }
fn rlp_base(&self, rlp: &mut RlpStream) { pub(crate) fn rlp_base(&self, rlp: &mut RlpStream) {
rlp_opt(rlp, self.nonce); rlp_opt(rlp, self.nonce);
rlp_opt(rlp, self.gas_price); rlp_opt(rlp, self.gas_price);
rlp_opt(rlp, self.gas); rlp_opt(rlp, self.gas);
@ -206,163 +198,6 @@ impl TransactionRequest {
} }
} }
fn rlp_opt<T: rlp::Encodable>(rlp: &mut RlpStream, opt: Option<T>) {
if let Some(ref inner) = opt {
rlp.append(inner);
} else {
rlp.append(&"");
}
}
/// Details of a signed transaction
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct Transaction {
/// The transaction's hash
pub hash: H256,
/// The transaction's nonce
pub nonce: U256,
/// Block hash. None when pending.
#[serde(rename = "blockHash")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_hash: Option<H256>,
/// Block number. None when pending.
#[serde(rename = "blockNumber")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_number: Option<U64>,
/// Transaction Index. None when pending.
#[serde(rename = "transactionIndex")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_index: Option<U64>,
/// Sender
pub from: Address,
/// Recipient (None when contract creation)
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<Address>,
/// Transfered value
pub value: U256,
/// Gas Price
#[serde(rename = "gasPrice")]
pub gas_price: U256,
/// Gas amount
pub gas: U256,
/// Input data
pub input: Bytes,
/// ECDSA recovery id
pub v: U64,
/// ECDSA signature r
pub r: U256,
/// ECDSA signature s
pub s: U256,
///////////////// Celo-specific transaction fields /////////////////
/// The currency fees are paid in (None for native currency)
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(skip_serializing_if = "Option::is_none", rename = "feeCurrency")]
pub fee_currency: Option<Address>,
/// Gateway fee recipient (None for no gateway fee paid)
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(
skip_serializing_if = "Option::is_none",
rename = "gatewayFeeRecipient"
)]
pub gateway_fee_recipient: Option<Address>,
/// Gateway fee amount (None for no gateway fee paid)
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(skip_serializing_if = "Option::is_none", rename = "gatewayFee")]
pub gateway_fee: Option<U256>,
}
impl Transaction {
// modifies the RLP stream with the Celo-specific information
// This is duplicated from TransactionRequest. Is there a good way to get rid
// 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);
}
pub fn hash(&self) -> H256 {
keccak256(&self.rlp().as_ref()).into()
}
pub fn rlp(&self) -> Bytes {
let mut rlp = RlpStream::new();
rlp.begin_list(NUM_TX_FIELDS);
rlp.append(&self.nonce);
rlp.append(&self.gas_price);
rlp.append(&self.gas);
#[cfg(feature = "celo")]
self.inject_celo_metadata(&mut rlp);
rlp_opt(&mut rlp, self.to);
rlp.append(&self.value);
rlp.append(&self.input.as_ref());
rlp.append(&self.v);
rlp.append(&self.r);
rlp.append(&self.s);
rlp.out().freeze().into()
}
}
/// "Receipt" of an executed transaction: details of its execution.
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct TransactionReceipt {
/// Transaction hash.
#[serde(rename = "transactionHash")]
pub transaction_hash: H256,
/// Index within the block.
#[serde(rename = "transactionIndex")]
pub transaction_index: U64,
/// Hash of the block this transaction was included within.
#[serde(rename = "blockHash")]
pub block_hash: Option<H256>,
/// Number of the block this transaction was included within.
#[serde(rename = "blockNumber")]
pub block_number: Option<U64>,
/// Cumulative gas used within the block after this was executed.
#[serde(rename = "cumulativeGasUsed")]
pub cumulative_gas_used: U256,
/// Gas used by this transaction alone.
///
/// Gas used is `None` if the the client is running in light client mode.
#[serde(rename = "gasUsed")]
pub gas_used: Option<U256>,
/// Contract address created, or `None` if not a deployment.
#[serde(rename = "contractAddress")]
pub contract_address: Option<Address>,
/// Logs generated within this transaction.
pub logs: Vec<Log>,
/// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
pub status: Option<U64>,
/// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
pub root: Option<H256>,
/// Logs bloom
#[serde(rename = "logsBloom")]
pub logs_bloom: Bloom,
}
#[cfg(test)] #[cfg(test)]
#[cfg(not(feature = "celo"))] #[cfg(not(feature = "celo"))]
mod tests { mod tests {
@ -385,47 +220,4 @@ mod tests {
) )
.unwrap(); .unwrap();
} }
#[test]
fn decode_transaction_response() {
let _res: Transaction = serde_json::from_str(
r#"{
"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2",
"blockNumber":"0x5daf3b",
"from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
"gas":"0xc350",
"gasPrice":"0x4a817c800",
"hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
"input":"0x68656c6c6f21",
"nonce":"0x15",
"to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
"transactionIndex":"0x41",
"value":"0xf3dbb76162000",
"v":"0x25",
"r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
"s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c"
}"#,
)
.unwrap();
let _res: Transaction = serde_json::from_str(
r#"{
"hash":"0xdd79ab0f996150aa3c9f135bbb9272cf0dedb830fafcbbf0c06020503565c44f",
"nonce":"0xe",
"blockHash":"0xef3fe1f532c3d8783a6257619bc123e9453aa8d6614e4cdb4cc8b9e1ed861404",
"blockNumber":"0xf",
"transactionIndex":"0x0",
"from":"0x1b67b03cdccfae10a2d80e52d3d026dbe2960ad0",
"to":"0x986ee0c8b91a58e490ee59718cca41056cf55f24",
"value":"0x2710",
"gas":"0x5208",
"gasPrice":"0x186a0",
"input":"0x",
"v":"0x25",
"r":"0x75188beb2f601bb8cf52ef89f92a6ba2bb7edcf8e3ccde90548cc99cbea30b1e",
"s":"0xc0559a540f16d031f3404d5df2bb258084eee56ed1193d8b534bb6affdb3c2c"
}"#,
)
.unwrap();
}
} }

View File

@ -0,0 +1,290 @@
//! Transaction types
use super::{eip2930::AccessList, rlp_opt, NUM_TX_FIELDS};
use crate::{
types::{Address, Bloom, Bytes, Log, H256, U256, U64},
utils::keccak256,
};
use rlp::RlpStream;
use serde::{Deserialize, Serialize};
/// Details of a signed transaction
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct Transaction {
/// The transaction's hash
pub hash: H256,
/// The transaction's nonce
pub nonce: U256,
/// Block hash. None when pending.
#[serde(rename = "blockHash")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_hash: Option<H256>,
/// Block number. None when pending.
#[serde(rename = "blockNumber")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_number: Option<U64>,
/// Transaction Index. None when pending.
#[serde(rename = "transactionIndex")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_index: Option<U64>,
/// Sender
pub from: Address,
/// Recipient (None when contract creation)
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<Address>,
/// Transfered value
pub value: U256,
/// Gas Price
#[serde(rename = "gasPrice")]
// TODO: Should this be deprecated?
// https://twitter.com/TimBeiko/status/1413536062429794304
// If yes, how will we handle pre-calculating the transaction's hash keccak256(rlp(tx)),
// given that it contains the gas price?
pub gas_price: U256,
/// Gas amount
pub gas: U256,
/// Input data
pub input: Bytes,
/// ECDSA recovery id
pub v: U64,
/// ECDSA signature r
pub r: U256,
/// ECDSA signature s
pub s: U256,
///////////////// Celo-specific transaction fields /////////////////
/// The currency fees are paid in (None for native currency)
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(skip_serializing_if = "Option::is_none", rename = "feeCurrency")]
pub fee_currency: Option<Address>,
/// Gateway fee recipient (None for no gateway fee paid)
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(
skip_serializing_if = "Option::is_none",
rename = "gatewayFeeRecipient"
)]
pub gateway_fee_recipient: Option<Address>,
/// Gateway fee amount (None for no gateway fee paid)
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(skip_serializing_if = "Option::is_none", rename = "gatewayFee")]
pub gateway_fee: Option<U256>,
// EIP2718
/// Transaction type, Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<U64>,
// EIP2930
#[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 Transaction {
// modifies the RLP stream with the Celo-specific information
// This is duplicated from TransactionRequest. Is there a good way to get rid
// 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);
}
pub fn hash(&self) -> H256 {
keccak256(&self.rlp().as_ref()).into()
}
pub fn rlp(&self) -> Bytes {
let mut rlp = RlpStream::new();
rlp.begin_list(NUM_TX_FIELDS);
rlp.append(&self.nonce);
rlp.append(&self.gas_price);
rlp.append(&self.gas);
#[cfg(feature = "celo")]
self.inject_celo_metadata(&mut rlp);
rlp_opt(&mut rlp, self.to);
rlp.append(&self.value);
rlp.append(&self.input.as_ref());
rlp.append(&self.v);
rlp.append(&self.r);
rlp.append(&self.s);
rlp.out().freeze().into()
}
}
/// "Receipt" of an executed transaction: details of its execution.
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct TransactionReceipt {
/// Transaction hash.
#[serde(rename = "transactionHash")]
pub transaction_hash: H256,
/// Index within the block.
#[serde(rename = "transactionIndex")]
pub transaction_index: U64,
/// Hash of the block this transaction was included within.
#[serde(rename = "blockHash")]
pub block_hash: Option<H256>,
/// Number of the block this transaction was included within.
#[serde(rename = "blockNumber")]
pub block_number: Option<U64>,
/// Cumulative gas used within the block after this was executed.
#[serde(rename = "cumulativeGasUsed")]
pub cumulative_gas_used: U256,
/// Gas used by this transaction alone.
///
/// Gas used is `None` if the the client is running in light client mode.
#[serde(rename = "gasUsed")]
pub gas_used: Option<U256>,
/// Contract address created, or `None` if not a deployment.
#[serde(rename = "contractAddress")]
pub contract_address: Option<Address>,
/// Logs generated within this transaction.
pub logs: Vec<Log>,
/// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
pub status: Option<U64>,
/// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
pub root: Option<H256>,
/// Logs bloom
#[serde(rename = "logsBloom")]
pub logs_bloom: Bloom,
/// Transaction type, Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<U64>,
/// The price paid post-execution by the transaction (i.e. base fee + priority fee).
/// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the amount
/// that's actually paid by users can only be determined post-execution
#[serde(
rename = "effectiveGasPrice",
default,
skip_serializing_if = "Option::is_none"
)]
pub effective_gas_price: Option<U256>,
}
#[cfg(test)]
#[cfg(not(feature = "celo"))]
mod tests {
use crate::types::transaction::eip2930::AccessListItem;
use super::*;
#[test]
fn decode_transaction_response() {
let _res: Transaction = serde_json::from_str(
r#"{
"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2",
"blockNumber":"0x5daf3b",
"from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
"gas":"0xc350",
"gasPrice":"0x4a817c800",
"hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
"input":"0x68656c6c6f21",
"nonce":"0x15",
"to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
"transactionIndex":"0x41",
"value":"0xf3dbb76162000",
"v":"0x25",
"r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
"s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c"
}"#,
)
.unwrap();
let _res: Transaction = serde_json::from_str(
r#"{
"hash":"0xdd79ab0f996150aa3c9f135bbb9272cf0dedb830fafcbbf0c06020503565c44f",
"nonce":"0xe",
"blockHash":"0xef3fe1f532c3d8783a6257619bc123e9453aa8d6614e4cdb4cc8b9e1ed861404",
"blockNumber":"0xf",
"transactionIndex":"0x0",
"from":"0x1b67b03cdccfae10a2d80e52d3d026dbe2960ad0",
"to":"0x986ee0c8b91a58e490ee59718cca41056cf55f24",
"value":"0x2710",
"gas":"0x5208",
"gasPrice":"0x186a0",
"input":"0x",
"v":"0x25",
"r":"0x75188beb2f601bb8cf52ef89f92a6ba2bb7edcf8e3ccde90548cc99cbea30b1e",
"s":"0xc0559a540f16d031f3404d5df2bb258084eee56ed1193d8b534bb6affdb3c2c"
}"#,
)
.unwrap();
}
#[test]
fn decode_london_receipt() {
let receipt: TransactionReceipt = serde_json::from_value(serde_json::json!({"blockHash":"0x55ae43d3511e327dc532855510d110676d340aa1bbba369b4b98896d86559586","blockNumber":"0xa3d322","contractAddress":null,"cumulativeGasUsed":"0x207a5b","effectiveGasPrice":"0x3b9aca07","from":"0x541d6a0e9ca9e7a083e41e2e178eef9f22d7492e","gasUsed":"0x6a40","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":"0x8210357f377e901f18e45294e86a2a32215cc3c9","transactionHash":"0x824384376c5972498c6fcafe71fd8cad1689f64e7d5e270d025a898638c0c34d","transactionIndex":"0xd","type":"0x2"})).unwrap();
assert_eq!(receipt.transaction_type.unwrap().as_u64(), 2);
assert_eq!(receipt.effective_gas_price.unwrap().as_u64(), 0x3b9aca07);
}
#[test]
fn decode_london_tx() {
let tx: Transaction = serde_json::from_value(serde_json::json!({"accessList":[{"address":"0x8ba1f109551bd432803012645ac136ddd64dba72","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000042"]}],"blockHash":"0x55ae43d3511e327dc532855510d110676d340aa1bbba369b4b98896d86559586","blockNumber":"0xa3d322","chainId":"0x3","from":"0x541d6a0e9ca9e7a083e41e2e178eef9f22d7492e","gas":"0x6a40","gasPrice":"0x3b9aca07","hash":"0x824384376c5972498c6fcafe71fd8cad1689f64e7d5e270d025a898638c0c34d","input":"0x","maxFeePerGas":"0x3b9aca0e","maxPriorityFeePerGas":"0x3b9aca00","nonce":"0x2","r":"0xf13b5088108f783f4b6048d4be456971118aabfb88be96bb541d734b6c2b20dc","s":"0x13fb7eb25a7d5df42a176cd4c6a086e19163ed7cd8ffba015f939d24f66bc17a","to":"0x8210357f377e901f18e45294e86a2a32215cc3c9","transactionIndex":"0xd","type":"0x2","v":"0x1","value":"0x7b"})).unwrap();
assert_eq!(tx.transaction_type.unwrap().as_u64(), 2);
let lst = AccessList(vec![AccessListItem {
address: "0x8ba1f109551bd432803012645ac136ddd64dba72"
.parse()
.unwrap(),
storage_keys: vec![
"0x0000000000000000000000000000000000000000000000000000000000000000"
.parse()
.unwrap(),
"0x0000000000000000000000000000000000000000000000000000000000000042"
.parse()
.unwrap(),
],
}]);
assert_eq!(tx.access_list.unwrap(), lst);
assert_eq!(tx.max_fee_per_gas.unwrap().as_u64(), 0x3b9aca0e);
assert_eq!(tx.max_priority_fee_per_gas.unwrap().as_u64(), 0x3b9aca00);
}
}

View File

@ -1,9 +1,4 @@
use ethers_core::{ use ethers_core::types::{Address, BlockId, Bytes, NameOrAddress, Signature, TransactionRequest};
types::{
Address, BlockId, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest, U256,
},
utils::keccak256,
};
use ethers_providers::{FromErr, Middleware, PendingTransaction}; use ethers_providers::{FromErr, Middleware, PendingTransaction};
use ethers_signers::Signer; use ethers_signers::Signer;
@ -120,64 +115,19 @@ where
} }
} }
/// Signs and returns the RLP encoding of the signed transaction
async fn sign_transaction( async fn sign_transaction(
&self, &self,
tx: TransactionRequest, tx: TransactionRequest,
) -> Result<Transaction, SignerMiddlewareError<M, S>> { ) -> Result<Bytes, SignerMiddlewareError<M, S>> {
// The nonce, gas and gasprice fields must already be populated
let nonce = tx.nonce.ok_or(SignerMiddlewareError::NonceMissing)?;
let gas_price = tx.gas_price.ok_or(SignerMiddlewareError::GasPriceMissing)?;
let gas = tx.gas.ok_or(SignerMiddlewareError::GasMissing)?;
// Can't sign a transaction from a different address
if tx.from.is_some() && tx.from != Some(self.address()) {
return Err(SignerMiddlewareError::WrongSigner);
}
let signature = self let signature = self
.signer .signer
.sign_transaction(&tx) .sign_transaction(&tx)
.await .await
.map_err(SignerMiddlewareError::SignerError)?; .map_err(SignerMiddlewareError::SignerError)?;
// Get the actual transaction hash // Return the raw rlp-encoded signed transaction
let rlp = tx.rlp_signed(&signature); Ok(tx.rlp_signed(&signature))
let hash = keccak256(&rlp.as_ref());
// This function should not be called with ENS names
let to = tx.to.map(|to| match to {
NameOrAddress::Address(inner) => inner,
NameOrAddress::Name(_) => {
panic!("Expected `to` to be an Ethereum Address, not an ENS name")
}
});
Ok(Transaction {
hash: hash.into(),
nonce,
from: self.address(),
to,
value: tx.value.unwrap_or_default(),
gas_price,
gas,
input: tx.data.unwrap_or_default(),
v: signature.v.into(),
r: U256::from_big_endian(signature.r.as_bytes()),
s: U256::from_big_endian(signature.s.as_bytes()),
// Leave these empty as they're only used for included transactions
block_hash: None,
block_number: None,
transaction_index: None,
// Celo support
#[cfg(feature = "celo")]
fee_currency: tx.fee_currency,
#[cfg(feature = "celo")]
gateway_fee: tx.gateway_fee,
#[cfg(feature = "celo")]
gateway_fee_recipient: tx.gateway_fee_recipient,
})
} }
async fn fill_transaction( async fn fill_transaction(
@ -282,7 +232,7 @@ where
// Submit the raw transaction // Submit the raw transaction
self.inner self.inner
.send_raw_transaction(&signed_tx) .send_raw_transaction(signed_tx)
.await .await
.map_err(SignerMiddlewareError::MiddlewareError) .map_err(SignerMiddlewareError::MiddlewareError)
} }
@ -317,6 +267,7 @@ where
mod tests { mod tests {
use super::*; use super::*;
use ethers::{providers::Provider, signers::LocalWallet}; use ethers::{providers::Provider, signers::LocalWallet};
use ethers_core::utils::{self, keccak256, Ganache};
use std::convert::TryFrom; use std::convert::TryFrom;
#[tokio::test] #[tokio::test]
@ -349,51 +300,60 @@ mod tests {
let tx = client.sign_transaction(tx).await.unwrap(); let tx = client.sign_transaction(tx).await.unwrap();
assert_eq!( assert_eq!(
tx.hash, keccak256(&tx)[..],
"de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593" hex::decode("de8db924885b0803d2edc335f745b2b8750c8848744905684c20b987443a9593")
.parse()
.unwrap() .unwrap()
); );
let expected_rlp = Bytes::from(hex::decode("f869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68").unwrap()); let expected_rlp = Bytes::from(hex::decode("f869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68").unwrap());
assert_eq!(tx.rlp(), expected_rlp); assert_eq!(tx, expected_rlp);
} }
#[tokio::test] #[tokio::test]
async fn handles_tx_from_field() { async fn handles_tx_from_field() {
use ethers_core::types::Address; let ganache = Ganache::new().spawn();
let acc = ganache.addresses()[0];
// new SignerMiddleware let provider = Provider::try_from(ganache.endpoint()).unwrap();
let provider = Provider::try_from("http://localhost:8545").unwrap();
let key = LocalWallet::new(&mut rand::thread_rng()).with_chain_id(1u32); let key = LocalWallet::new(&mut rand::thread_rng()).with_chain_id(1u32);
provider
.send_transaction(
TransactionRequest::pay(key.address(), utils::parse_ether(1u64).unwrap()).from(acc),
None,
)
.await
.unwrap();
let client = SignerMiddleware::new(provider, key); let client = SignerMiddleware::new(provider, key);
// an address that is not the signer address let request = TransactionRequest::new();
let other = "0x863DF6BFa4469f3ead0bE8f9F2AAE51c91A907b4"
.parse::<Address>()
.unwrap();
let request = TransactionRequest::new().nonce(0).gas_price(0).gas(0);
// signing a TransactionRequest with a from field of None should yield // signing a TransactionRequest with a from field of None should yield
// a signed transaction from the signer address // a signed transaction from the signer address
let request_from_none = request.clone(); let request_from_none = request.clone();
let signing_result = client.sign_transaction(request_from_none).await; let hash = *client
.send_transaction(request_from_none, None)
assert_eq!(signing_result.unwrap().from, client.address()); .await
.unwrap();
let tx = client.get_transaction(hash).await.unwrap().unwrap();
assert_eq!(tx.from, client.address());
// signing a TransactionRequest with the signer as the from address // signing a TransactionRequest with the signer as the from address
// should yield a signed transaction from the signer // should yield a signed transaction from the signer
let request_from_signer = request.clone().from(client.address()); let request_from_signer = request.clone().from(client.address());
let signing_result = client.sign_transaction(request_from_signer.clone()).await; let hash = *client
.send_transaction(request_from_signer, None)
assert_eq!(signing_result.unwrap().from, client.address()); .await
.unwrap();
let tx = client.get_transaction(hash).await.unwrap().unwrap();
assert_eq!(tx.from, client.address());
// signing a TransactionRequest with a from address that is not the // signing a TransactionRequest with a from address that is not the
// signer should result in a WrongSigner error // signer should result in the default ganache account being used
let request_from_other = request.from(other); let request_from_other = request.from(acc);
let signing_result = client.sign_transaction(request_from_other.clone()).await; let hash = *client
.send_transaction(request_from_other, None)
assert!(signing_result.is_err()); .await
.unwrap();
let tx = client.get_transaction(hash).await.unwrap().unwrap();
assert_eq!(tx.from, acc);
} }
} }

View File

@ -313,7 +313,7 @@ pub trait Middleware: Sync + Send + Debug {
async fn send_raw_transaction<'a>( async fn send_raw_transaction<'a>(
&'a self, &'a self,
tx: &Transaction, tx: Bytes,
) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> { ) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
self.inner() self.inner()
.send_raw_transaction(tx) .send_raw_transaction(tx)

View File

@ -354,9 +354,9 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
/// This will consume gas from the account that signed the transaction. /// This will consume gas from the account that signed the transaction.
async fn send_raw_transaction<'a>( async fn send_raw_transaction<'a>(
&'a self, &'a self,
tx: &Transaction, tx: Bytes,
) -> Result<PendingTransaction<'a, P>, ProviderError> { ) -> Result<PendingTransaction<'a, P>, ProviderError> {
let rlp = utils::serialize(&tx.rlp()); let rlp = utils::serialize(&tx);
let tx_hash = self.request("eth_sendRawTransaction", [rlp]).await?; let tx_hash = self.request("eth_sendRawTransaction", [rlp]).await?;
Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval())) Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval()))
} }