From bd86edfd1c2f4d978e87de023ca8e5a8f6526bf9 Mon Sep 17 00:00:00 2001 From: john xu Date: Tue, 17 Oct 2023 13:18:20 +0000 Subject: [PATCH] feat: support taiko protocol --- crates/interpreter/Cargo.toml | 1 + crates/precompile/Cargo.toml | 1 + crates/primitives/Cargo.toml | 1 + crates/primitives/src/env.rs | 36 ++++++ crates/primitives/src/lib.rs | 4 + crates/primitives/src/result.rs | 74 +++++++++++ crates/primitives/src/taiko.rs | 5 + crates/primitives/src/taiko/anchor.rs | 27 ++++ crates/primitives/src/taiko/env.rs | 15 +++ crates/revm/Cargo.toml | 1 + crates/revm/src/evm_impl.rs | 28 ++++- crates/revm/src/handler.rs | 13 ++ crates/revm/src/handler/taiko.rs | 175 ++++++++++++++++++++++++++ 13 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 crates/primitives/src/taiko.rs create mode 100644 crates/primitives/src/taiko/anchor.rs create mode 100644 crates/primitives/src/taiko/env.rs create mode 100644 crates/revm/src/handler/taiko.rs diff --git a/crates/interpreter/Cargo.toml b/crates/interpreter/Cargo.toml index bc233bec394..ff50020eb7a 100644 --- a/crates/interpreter/Cargo.toml +++ b/crates/interpreter/Cargo.toml @@ -22,6 +22,7 @@ serde = ["dep:serde", "revm-primitives/serde"] arbitrary = ["std", "revm-primitives/arbitrary"] optimism = ["revm-primitives/optimism"] +taiko = ["revm-primitives/taiko"] dev = [ "memory_limit", diff --git a/crates/precompile/Cargo.toml b/crates/precompile/Cargo.toml index 99d8183afad..aa3c49c93fc 100644 --- a/crates/precompile/Cargo.toml +++ b/crates/precompile/Cargo.toml @@ -43,6 +43,7 @@ std = [ ] optimism = ["revm-primitives/optimism"] +taiko = ["revm-primitives/taiko"] # This library may not work on all no_std platforms as they depend on C libraries. diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 640a5c045a1..1b27147d56d 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -47,6 +47,7 @@ serde = [ arbitrary = ["std", "alloy-primitives/arbitrary", "bitflags/arbitrary"] optimism = [] +taiko = ["dep:once_cell"] dev = [ "memory_limit", diff --git a/crates/primitives/src/env.rs b/crates/primitives/src/env.rs index 1c64b735fbc..11634bc4fd1 100644 --- a/crates/primitives/src/env.rs +++ b/crates/primitives/src/env.rs @@ -3,6 +3,8 @@ use crate::{ InvalidTransaction, Spec, SpecId, B256, GAS_PER_BLOB, KECCAK_EMPTY, MAX_BLOB_NUMBER_PER_BLOCK, MAX_INITCODE_SIZE, U256, VERSIONED_HASH_VERSION_KZG, }; +#[cfg(feature = "taiko")] +use crate::{EVMError, TaikoEnv, TxType}; use core::cmp::{min, Ordering}; #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -11,6 +13,23 @@ pub struct Env { pub cfg: CfgEnv, pub block: BlockEnv, pub tx: TxEnv, + #[cfg(feature = "taiko")] + /// Configuration of the taiko + pub taiko: TaikoEnv, +} + +#[cfg(feature = "taiko")] +impl Env { + pub fn pre_check(&self) -> Result<(), EVMError> { + if !crate::anchor::validate(self) { + return Err(InvalidTransaction::InvalidAnchorTransaction.into()); + } + Ok(()) + } + + pub fn is_anchor(&self) -> bool { + self.tx.index == 0 + } } /// The block environment. @@ -129,6 +148,13 @@ impl BlockEnv { #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TxEnv { + /// The index of the transaction in the block. + #[cfg(feature = "taiko")] + pub index: usize, + /// The type of the transaction. + #[cfg(feature = "taiko")] + pub tx_type: TxType, + /// Caller aka Author aka transaction signer. pub caller: Address, /// The gas limit of the transaction. @@ -428,6 +454,11 @@ impl Default for BlockEnv { impl Default for TxEnv { fn default() -> Self { Self { + #[cfg(feature = "taiko")] + index: 0, + #[cfg(feature = "taiko")] + tx_type: TxType::Legacy, + caller: Address::ZERO, gas_limit: u64::MAX, gas_price: U256::ZERO, @@ -648,6 +679,11 @@ impl Env { .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?; } + #[cfg(feature = "taiko")] + if self.is_anchor() { + return Ok(()); + } + // Check if account has enough balance for gas_limit*gas_price and value transfer. // Transfer will be done inside `*_inner` functions. if balance_check > account.info.balance { diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 00cda698d40..7590cdda81b 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -14,6 +14,8 @@ pub mod precompile; pub mod result; pub mod specification; pub mod state; +#[cfg(feature = "taiko")] +pub mod taiko; pub mod utilities; pub use alloy_primitives::{ @@ -32,4 +34,6 @@ pub use precompile::*; pub use result::*; pub use specification::*; pub use state::*; +#[cfg(feature = "taiko")] +pub use taiko::*; pub use utilities::*; diff --git a/crates/primitives/src/result.rs b/crates/primitives/src/result.rs index e5f230099c3..ad299341ab7 100644 --- a/crates/primitives/src/result.rs +++ b/crates/primitives/src/result.rs @@ -208,6 +208,80 @@ pub enum InvalidTransaction { /// post-regolith hardfork. #[cfg(feature = "optimism")] DepositSystemTxPostRegolith, + /// Anchor check failed + #[cfg(feature = "taiko")] + InvalidAnchorTransaction, +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidTransaction {} + +impl fmt::Display for InvalidTransaction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InvalidTransaction::PriorityFeeGreaterThanMaxFee => { + write!(f, "Priority fee is greater than max fee") + } + InvalidTransaction::GasPriceLessThanBasefee => { + write!(f, "Gas price is less than basefee") + } + InvalidTransaction::CallerGasLimitMoreThanBlock => { + write!(f, "Caller gas limit exceeds the block gas limit") + } + InvalidTransaction::CallGasCostMoreThanGasLimit => { + write!(f, "Call gas cost exceeds the gas limit") + } + InvalidTransaction::RejectCallerWithCode => { + write!(f, "Reject transactions from senders with deployed code") + } + InvalidTransaction::LackOfFundForMaxFee { fee, balance } => { + write!(f, "Lack of funds {} for max fee {}", balance, fee) + } + InvalidTransaction::OverflowPaymentInTransaction => { + write!(f, "Overflow payment in transaction") + } + InvalidTransaction::NonceOverflowInTransaction => { + write!(f, "Nonce overflow in transaction") + } + InvalidTransaction::NonceTooHigh { tx, state } => { + write!(f, "Nonce too high {}, expected {}", tx, state) + } + InvalidTransaction::NonceTooLow { tx, state } => { + write!(f, "Nonce {} too low, expected {}", tx, state) + } + InvalidTransaction::CreateInitcodeSizeLimit => { + write!(f, "Create initcode size limit") + } + InvalidTransaction::InvalidChainId => write!(f, "Invalid chain id"), + InvalidTransaction::AccessListNotSupported => { + write!(f, "Access list not supported") + } + InvalidTransaction::MaxFeePerBlobGasNotSupported => { + write!(f, "Max fee per blob gas not supported") + } + InvalidTransaction::BlobVersionedHashesNotSupported => { + write!(f, "Blob versioned hashes not supported") + } + InvalidTransaction::BlobGasPriceGreaterThanMax => { + write!(f, "Blob gas price is greater than max fee per blob gas") + } + InvalidTransaction::EmptyBlobs => write!(f, "Empty blobs"), + InvalidTransaction::BlobCreateTransaction => write!(f, "Blob create transaction"), + InvalidTransaction::TooManyBlobs => write!(f, "Too many blobs"), + InvalidTransaction::BlobVersionNotSupported => write!(f, "Blob version not supported"), + #[cfg(feature = "optimism")] + InvalidTransaction::DepositSystemTxPostRegolith => { + write!( + f, + "Deposit system transactions post regolith hardfork are not supported" + ) + } + #[cfg(feature = "taiko")] + InvalidTransaction::InvalidAnchorTransaction => { + write!(f, "Invalid anchor transaction") + } + } + } } impl From for EVMError { diff --git a/crates/primitives/src/taiko.rs b/crates/primitives/src/taiko.rs new file mode 100644 index 00000000000..d9c7e067383 --- /dev/null +++ b/crates/primitives/src/taiko.rs @@ -0,0 +1,5 @@ +pub mod anchor; +pub mod env; + +pub use anchor::*; +pub use env::*; diff --git a/crates/primitives/src/taiko/anchor.rs b/crates/primitives/src/taiko/anchor.rs new file mode 100644 index 00000000000..c63c1dac701 --- /dev/null +++ b/crates/primitives/src/taiko/anchor.rs @@ -0,0 +1,27 @@ +use super::env::TxType; +use crate::{Address, Env, TransactTo, U256}; +use once_cell::sync::Lazy; +use std::str::FromStr; + +const ANCHOR_SELECTOR: u32 = 0xda69d3db; +const ANCHOR_GAS_LIMIT: u64 = 180_000; +static GOLDEN_TOUCH_ACCOUNT: Lazy
= Lazy::new(|| { + Address::from_str("0x0000777735367b36bC9B61C50022d9D0700dB4Ec") + .expect("invalid golden touch account") +}); + +pub static TREASURY: Lazy
= Lazy::new(|| { + Address::from_str("0xdf09A0afD09a63fb04ab3573922437e1e637dE8b") + .expect("invalid treasury account") +}); + +pub(crate) fn validate(env: &Env) -> bool { + !env.is_anchor() + || (env.tx.tx_type == TxType::Eip1559 + && env.tx.transact_to == TransactTo::Call(env.taiko.l2_address) + && u32::from_be_bytes(env.tx.data[..4].try_into().unwrap()) == ANCHOR_SELECTOR + && env.tx.value == U256::ZERO + && env.tx.gas_limit == ANCHOR_GAS_LIMIT + && env.tx.gas_price == env.block.basefee + && env.tx.caller == *GOLDEN_TOUCH_ACCOUNT) +} diff --git a/crates/primitives/src/taiko/env.rs b/crates/primitives/src/taiko/env.rs new file mode 100644 index 00000000000..b46879fd9d1 --- /dev/null +++ b/crates/primitives/src/taiko/env.rs @@ -0,0 +1,15 @@ +use crate::Address; + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TaikoEnv { + pub l2_address: Address, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TxType { + Legacy, + Eip2930, + Eip1559, +} diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 017787ce3f5..b70ed60f00a 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -41,6 +41,7 @@ serde = ["dep:serde", "dep:serde_json", "revm-interpreter/serde"] arbitrary = ["revm-interpreter/arbitrary"] optimism = ["revm-interpreter/optimism", "revm-precompile/optimism"] +taiko = ["revm-interpreter/taiko", "revm-precompile/taiko"] ethersdb = ["std", "tokio", "futures", "ethers-providers", "ethers-core"] diff --git a/crates/revm/src/evm_impl.rs b/crates/revm/src/evm_impl.rs index 577b219e3f1..69c6e106c8e 100644 --- a/crates/revm/src/evm_impl.rs +++ b/crates/revm/src/evm_impl.rs @@ -158,6 +158,9 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact fn preverify_transaction(&mut self) -> Result<(), EVMError> { let env = self.env(); + #[cfg(feature = "taiko")] + env.pre_check()?; + // Important: validate block before tx. env.validate_block_env::()?; env.validate_tx::()?; @@ -193,6 +196,8 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact let tx_value = env.tx.value; let tx_data = env.tx.data.clone(); let tx_gas_limit = env.tx.gas_limit; + #[cfg(feature = "taiko")] + let is_anchor = env.is_anchor(); #[cfg(feature = "optimism")] let tx_l1_cost = { @@ -278,8 +283,15 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact gas_cost = gas_cost.saturating_add(data_fee); } - caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost); + #[cfg(feature = "taiko")] + if !is_anchor { + caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost); + } + #[cfg(not(feature = "taiko"))] + { + caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost); + } // touch account so we know it is changed. caller_account.mark_touch(); @@ -402,6 +414,18 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, precompiles: Precompiles, ) -> Self { let journaled_state = JournaledState::new(precompiles.len(), GSPEC::SPEC_ID); + + #[cfg(feature = "optimism")] + let handler = if env.cfg.optimism { + Handler::optimism::() + } else { + Handler::mainnet::() + }; + #[cfg(feature = "taiko")] + let handler = Handler::taiko::(); + #[cfg(not(any(feature = "optimism", feature = "taiko")))] + let handler = Handler::mainnet::(); + Self { data: EVMData { env, @@ -413,7 +437,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, l1_block_info: None, }, inspector, - handler: Handler::mainnet::(), + handler, _phantomdata: PhantomData {}, } } diff --git a/crates/revm/src/handler.rs b/crates/revm/src/handler.rs index 9a5e2f5b2f1..cdff4f69d08 100644 --- a/crates/revm/src/handler.rs +++ b/crates/revm/src/handler.rs @@ -1,6 +1,8 @@ pub mod mainnet; #[cfg(feature = "optimism")] pub mod optimism; +#[cfg(feature = "taiko")] +pub mod taiko; use revm_interpreter::primitives::db::Database; use revm_interpreter::primitives::{EVMError, EVMResultGeneric}; @@ -45,6 +47,17 @@ impl Handler { } } + /// Handler for the taiko + #[cfg(feature = "taiko")] + pub fn taiko() -> Self { + Self { + call_return: taiko::handle_call_return::, + calculate_gas_refund: taiko::calculate_gas_refund::, + reimburse_caller: taiko::handle_reimburse_caller::, + reward_beneficiary: taiko::reward_beneficiary::, + } + } + /// Handler for the optimism #[cfg(feature = "optimism")] pub fn optimism() -> Self { diff --git a/crates/revm/src/handler/taiko.rs b/crates/revm/src/handler/taiko.rs new file mode 100644 index 00000000000..1caa7cfa789 --- /dev/null +++ b/crates/revm/src/handler/taiko.rs @@ -0,0 +1,175 @@ +//! Mainnet related handlers. +use revm_interpreter::primitives::EVMError; + +use crate::{ + interpreter::{return_ok, return_revert, Gas, InstructionResult}, + primitives::{db::Database, Env, Spec, SpecId::LONDON, U256}, + EVMData, +}; + +/// Handle output of the transaction +pub fn handle_call_return( + env: &Env, + call_result: InstructionResult, + returned_gas: Gas, +) -> Gas { + let tx_gas_limit = env.tx.gas_limit; + // Spend the gas limit. Gas is reimbursed when the tx returns successfully. + let mut gas = Gas::new(tx_gas_limit); + if env.is_anchor() { + return gas; + } + gas.record_cost(tx_gas_limit); + + match call_result { + return_ok!() => { + gas.erase_cost(returned_gas.remaining()); + gas.record_refund(returned_gas.refunded()); + } + return_revert!() => { + gas.erase_cost(returned_gas.remaining()); + } + _ => {} + } + gas +} + +#[inline] +pub fn handle_reimburse_caller( + data: &mut EVMData<'_, DB>, + gas: &Gas, + gas_refund: u64, +) -> Result<(), EVMError> { + let _ = data; + if data.env.is_anchor() { + return Ok(()); + } + let caller = data.env.tx.caller; + let effective_gas_price = data.env.effective_gas_price(); + + // return balance of not spend gas. + let (caller_account, _) = data + .journaled_state + .load_account(caller, data.db) + .map_err(EVMError::Database)?; + + caller_account.info.balance = caller_account + .info + .balance + .saturating_add(effective_gas_price * U256::from(gas.remaining() + gas_refund)); + + Ok(()) +} + +/// Reward beneficiary with gas fee. +#[inline] +pub fn reward_beneficiary( + data: &mut EVMData<'_, DB>, + gas: &Gas, + gas_refund: u64, +) -> Result<(), EVMError> { + if data.env.is_anchor() { + return Ok(()); + } + let beneficiary = data.env.block.coinbase; + let effective_gas_price = data.env.effective_gas_price(); + + // transfer fee to coinbase/beneficiary. + // EIP-1559 discard basefee for coinbase transfer. Basefee amount of gas is discarded. + let coinbase_gas_price = if SPEC::enabled(LONDON) { + effective_gas_price.saturating_sub(data.env.block.basefee) + } else { + effective_gas_price + }; + + let (coinbase_account, _) = data + .journaled_state + .load_account(beneficiary, data.db) + .map_err(EVMError::Database)?; + + coinbase_account.mark_touch(); + coinbase_account.info.balance = coinbase_account + .info + .balance + .saturating_add(coinbase_gas_price * U256::from(gas.spend() - gas_refund)); + + let treasury = *crate::primitives::anchor::TREASURY; + let basefee = data.env.block.basefee; + + let (treasury_account, _) = data + .journaled_state + .load_account(treasury, data.db) + .map_err(EVMError::Database)?; + + treasury_account.mark_touch(); + treasury_account.info.balance = treasury_account + .info + .balance + .saturating_add(basefee * U256::from(gas.spend() - gas_refund)); + Ok(()) +} + +/// Calculate gas refund for transaction. +/// +/// If config is set to disable gas refund, it will return 0. +/// +/// If spec is set to london, it will decrease the maximum refund amount to 5th part of +/// gas spend. (Before london it was 2th part of gas spend) +#[inline] +pub fn calculate_gas_refund(env: &Env, gas: &Gas) -> u64 { + if env.cfg.is_gas_refund_disabled() { + 0 + } else { + // EIP-3529: Reduction in refunds + let max_refund_quotient = if SPEC::enabled(LONDON) { 5 } else { 2 }; + (gas.refunded() as u64).min(gas.spend() / max_refund_quotient) + } +} + +#[cfg(test)] +mod tests { + use revm_interpreter::primitives::CancunSpec; + + use super::*; + + #[test] + fn test_consume_gas() { + let mut env = Env::default(); + env.tx.gas_limit = 100; + + let gas = handle_call_return::(&env, InstructionResult::Stop, Gas::new(90)); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spend(), 10); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_consume_gas_with_refund() { + let mut env = Env::default(); + env.tx.gas_limit = 100; + + let mut return_gas = Gas::new(90); + return_gas.record_refund(30); + + let gas = handle_call_return::(&env, InstructionResult::Stop, return_gas); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spend(), 10); + assert_eq!(gas.refunded(), 30); + + let gas = handle_call_return::(&env, InstructionResult::Revert, return_gas); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spend(), 10); + assert_eq!(gas.refunded(), 0); + } + + #[test] + fn test_revert_gas() { + let mut env = Env::default(); + env.tx.gas_limit = 100; + + let gas = handle_call_return::(&env, InstructionResult::Revert, Gas::new(90)); + assert_eq!(gas.remaining(), 90); + assert_eq!(gas.spend(), 10); + assert_eq!(gas.refunded(), 0); + } +}