use super::{decode_to, extract_chain_id, rlp_opt, NUM_TX_FIELDS};
use crate::{
types::{
Address, Bytes, NameOrAddress, Signature, SignatureError, Transaction, H256, U256, U64,
},
utils::keccak256,
};
use rlp::{Decodable, RlpStream};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum RequestError {
#[error(transparent)]
DecodingError(#[from] rlp::DecoderError),
#[error(transparent)]
RecoveryError(#[from] SignatureError),
}
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
pub struct TransactionRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<Address>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<NameOrAddress>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gas: Option<U256>,
#[serde(rename = "gasPrice")]
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_price: Option<U256>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<U256>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Bytes>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<U256>,
#[serde(skip_serializing)]
#[serde(default, rename = "chainId")]
pub chain_id: Option<U64>,
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_currency: Option<Address>,
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway_fee_recipient: Option<Address>,
#[cfg(feature = "celo")]
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway_fee: Option<U256>,
}
impl TransactionRequest {
pub fn new() -> Self {
Self::default()
}
pub fn pay<T: Into<NameOrAddress>, V: Into<U256>>(to: T, value: V) -> Self {
TransactionRequest { to: Some(to.into()), value: Some(value.into()), ..Default::default() }
}
#[must_use]
pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
self.from = Some(from.into());
self
}
#[must_use]
pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
self.to = Some(to.into());
self
}
#[must_use]
pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
self.gas = Some(gas.into());
self
}
#[must_use]
pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
self.gas_price = Some(gas_price.into());
self
}
#[must_use]
pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
self.value = Some(value.into());
self
}
#[must_use]
pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
self.data = Some(data.into());
self
}
#[must_use]
pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
self.nonce = Some(nonce.into());
self
}
#[must_use]
pub fn chain_id<T: Into<U64>>(mut self, chain_id: T) -> Self {
self.chain_id = Some(chain_id.into());
self
}
pub fn sighash(&self) -> H256 {
match self.chain_id {
Some(_) => keccak256(self.rlp().as_ref()).into(),
None => keccak256(self.rlp_unsigned().as_ref()).into(),
}
}
pub fn rlp(&self) -> Bytes {
let mut rlp = RlpStream::new();
if let Some(chain_id) = self.chain_id {
rlp.begin_list(NUM_TX_FIELDS);
self.rlp_base(&mut rlp);
rlp.append(&chain_id);
rlp.append(&0u8);
rlp.append(&0u8);
} else {
rlp.begin_list(NUM_TX_FIELDS - 3);
self.rlp_base(&mut rlp);
}
rlp.out().freeze().into()
}
pub fn rlp_unsigned(&self) -> Bytes {
let mut rlp = RlpStream::new();
rlp.begin_list(NUM_TX_FIELDS - 3);
self.rlp_base(&mut rlp);
rlp.out().freeze().into()
}
pub fn rlp_signed(&self, signature: &Signature) -> Bytes {
let mut rlp = RlpStream::new();
rlp.begin_list(NUM_TX_FIELDS);
self.rlp_base(&mut rlp);
rlp.append(&signature.v);
rlp.append(&signature.r);
rlp.append(&signature.s);
rlp.out().freeze().into()
}
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);
#[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()));
}
pub(crate) fn decode_unsigned_rlp_base(
rlp: &rlp::Rlp,
offset: &mut usize,
) -> Result<Self, rlp::DecoderError> {
let mut txn = TransactionRequest::new();
txn.nonce = Some(rlp.at(*offset)?.as_val()?);
*offset += 1;
txn.gas_price = Some(rlp.at(*offset)?.as_val()?);
*offset += 1;
txn.gas = Some(rlp.at(*offset)?.as_val()?);
*offset += 1;
#[cfg(feature = "celo")]
{
txn.fee_currency = Some(rlp.at(*offset)?.as_val()?);
*offset += 1;
txn.gateway_fee_recipient = Some(rlp.at(*offset)?.as_val()?);
*offset += 1;
txn.gateway_fee = Some(rlp.at(*offset)?.as_val()?);
*offset += 1;
}
txn.to = decode_to(rlp, offset)?.map(NameOrAddress::Address);
txn.value = Some(rlp.at(*offset)?.as_val()?);
*offset += 1;
let txndata = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
txn.data = match txndata.len() {
0 => None,
_ => Some(Bytes::from(txndata.to_vec())),
};
*offset += 1;
Ok(txn)
}
pub fn decode_unsigned_rlp(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
let mut offset = 0;
let mut txn = Self::decode_unsigned_rlp_base(rlp, &mut offset)?;
if let Ok(chainid) = rlp.val_at(offset) {
txn.chain_id = Some(chainid);
}
Ok(txn)
}
pub fn decode_signed_rlp(rlp: &rlp::Rlp) -> Result<(Self, Signature), RequestError> {
let mut offset = 0;
let mut txn = Self::decode_unsigned_rlp_base(rlp, &mut offset)?;
let v = rlp.at(offset)?.as_val()?;
txn.chain_id = extract_chain_id(v);
offset += 1;
let r = rlp.at(offset)?.as_val()?;
offset += 1;
let s = rlp.at(offset)?.as_val()?;
let sig = Signature { r, s, v };
txn.from = Some(sig.recover(txn.sighash())?);
Ok((txn, sig))
}
}
impl Decodable for TransactionRequest {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
Self::decode_unsigned_rlp(rlp)
}
}
impl From<&Transaction> for TransactionRequest {
fn from(tx: &Transaction) -> TransactionRequest {
TransactionRequest {
from: Some(tx.from),
to: tx.to.map(NameOrAddress::Address),
gas: Some(tx.gas),
gas_price: tx.gas_price,
value: Some(tx.value),
data: Some(Bytes(tx.input.0.clone())),
nonce: Some(tx.nonce),
chain_id: tx.chain_id.map(|x| U64::from(x.as_u64())),
#[cfg(feature = "celo")]
fee_currency: tx.fee_currency,
#[cfg(feature = "celo")]
gateway_fee_recipient: tx.gateway_fee_recipient,
#[cfg(feature = "celo")]
gateway_fee: tx.gateway_fee,
}
}
}
#[cfg(feature = "celo")]
impl TransactionRequest {
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);
}
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[must_use]
pub fn fee_currency<T: Into<Address>>(mut self, fee_currency: T) -> Self {
self.fee_currency = Some(fee_currency.into());
self
}
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[must_use]
pub fn gateway_fee<T: Into<U256>>(mut self, gateway_fee: T) -> Self {
self.gateway_fee = Some(gateway_fee.into());
self
}
#[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
#[must_use]
pub fn gateway_fee_recipient<T: Into<Address>>(mut self, gateway_fee_recipient: T) -> Self {
self.gateway_fee_recipient = Some(gateway_fee_recipient.into());
self
}
}
#[cfg(test)]
#[cfg(not(any(feature = "celo", feature = "optimism")))]
mod tests {
use super::*;
use crate::types::{transaction::eip2718::TypedTransaction, Bytes, NameOrAddress, Signature};
use rlp::{Decodable, Rlp};
use std::str::FromStr;
#[test]
fn encode_decode_rlp() {
let tx = TransactionRequest::new()
.nonce(3)
.gas_price(1)
.gas(25000)
.to("b94f5374fce5edbc8e2a8697c15331677e6ebf0b".parse::<Address>().unwrap())
.value(10)
.data(vec![0x55, 0x44])
.chain_id(1);
let rlp_bytes = &tx.rlp().to_vec()[..];
let got_rlp = Rlp::new(rlp_bytes);
let txn_request = TransactionRequest::decode(&got_rlp).unwrap();
assert_eq!(tx.sighash(), txn_request.sighash());
}
#[test]
fn empty_sighash_check() {
let tx = TransactionRequest::new()
.nonce(0)
.to("095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap())
.value(0)
.gas(0)
.gas_price(0);
let expected_sighash = "c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386";
let got_sighash = hex::encode(tx.sighash().as_bytes());
assert_eq!(expected_sighash, got_sighash);
}
#[test]
fn decode_unsigned_transaction() {
let _res: TransactionRequest = serde_json::from_str(
r#"{
"gas":"0xc350",
"gasPrice":"0x4a817c800",
"hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
"input":"0x68656c6c6f21",
"nonce":"0x15",
"to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
"transactionIndex":"0x41",
"value":"0xf3dbb76162000",
"chain_id": "0x1"
}"#,
)
.unwrap();
}
#[test]
fn decode_known_rlp_goerli() {
let tx = TransactionRequest::new()
.nonce(70272)
.from("fab2b4b677a4e104759d378ea25504862150256e".parse::<Address>().unwrap())
.to("d1f23226fb4d2b7d2f3bcdd99381b038de705a64".parse::<Address>().unwrap())
.value(0)
.gas_price(1940000007)
.gas(21000);
let expected_rlp = hex::decode("f866830112808473a20d0782520894d1f23226fb4d2b7d2f3bcdd99381b038de705a6480801ca04bc89d41c954168afb4cbd01fe2e0f9fe12e3aa4665eefcee8c4a208df044b5da05d410fd85a2e31870ea6d6af53fafc8e3c1ae1859717c863cac5cff40fee8da4").unwrap();
let (got_tx, _signature) =
TransactionRequest::decode_signed_rlp(&Rlp::new(&expected_rlp)).unwrap();
assert_eq!(got_tx.sighash(), tx.sighash());
}
#[test]
fn decode_unsigned_rlp_no_chainid() {
let expected_tx = TransactionRequest::new()
.to(Address::from_str("0xc7696b27830dd8aa4823a1cba8440c27c36adec4").unwrap())
.gas(3_000_000)
.gas_price(20_000_000_000u64)
.value(0)
.nonce(6306u64)
.data(
Bytes::from_str(
"0x91b7f5ed0000000000000000000000000000000000000000000000000000000000000372",
)
.unwrap(),
);
let expected_rlp = hex::decode("f8488218a28504a817c800832dc6c094c7696b27830dd8aa4823a1cba8440c27c36adec480a491b7f5ed0000000000000000000000000000000000000000000000000000000000000372").unwrap();
let real_tx = TransactionRequest::decode(&Rlp::new(&expected_rlp)).unwrap();
assert_eq!(real_tx, expected_tx);
}
#[test]
fn test_eip155_encode() {
let tx = TransactionRequest::new()
.nonce(9)
.to("3535353535353535353535353535353535353535".parse::<Address>().unwrap())
.value(1000000000000000000u64)
.gas_price(20000000000u64)
.gas(21000)
.chain_id(1);
let expected_rlp = hex::decode("ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080").unwrap();
assert_eq!(expected_rlp, tx.rlp().to_vec());
let expected_sighash =
hex::decode("daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53")
.unwrap();
assert_eq!(expected_sighash, tx.sighash().as_bytes().to_vec());
}
#[test]
fn test_eip155_decode() {
let tx = TransactionRequest::new()
.nonce(9)
.to("3535353535353535353535353535353535353535".parse::<Address>().unwrap())
.value(1000000000000000000u64)
.gas_price(20000000000u64)
.gas(21000)
.chain_id(1);
let expected_hex = hex::decode("ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080").unwrap();
let expected_rlp = rlp::Rlp::new(expected_hex.as_slice());
let decoded_transaction = TransactionRequest::decode(&expected_rlp).unwrap();
assert_eq!(tx, decoded_transaction);
}
#[test]
fn test_eip155_decode_signed() {
let expected_signed_bytes = hex::decode("f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83").unwrap();
let expected_signed_rlp = rlp::Rlp::new(expected_signed_bytes.as_slice());
let (decoded_tx, decoded_sig) =
TransactionRequest::decode_signed_rlp(&expected_signed_rlp).unwrap();
let expected_sig = Signature {
v: 37,
r: U256::from_dec_str(
"18515461264373351373200002665853028612451056578545711640558177340181847433846",
)
.unwrap(),
s: U256::from_dec_str(
"46948507304638947509940763649030358759909902576025900602547168820602576006531",
)
.unwrap(),
};
assert_eq!(expected_sig, decoded_sig);
assert_eq!(decoded_tx.chain_id, Some(U64::from(1)));
}
#[test]
fn test_eip155_signing_decode_vitalik() {
let rlp_transactions =
vec!["f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d",
"f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6",
"f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5",
"f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de",
"f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060",
"f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1",
"f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d",
"f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021",
"f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
"f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"];
let rlp_transactions_bytes = rlp_transactions
.iter()
.map(|rlp_str| hex::decode(rlp_str).unwrap())
.collect::<Vec<Vec<u8>>>();
let raw_addresses = vec![
"0xf0f6f18bca1b28cd68e4357452947e021241e9ce",
"0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112",
"0x2e485e0c23b4c3c542628a5f672eeab0ad4888be",
"0x82a88539669a3fd524d669e858935de5e5410cf0",
"0xf9358f2538fd5ccfeb848b64a96b743fcc930554",
"0xa8f7aba377317440bc5b26198a363ad22af1f3a4",
"0xf1f571dc362a0e5b2696b8e775f8491d3e50de35",
"0xd37922162ab7cea97c97a87551ed02c9a38b7332",
"0x9bddad43f934d313c2b79ca28a432dd2b7281029",
"0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f",
];
let addresses = raw_addresses.iter().map(|addr| addr.parse::<Address>().unwrap().into());
let decoded_transactions = rlp_transactions_bytes.iter().map(|raw_tx| {
TransactionRequest::decode_signed_rlp(&Rlp::new(raw_tx.as_slice())).unwrap().0
});
for (tx, from_addr) in decoded_transactions.zip(addresses) {
let from_tx: NameOrAddress = tx.from.unwrap().into();
assert_eq!(from_tx, from_addr);
}
}
#[test]
fn test_recover_legacy_tx() {
let raw_tx = "f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8";
let data = hex::decode(raw_tx).unwrap();
let rlp = Rlp::new(&data);
let (tx, sig) = TypedTransaction::decode_signed(&rlp).unwrap();
let recovered = sig.recover(tx.sighash()).unwrap();
let expected: Address = "0xa12e1462d0ced572f396f58b6e2d03894cd7c8a4".parse().unwrap();
assert_eq!(expected, recovered);
}
}