Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/node/tests/it/key_authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@ async fn test_post_t1b_keyauth_oog_fixed() -> eyre::Result<()> {
.expect_err("Post-T1B: replay must be rejected");
let err_msg = replay_err.to_string();
assert!(
err_msg.contains("nonce too low: next nonce 1, tx nonce 0"),
"Post-T1B: replay error must be nonce-too-low, got: {err_msg}"
err_msg.contains("KeyAlreadyExists"),
"Post-T1B: replay error must be KeyAlreadyExists, got: {err_msg}"
);

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion crates/node/tests/it/liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ async fn test_block_building_insufficient_fee_amm_liquidity() -> eyre::Result<()
nonce += 1;
}
Err(err) => {
if err.to_string().contains("Insufficient liquidity") {
if err.to_string().contains("insufficient liquidity") {
transactions_rejected += 1;
} else {
panic!("Transaction {i} rejected with unexpected error: {err}");
Expand Down
5 changes: 3 additions & 2 deletions crates/node/tests/it/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use alloy::{
};
use alloy_eips::Decodable2718;
use alloy_primitives::{Address, TxKind, U256};
use reth_chainspec::EthChainSpec;
use reth_ethereum::{
evm::revm::primitives::hex,
node::builder::{NodeBuilder, NodeHandle},
Expand Down Expand Up @@ -87,7 +88,7 @@ async fn test_insufficient_funds() -> eyre::Result<()> {
"../assets/test-genesis.json"
))?);

let node_config = NodeConfig::new(Arc::new(chain_spec))
let node_config = NodeConfig::new(Arc::new(chain_spec.clone()))
.with_unused_ports()
.dev()
.with_rpc(RpcServerArgs::default().with_unused_ports().with_http());
Expand All @@ -102,7 +103,7 @@ async fn test_insufficient_funds() -> eyre::Result<()> {
.await?;

let tx = TempoTransaction {
chain_id: 1,
chain_id: chain_spec.chain_id(),
nonce: U256::random().saturating_to(),
fee_token: Some(DEFAULT_FEE_TOKEN),
max_priority_fee_per_gas: 74982851675,
Expand Down
116 changes: 3 additions & 113 deletions crates/node/tests/it/tip_fee_manager.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::utils::{TestNodeBuilder, make_genesis_at, setup_test_token};
use crate::utils::{TestNodeBuilder, setup_test_token};
use alloy::{
consensus::Transaction,
network::ReceiptResponse,
Expand Down Expand Up @@ -405,116 +405,6 @@ async fn test_fee_payer_tx() -> eyre::Result<()> {
Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_fee_payer_transfer_whitelist_pre_t1c() -> eyre::Result<()> {
reth_tracing::init_test_tracing();

let pre_t1c_genesis = make_genesis_at(tempo_chainspec::hardfork::TempoHardfork::T1B);

let setup = TestNodeBuilder::new()
.with_genesis(pre_t1c_genesis)
.build_http_only()
.await?;

let admin = MnemonicBuilder::from_phrase(crate::utils::TEST_MNEMONIC).build()?;
let admin_addr = admin.address();
let fee_payer_signer = MnemonicBuilder::from_phrase(crate::utils::TEST_MNEMONIC)
.index(1)?
.build()?;
let fee_payer_addr = fee_payer_signer.address();
let provider = ProviderBuilder::new()
.wallet(admin.clone())
.connect_http(setup.http_url.clone());

// Create a token where admin has DEFAULT_ADMIN_ROLE
let admin_token = setup_test_token(provider.clone(), admin_addr).await?;
let token_addr = *admin_token.address();
admin_token
.mint(admin_addr, U256::from(1e18 as u64))
.gas(1_000_000)
.send()
.await?
.get_receipt()
.await?;
admin_token
.mint(fee_payer_addr, U256::from(1e18 as u64))
.gas(1_000_000)
.send()
.await?
.get_receipt()
.await?;

// Provide AMM liquidity so admin_token can be swapped to PATH_USD for fee settlement
let fee_amm = ITIPFeeAMM::new(TIP_FEE_MANAGER_ADDRESS, provider.clone());
fee_amm
.mint(
token_addr,
PATH_USD_ADDRESS,
U256::from(1e17 as u64),
admin_addr,
)
.send()
.await?
.get_receipt()
.await?;

// Set fee_payer's fee preference to admin_token
let fee_payer_provider = ProviderBuilder::new()
.wallet(fee_payer_signer.clone())
.connect_http(setup.http_url.clone());
let fee_manager_fp = IFeeManager::new(TIP_FEE_MANAGER_ADDRESS, fee_payer_provider.clone());
fee_manager_fp
.setUserToken(token_addr)
.send()
.await?
.get_receipt()
.await?;

// Create whitelist policy: whitelist fee_payer only (FeeManager not whitelisted pre-T1C)
let registry = ITIP403Registry::new(TIP403_REGISTRY_ADDRESS, provider.clone());
let policy_receipt = registry
.createPolicy(admin_addr, ITIP403Registry::PolicyType::WHITELIST)
.gas(1_000_000)
.send()
.await?
.get_receipt()
.await?;

let policy_id = policy_receipt
.logs()
.iter()
.filter_map(|log| ITIP403Registry::PolicyCreated::decode_log(&log.inner).ok())
.next()
.expect("PolicyCreated event should be emitted")
.policyId;

registry
.modifyPolicyWhitelist(policy_id, fee_payer_addr, true)
.gas(1_000_000)
.send()
.await?
.get_receipt()
.await?;

let admin_token_contract = ITIP20::new(token_addr, &provider);
let receipt = admin_token_contract
.changeTransferPolicyId(policy_id)
.gas(1_000_000)
.send()
.await?
.get_receipt()
.await?;
assert!(receipt.status(), "changeTransferPolicyId should succeed");

// Pre-T1C: tx should succeed without whitelisting the FeeManager
let tx = TransactionRequest::default()
.to(Address::ZERO)
.value(U256::ZERO);
let _ = fee_payer_provider.send_transaction(tx).await?;

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_fee_payer_transfer_whitelist_post_t1c() -> eyre::Result<()> {
reth_tracing::init_test_tracing();
Expand Down Expand Up @@ -620,8 +510,8 @@ async fn test_fee_payer_transfer_whitelist_post_t1c() -> eyre::Result<()> {
let result = fee_payer_provider.send_transaction(tx).await;
let err = result.unwrap_err().to_string();
assert!(
err.contains("blacklisted"),
"expected blacklisted fee payer error, got: {err}"
err.contains("PolicyForbids"),
"expected PolicyForbids error, got: {err}"
);

// Whitelist FeeManager — now fee_payer's tx should go through
Expand Down
82 changes: 82 additions & 0 deletions crates/revm/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,88 @@ pub enum TempoInvalidTransaction {
CallsValidation(&'static str),
}

impl TempoInvalidTransaction {
/// Returns `true` if this error is deterministic — i.e. the transaction is inherently
/// malformed and will never become valid regardless of state changes.
///
/// Returns `false` for state-dependent errors (balance, nonce, expiry, liquidity)
/// that may resolve as state advances.
pub fn is_bad_transaction(&self) -> bool {
match self {
Self::EthInvalidTransaction(eth) => match eth {
InvalidTransaction::PriorityFeeGreaterThanMaxFee
| InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
| InvalidTransaction::GasFloorMoreThanGasLimit { .. }
| InvalidTransaction::CreateInitCodeSizeLimit
| InvalidTransaction::InvalidChainId
| InvalidTransaction::MissingChainId
| InvalidTransaction::AccessListNotSupported
| InvalidTransaction::MaxFeePerBlobGasNotSupported
| InvalidTransaction::BlobVersionedHashesNotSupported
| InvalidTransaction::EmptyBlobs
| InvalidTransaction::BlobCreateTransaction
| InvalidTransaction::TooManyBlobs { .. }
| InvalidTransaction::BlobVersionNotSupported
| InvalidTransaction::AuthorizationListNotSupported
| InvalidTransaction::AuthorizationListInvalidFields
| InvalidTransaction::EmptyAuthorizationList
| InvalidTransaction::Eip2930NotSupported
| InvalidTransaction::Eip1559NotSupported
| InvalidTransaction::Eip4844NotSupported
| InvalidTransaction::Eip7702NotSupported
| InvalidTransaction::Eip7873NotSupported
| InvalidTransaction::Eip7873MissingTarget
| InvalidTransaction::OverflowPaymentInTransaction
| InvalidTransaction::NonceOverflowInTransaction
| InvalidTransaction::TxGasLimitGreaterThanCap { .. } => true,

InvalidTransaction::GasPriceLessThanBasefee
| InvalidTransaction::CallerGasLimitMoreThanBlock
| InvalidTransaction::RejectCallerWithCode
| InvalidTransaction::LackOfFundForMaxFee { .. }
| InvalidTransaction::NonceTooHigh { .. }
| InvalidTransaction::NonceTooLow { .. }
| InvalidTransaction::BlobGasPriceGreaterThanMax { .. }
| InvalidTransaction::Str(_) => false,
},

// Deterministic: tx is inherently malformed.
Self::SystemTransactionMustBeCall
| Self::SystemTransactionFailed(_)
| Self::InvalidFeePayerSignature
| Self::SelfSponsoredFeePayer
| Self::InvalidP256Signature
| Self::InvalidWebAuthnSignature { .. }
| Self::AccessKeyRecoveryFailed
| Self::AccessKeyCannotAuthorizeOtherKeys
| Self::KeyAuthorizationSignatureRecoveryFailed
| Self::KeyAuthorizationNotSignedByRoot { .. }
| Self::KeychainUserAddressMismatch { .. }
| Self::KeyAuthorizationChainIdMismatch { .. }
| Self::ValueTransferNotAllowed
| Self::ValueTransferNotAllowedInAATx
| Self::ExpiringNonceMissingTxEnv
| Self::ExpiringNonceMissingValidBefore
| Self::ExpiringNonceNonceNotZero
| Self::SubblockTransactionMustHaveZeroFee
| Self::KeychainOpInSubblockTransaction
| Self::LegacyKeychainSignature
| Self::CallsValidation(_) => true,

// State-dependent: may resolve as state advances.
Self::ValidAfter { .. }
| Self::ValidBefore { .. }
| Self::InvalidFeeToken(_)
| Self::AccessKeyExpiryInPast { .. }
| Self::KeychainPrecompileError { .. }
| Self::KeychainValidationFailed { .. }
| Self::CollectFeePreTx(_)
| Self::NonceManagerError(_)
| Self::V2KeychainBeforeActivation => false,
}
}
}

impl InvalidTxError for TempoInvalidTransaction {
fn is_nonce_too_low(&self) -> bool {
match self {
Expand Down
16 changes: 9 additions & 7 deletions crates/transaction-pool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ reth-transaction-pool.workspace = true
reth-evm.workspace = true
reth-chainspec.workspace = true
reth-primitives-traits.workspace = true
reth-revm.workspace = true
reth-storage-api.workspace = true
reth-provider.workspace = true
reth-eth-wire-types.workspace = true
Expand All @@ -48,13 +49,14 @@ metrics.workspace = true
[features]
default = []
test-utils = [
"reth-chainspec/test-utils",
"reth-ethereum-primitives/test-utils",
"reth-evm/test-utils",
"reth-primitives-traits/test-utils",
"reth-provider/test-utils",
"reth-transaction-pool/test-utils",
"tempo-precompiles/test-utils",
"reth-chainspec/test-utils",
"reth-ethereum-primitives/test-utils",
"reth-evm/test-utils",
"reth-primitives-traits/test-utils",
"reth-provider/test-utils",
"reth-transaction-pool/test-utils",
"tempo-precompiles/test-utils",
"reth-revm/test-utils"
]

[dev-dependencies]
Expand Down
9 changes: 7 additions & 2 deletions crates/transaction-pool/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ use crate::transaction::TempoPooledTransaction;
use alloy_consensus::{Transaction, TxEip1559};
use alloy_eips::eip2930::AccessList;
use alloy_primitives::{Address, B256, Signature, TxKind, U256};
use reth_chainspec::EthChainSpec;
use reth_primitives_traits::Recovered;
use reth_provider::test_utils::{ExtendedAccount, MockEthProvider};
use reth_transaction_pool::{TransactionOrigin, ValidPoolTransaction};
use std::time::Instant;
use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardfork, spec::DEV};
use tempo_chainspec::{
TempoChainSpec,
hardfork::TempoHardfork,
spec::{DEV, MODERATO},
};
use tempo_precompiles::storage::{StorageCtx, hashmap::HashMapStorageProvider};
use tempo_primitives::{
TempoTxEnvelope,
Expand Down Expand Up @@ -190,7 +195,7 @@ impl TxBuilder {
});

let tx = TempoTransaction {
chain_id: 1,
chain_id: MODERATO.chain_id(),
max_priority_fee_per_gas: self.max_priority_fee_per_gas,
max_fee_per_gas: self.max_fee_per_gas,
gas_limit: self.gas_limit,
Expand Down
Loading
Loading