use serde::Deserialize;
use crate::account::keys::{L1Signature, WalletError};
#[cfg(feature = "sign")]
use crate::account::keys::{LocalWallet, recover_digest};
use crate::client::evm::rlp;
use crate::client::evm::u256::{B256, U256};
use crate::utils::Address;
use crate::utils::sign::keccak256;
pub const EIP1559: u8 = 0x02;
pub const GAS_HEADROOM: u64 = 130;
const BASE_FEE_TIMES: u64 = 2;
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccessItem {
pub address: Address,
pub storage_keys: Vec<B256>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Eip1559 {
pub chain_id: u64,
pub nonce: u64,
pub max_priority_fee: U256,
pub max_fee: U256,
pub gas: u64,
pub to: Option<Address>,
pub value: U256,
pub data: Vec<u8>,
pub access_list: Vec<AccessItem>,
}
impl Eip1559 {
fn fields(&self) -> Vec<Vec<u8>> {
let to = match &self.to {
Some(to) => rlp::bytes(to.bytes()),
None => rlp::bytes(&[]),
};
let access: Vec<Vec<u8>> = self
.access_list
.iter()
.map(|item| {
let keys: Vec<Vec<u8>> = item.storage_keys.iter().map(|key| rlp::bytes(&key.0)).collect();
rlp::list(&[rlp::bytes(item.address.bytes()), rlp::list(&keys)])
})
.collect();
vec![
rlp::uint(u128::from(self.chain_id)),
rlp::uint(u128::from(self.nonce)),
rlp::u256(self.max_priority_fee),
rlp::u256(self.max_fee),
rlp::uint(u128::from(self.gas)),
to,
rlp::u256(self.value),
rlp::bytes(&self.data),
rlp::list(&access),
]
}
#[must_use]
pub fn unsigned(&self) -> Vec<u8> {
typed(&self.fields())
}
#[must_use]
pub fn signing_hash(&self) -> B256 {
B256(keccak256(&self.unsigned()))
}
pub fn signed(&self, signature: &L1Signature) -> Result<Vec<u8>, WalletError> {
let parity = match signature.v() {
27 => 0u8,
28 => 1u8,
other => return Err(WalletError::BadV(other)),
};
let mut fields = self.fields();
fields.push(rlp::uint(u128::from(parity)));
fields.push(rlp::word(signature.r()));
fields.push(rlp::word(signature.s()));
Ok(typed(&fields))
}
#[cfg(feature = "sign")]
pub fn sign(&self, wallet: &LocalWallet) -> Result<Vec<u8>, WalletError> {
self.signed(&wallet.sign_digest(&self.signing_hash().0)?)
}
#[cfg(feature = "sign")]
pub fn signer(&self, signature: &L1Signature) -> Result<Address, WalletError> {
recover_digest(&self.signing_hash().0, signature)
}
#[must_use]
pub fn gas_ceiling(&self) -> U256 {
self.max_fee.saturating_mul(U256::from_u64(self.gas))
}
#[must_use]
pub fn most_spent(&self) -> Option<U256> {
self.max_fee.checked_mul(U256::from_u64(self.gas))?.checked_add(self.value)
}
}
fn typed(fields: &[Vec<u8>]) -> Vec<u8> {
let mut out = vec![EIP1559];
out.extend(rlp::list(fields));
out
}
#[must_use]
pub fn tx_hash(raw: &[u8]) -> B256 {
B256(keccak256(raw))
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Call {
pub to: Option<Address>,
pub value: U256,
pub data: Vec<u8>,
}
impl Call {
#[must_use]
pub fn to(to: Address, data: Vec<u8>) -> Call {
Call { to: Some(to), value: U256::ZERO, data }
}
#[must_use]
pub fn transaction(self, chain_id: u64, nonce: u64, gas: u64, fees: Fees) -> Eip1559 {
Eip1559 {
chain_id,
nonce,
max_priority_fee: fees.max_priority_fee,
max_fee: fees.max_fee,
gas,
to: self.to,
value: self.value,
data: self.data,
access_list: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Fees {
pub max_priority_fee: U256,
pub max_fee: U256,
}
impl Fees {
#[must_use]
pub fn over_base(base: U256, tip: U256) -> Fees {
let max_fee = base.saturating_mul(U256::from_u64(BASE_FEE_TIMES)).saturating_add(tip);
Fees { max_priority_fee: tip, max_fee }
}
}
#[must_use]
pub fn with_headroom(gas: u64) -> u64 {
let wide = u128::from(gas) * u128::from(GAS_HEADROOM) / 100;
u64::try_from(wide).unwrap_or(u64::MAX)
}
#[cfg(test)]
#[path = "tests/tx.rs"]
mod tests;