From 895e66d937f259545a9b8ad0a34dd1087fd0e4fa Mon Sep 17 00:00:00 2001 From: Will Qiu Date: Sat, 27 May 2023 09:27:55 +0800 Subject: [PATCH 1/4] test: enable validate tests --- Cargo.lock | 1 + tests/Cargo.toml | 1 + tests/src/common/gen.rs | 4 + tests/src/common/mod.rs | 16 ++- tests/src/validate_tests.rs | 239 +++++++++++++++++++++++------------- 5 files changed, 176 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67e982a2..e105590d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,6 +118,7 @@ version = "0.1.0" dependencies = [ "aa-bundler-contracts", "aa-bundler-primitives", + "aa-bundler-uopool", "anyhow", "ethers", "tempdir", diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 0e229223..35a975ed 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -14,6 +14,7 @@ rust-version = "1.69.0" [dev-dependencies] aa-bundler-contracts = { path = "../crates/contracts" } aa-bundler-primitives = { path = "../crates/primitives" } +aa-bundler-uopool = { path = "../crates/uopool" } anyhow = "1" ethers = { workspace = true } diff --git a/tests/src/common/gen.rs b/tests/src/common/gen.rs index 07dca22c..2e53ca53 100644 --- a/tests/src/common/gen.rs +++ b/tests/src/common/gen.rs @@ -42,3 +42,7 @@ abigen!( TracerTest, "$CARGO_WORKSPACE_DIR/thirdparty/bundler/packages/bundler/artifacts/contracts/tests/TracerTest.sol/TracerTest.json" ); +abigen!( + TestCoin, + "$CARGO_WORKSPACE_DIR/thirdparty/bundler/packages/bundler/artifacts/contracts/tests/TestCoin.sol/TestCoin.json" +); diff --git a/tests/src/common/mod.rs b/tests/src/common/mod.rs index 2f4e471a..5376d498 100644 --- a/tests/src/common/mod.rs +++ b/tests/src/common/mod.rs @@ -13,8 +13,9 @@ use ethers::{ use tempdir::TempDir; use self::gen::{ - EntryPointContract, TestOpcodesAccount, TestOpcodesAccountFactory, TestRecursionAccount, - TestRulesAccountFactory, TestStorageAccount, TestStorageAccountFactory, TracerTest, + EntryPointContract, TestCoin, TestOpcodesAccount, TestOpcodesAccountFactory, + TestRecursionAccount, TestRulesAccountFactory, TestStorageAccount, TestStorageAccountFactory, + TracerTest, }; pub mod gen; @@ -67,8 +68,9 @@ pub async fn deploy_test_opcode_account_factory( pub async fn deploy_test_storage_account_factory( client: Arc, + test_coin_addr: Address, ) -> anyhow::Result>> { - let (factory, receipt) = TestStorageAccountFactory::deploy(client, ())? + let (factory, receipt) = TestStorageAccountFactory::deploy(client, test_coin_addr)? .send_with_receipt() .await?; let address = receipt.contract_address.unwrap(); @@ -114,6 +116,14 @@ pub async fn deploy_tracer_test( Ok(DeployedContract::new(factory, address)) } +pub async fn deploy_test_coin( + client: Arc, +) -> anyhow::Result>> { + let (factory, receipt) = TestCoin::deploy(client, ())?.send_with_receipt().await?; + let address = receipt.contract_address.unwrap(); + Ok(DeployedContract::new(factory, address)) +} + pub async fn sign( user_op: &mut UserOperation, entry_point_address: &Address, diff --git a/tests/src/validate_tests.rs b/tests/src/validate_tests.rs index e713a4f8..1a0b6748 100644 --- a/tests/src/validate_tests.rs +++ b/tests/src/validate_tests.rs @@ -1,4 +1,7 @@ -use aa_bundler_primitives::UserOperation; +use aa_bundler_contracts::EntryPoint; +use aa_bundler_primitives::{Chain, UserOperation}; +use aa_bundler_uopool::canonical::simulation::{SimulateValidationError, SimulationResult}; +use aa_bundler_uopool::{mempool_id, MemoryMempool, MemoryReputation, UoPool}; use ethers::abi::Token; use ethers::prelude::BaseContract; use ethers::types::transaction::eip2718::TypedTransaction; @@ -8,9 +11,11 @@ use ethers::{ providers::Middleware, types::{Bytes, U256}, }; +use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; +use crate::common::deploy_test_coin; use crate::common::{ deploy_entry_point, deploy_test_opcode_account, deploy_test_opcode_account_factory, deploy_test_recursion_account, deploy_test_rules_account_factory, deploy_test_storage_account, @@ -22,7 +27,7 @@ use crate::common::{ setup_geth, ClientType, DeployedContract, }; -struct TestContext { +struct TestContext { pub client: Arc, pub _geth: GethInstance, pub entry_point: DeployedContract>, @@ -31,9 +36,11 @@ struct TestContext { pub storage_factory: DeployedContract>, pub _rules_factory: DeployedContract>, pub storage_account: DeployedContract>, + pub uopool: UoPool, } async fn setup() -> anyhow::Result> { + let chain_id = 1337u64; let (_geth, _client) = setup_geth().await?; let client = Arc::new(_client); let entry_point = deploy_entry_point(client.clone()).await?; @@ -51,8 +58,10 @@ async fn setup() -> anyhow::Result> { .send() .await?; + let test_coin = deploy_test_coin(client.clone()).await?; let opcodes_factory = deploy_test_opcode_account_factory(client.clone()).await?; - let storage_factory = deploy_test_storage_account_factory(client.clone()).await?; + let storage_factory = + deploy_test_storage_account_factory(client.clone(), test_coin.address).await?; let rules_factory = deploy_test_rules_account_factory(client.clone()).await?; let storage_account_call = rules_factory.contract().create("".to_string()); @@ -66,6 +75,25 @@ async fn setup() -> anyhow::Result> { .value(parse_units("1", "ether").unwrap()) .send() .await?; + + let mempool_id = mempool_id(&entry_point.address, &U256::from(chain_id)); + let mut entrypoints_map = HashMap::new(); + entrypoints_map.insert( + mempool_id, + EntryPoint::new(client.clone(), entry_point.address), + ); + let mempools = Box::new(MemoryMempool::default()); + let reputation = Box::new(MemoryReputation::default()); + let pool = UoPool::new( + EntryPoint::new(client.clone(), entry_point.address), + mempools, + reputation, + client.clone(), + U256::from(1500000000_u64), + U256::from(1u64), + Chain::from(chain_id), + ); + Ok(TestContext:: { client: client.clone(), _geth, @@ -78,6 +106,7 @@ async fn setup() -> anyhow::Result> { TestRulesAccount::new(storage_account_address, client.clone()), storage_account_address, ), + uopool: pool, }) } @@ -110,14 +139,13 @@ async fn create_opcode_factory_init_code(init_func: String) -> anyhow::Result<(B } async fn create_test_user_op( + context: &TestContext, validate_rule: String, pm_rule: Option, init_code: Bytes, init_func: Bytes, factory_address: Address, ) -> anyhow::Result { - let context = setup().await?; - let paymaster_and_data = if let Some(rule) = pm_rule { let mut data = vec![]; data.extend_from_slice(context.paymaster.address.as_bytes()); @@ -161,18 +189,17 @@ async fn create_test_user_op( }) } -async fn existing_storage_account_user_op( +fn existing_storage_account_user_op( + context: &TestContext, validate_rule: String, pm_rule: String, -) -> anyhow::Result { - let context = setup().await?; - +) -> UserOperation { let mut paymaster_and_data = vec![]; paymaster_and_data.extend_from_slice(context.paymaster.address.as_bytes()); paymaster_and_data.extend_from_slice(pm_rule.as_bytes()); let signature = Bytes::from(validate_rule.as_bytes().to_vec()); - Ok(UserOperation { + UserOperation { sender: context.storage_account.address, nonce: U256::zero(), init_code: Bytes::default(), @@ -184,45 +211,55 @@ async fn existing_storage_account_user_op( max_priority_fee_per_gas: U256::from(0), paymaster_and_data: Bytes::from(paymaster_and_data), signature, - }) + } } -fn validate(_user_op: UserOperation) -> anyhow::Result<()> { - // TODO - Ok(()) +async fn validate( + context: &TestContext, + user_op: UserOperation, +) -> Result { + context.uopool.simulate_user_operation(&user_op).await } async fn test_user_op( + context: &TestContext, validate_rule: String, pm_rule: Option, init_code: Bytes, init_func: Bytes, factory_address: Address, -) -> anyhow::Result<()> { +) -> Result { let user_op = create_test_user_op( + &context, validate_rule, pm_rule, init_code, init_func, factory_address, ) - .await?; - validate(user_op) + .await + .expect("Create test user operation failed."); + validate(&context, user_op).await } -async fn test_existing_user_op(validate_rule: String, pm_rule: String) -> anyhow::Result<()> { - let user_op = existing_storage_account_user_op(validate_rule, pm_rule).await?; - validate(user_op) +async fn test_existing_user_op( + validate_rule: String, + pm_rule: String, +) -> Result { + let context = setup().await.expect("Setup context failed"); + + let user_op = existing_storage_account_user_op(&context, validate_rule, pm_rule); + validate(&context, user_op).await } #[tokio::test] -#[ignore] async fn accept_plain_request() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_opcode_factory_init_code("".to_string()) .await .unwrap(); test_user_op( + &context, "".to_string(), None, init_code, @@ -235,188 +272,213 @@ async fn accept_plain_request() -> anyhow::Result<()> { } #[tokio::test] -#[ignore] async fn reject_unkown_rule() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_opcode_factory_init_code("".to_string()) .await .unwrap(); - test_user_op( + let res = test_user_op( + &context, "".to_string(), None, init_code, init_func, context.opcodes_factory.address, ) - .await - .expect_err("unknown rule"); + .await; + assert!(matches!( + res, + Err(SimulateValidationError::UserOperationRejected { message }) if message.contains("unknown-rule") + )); Ok(()) } #[tokio::test] -#[ignore] async fn fail_with_bad_opcode_in_ctr() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_opcode_factory_init_code("coinbase".to_string()) .await .unwrap(); - test_user_op( + let res = test_user_op( + &context, "".to_string(), None, init_code, init_func, context.opcodes_factory.address, ) - .await - .expect_err("factory uses banned opcode: COINBASE"); + .await; + assert!(matches!( + res, + Err(SimulateValidationError::OpcodeValidation { entity, opcode }) if entity=="factory" && opcode == "COINBASE" + )); Ok(()) } #[tokio::test] -#[ignore] async fn fail_with_bad_opcode_in_paymaster() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_opcode_factory_init_code("".to_string()) .await .unwrap(); - test_user_op( + let res = test_user_op( + &context, "".to_string(), Some("coinbase".to_string()), init_code, init_func, context.opcodes_factory.address, ) - .await - .expect_err("paymaster uses banned opcode: COINBASE"); + .await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::OpcodeValidation { entity, opcode }) if entity=="paymaster" && opcode == "COINBASE" + )); Ok(()) } #[tokio::test] -#[ignore] async fn fail_with_bad_opcode_in_validation() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_opcode_factory_init_code("".to_string()) .await .unwrap(); - test_user_op( + let res = test_user_op( + &context, "blockhash".to_string(), None, init_code, init_func, context.opcodes_factory.address, ) - .await - .expect_err("account uses banned opcode: BLOCKHASH"); + .await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::OpcodeValidation { entity, opcode }) if entity=="account" && opcode == "BLOCKHASH" + )); Ok(()) } #[tokio::test] -#[ignore] async fn fail_if_create_too_many() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_opcode_factory_init_code("".to_string()) .await .unwrap(); - test_user_op( + let res = test_user_op( + &context, "create2".to_string(), None, init_code, init_func, context.opcodes_factory.address, ) - .await - .expect("account uses banned opcode: CREATE2"); + .await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::OpcodeValidation { entity, opcode }) if entity=="account" && opcode == "CREATE2" + )); Ok(()) } #[tokio::test] -#[ignore] async fn fail_referencing_self_token() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_storage_factory_init_code(0, "".to_string()) .await .unwrap(); - test_user_op( + let res = test_user_op( + &context, "balance-self".to_string(), None, init_code, init_func, context.storage_factory.address, ) - .await - .expect_err("unstaked account accessed"); + .await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::StorageAccessValidation { .. }) + )); Ok(()) } #[tokio::test] -#[ignore] async fn account_succeeds_referecing_its_own_balance() { - test_existing_user_op("balance-self".to_string(), "".to_string()) - .await - .expect("succeed"); + let res = test_existing_user_op("balance-self".to_string(), "".to_string()).await; + println!("{res:?}"); + assert!(matches!(res, Ok(..))); } #[tokio::test] -#[ignore] async fn account_fail_to_read_allowance_of_address() { - test_existing_user_op("allowance-self-1".to_string(), "".to_string()) - .await - .expect_err("account has forbidden read"); + let res = test_existing_user_op("allowance-self-1".to_string(), "".to_string()).await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::StorageAccessValidation { .. }) + )); } #[tokio::test] -#[ignore] async fn account_can_reference_its_own_allowance_on_other_contract_balance() { - test_existing_user_op("allowance-1-self".to_string(), "".to_string()) - .await - .expect("succeed"); + let res = test_existing_user_op("allowance-1-self".to_string(), "".to_string()).await; + println!("{res:?}"); + assert!(matches!(res, Ok(..))); } #[tokio::test] -#[ignore] async fn access_self_struct_data() { - test_existing_user_op("struct-self".to_string(), "".to_string()) - .await - .expect("succeed"); + let res = test_existing_user_op("struct-self".to_string(), "".to_string()).await; + println!("{res:?}"); + assert!(matches!(res, Ok(..))); } #[tokio::test] -#[ignore] async fn fail_to_access_other_address_struct_data() { - test_existing_user_op("struct-1".to_string(), "".to_string()) - .await - .expect_err("account has forbidden read"); + let res = test_existing_user_op("struct-1".to_string(), "".to_string()).await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::StorageAccessValidation { .. }) + )); } #[tokio::test] -#[ignore] async fn fail_if_referencing_other_token_balance() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_storage_factory_init_code(0, "".to_string()) .await .unwrap(); - test_user_op( + let res = test_user_op( + &context, "balance-1".to_string(), None, init_code, init_func, context.storage_factory.address, ) - .await - .expect_err("account has forbidden read"); + .await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::StorageAccessValidation { .. }) + )); Ok(()) } #[tokio::test] -#[ignore] async fn fail_if_referencing_self_token_balance_after_wallet_creation() { - test_existing_user_op("balance-self".to_string(), "".to_string()) - .await - .expect("succeed"); + let res = test_existing_user_op("balance-self".to_string(), "".to_string()).await; + println!("{res:?}"); + assert!(matches!(res, Ok(..))); } #[tokio::test] -#[ignore] async fn fail_with_unstaked_paymaster_returning_context() -> anyhow::Result<()> { let context = setup().await?; let pm = deploy_test_storage_account(context.client.clone()) @@ -443,12 +505,16 @@ async fn fail_with_unstaked_paymaster_returning_context() -> anyhow::Result<()> paymaster_and_data: Bytes::from(paymaster_and_data), signature: Bytes::default(), }; - validate(user_op).expect_err("unstaked paymaster must not return context"); + let res = validate(&context, user_op).await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::StorageAccessValidation { .. }) + )); Ok(()) } #[tokio::test] -#[ignore] async fn fail_with_validation_recursively_calls_handle_ops() -> anyhow::Result<()> { let context = setup().await?; let acct = deploy_test_recursion_account(context.client.clone(), context.entry_point.address) @@ -467,18 +533,23 @@ async fn fail_with_validation_recursively_calls_handle_ops() -> anyhow::Result<( paymaster_and_data: Bytes::default(), signature: Bytes::from("handleOps".as_bytes().to_vec()), }; - validate(user_op).expect_err("illegal call into EntryPoint"); + let res = validate(&context, user_op).await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::StorageAccessValidation { .. }) + )); Ok(()) } #[tokio::test] -#[ignore] async fn succeed_with_inner_revert() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_storage_factory_init_code(0, "".to_string()) .await .unwrap(); test_user_op( + &context, "inner-revert".to_string(), None, init_code, @@ -491,20 +562,24 @@ async fn succeed_with_inner_revert() -> anyhow::Result<()> { } #[tokio::test] -#[ignore] async fn fail_with_inner_oog_revert() -> anyhow::Result<()> { let context = setup().await?; let (init_code, init_func) = create_storage_factory_init_code(0, "".to_string()) .await .unwrap(); - test_user_op( + let res = test_user_op( + &context, "oog".to_string(), None, init_code, init_func, context.storage_factory.address, ) - .await - .expect_err("oog"); + .await; + println!("{res:?}"); + assert!(matches!( + res, + Err(SimulateValidationError::StorageAccessValidation { .. }) + )); Ok(()) } From 75ac7f5f17ee56a742565029b21a29a2a3674856 Mon Sep 17 00:00:00 2001 From: Will Qiu Date: Sun, 28 May 2023 10:55:35 +0800 Subject: [PATCH 2/4] fix: simulation add oog check --- crates/contracts/src/tracer.rs | 1 + crates/uopool/src/canonical/simulation.rs | 19 +++++++++++++++++++ tests/src/validate_tests.rs | 6 +----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/contracts/src/tracer.rs b/crates/contracts/src/tracer.rs index d0de4a8d..5c8050f4 100644 --- a/crates/contracts/src/tracer.rs +++ b/crates/contracts/src/tracer.rs @@ -30,6 +30,7 @@ pub struct Level { pub opcodes: HashMap, #[serde(rename = "contractSize")] pub contract_size: HashMap, + pub oog: Option, } #[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)] diff --git a/crates/uopool/src/canonical/simulation.rs b/crates/uopool/src/canonical/simulation.rs index 32933d0d..d69ad932 100644 --- a/crates/uopool/src/canonical/simulation.rs +++ b/crates/uopool/src/canonical/simulation.rs @@ -86,6 +86,7 @@ pub enum SimulateValidationError { CodeHashesValidation { message: String, }, + OutOfGas {}, UnknownError { error: String, }, @@ -140,6 +141,11 @@ impl From for SimulationError { SimulateValidationError::CodeHashesValidation { message } => { SimulationError::owned(OPCODE_VALIDATION_ERROR_CODE, message, None::) } + SimulateValidationError::OutOfGas {} => SimulationError::owned( + OPCODE_VALIDATION_ERROR_CODE, + "UserOperation out of gas", + None::, + ), SimulateValidationError::UnknownError { error } => { SimulationError::owned(ErrorCode::InternalError.code(), error, None::) } @@ -311,6 +317,17 @@ impl UoPool { }; } + fn check_oog(&self, trace: &JsTracerFrame) -> Result<(), SimulateValidationError> { + for (index, _) in LEVEL_TO_ENTITY.iter().enumerate() { + if let Some(level) = trace.number_levels.get(index) { + if level.oog.unwrap_or(false) { + return Err(SimulateValidationError::OutOfGas {}); + } + } + } + Ok(()) + } + fn forbidden_opcodes(&self, trace: &JsTracerFrame) -> Result<(), SimulateValidationError> { for (index, _) in LEVEL_TO_ENTITY.iter().enumerate() { if let Some(level) = trace.number_levels.get(index) { @@ -682,6 +699,8 @@ impl UoPool { &mut stake_info_by_entity, ); + self.check_oog(&js_trace)?; + // may not invokes any forbidden opcodes self.forbidden_opcodes(&js_trace)?; diff --git a/tests/src/validate_tests.rs b/tests/src/validate_tests.rs index 1a0b6748..34b61c51 100644 --- a/tests/src/validate_tests.rs +++ b/tests/src/validate_tests.rs @@ -576,10 +576,6 @@ async fn fail_with_inner_oog_revert() -> anyhow::Result<()> { context.storage_factory.address, ) .await; - println!("{res:?}"); - assert!(matches!( - res, - Err(SimulateValidationError::StorageAccessValidation { .. }) - )); + assert!(matches!(res, Err(SimulateValidationError::OutOfGas { .. }))); Ok(()) } From c51f14b329b27184b5cdc4c43b0e36e43a4f85d3 Mon Sep 17 00:00:00 2001 From: Will Qiu Date: Sun, 28 May 2023 18:39:32 +0800 Subject: [PATCH 3/4] test: fix unstake validation test --- tests/src/validate_tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/src/validate_tests.rs b/tests/src/validate_tests.rs index 34b61c51..2ba55331 100644 --- a/tests/src/validate_tests.rs +++ b/tests/src/validate_tests.rs @@ -1,7 +1,7 @@ use aa_bundler_contracts::EntryPoint; use aa_bundler_primitives::{Chain, UserOperation}; use aa_bundler_uopool::canonical::simulation::{SimulateValidationError, SimulationResult}; -use aa_bundler_uopool::{mempool_id, MemoryMempool, MemoryReputation, UoPool}; +use aa_bundler_uopool::{mempool_id, MemoryMempool, MemoryReputation, Reputation, UoPool}; use ethers::abi::Token; use ethers::prelude::BaseContract; use ethers::types::transaction::eip2718::TypedTransaction; @@ -83,7 +83,8 @@ async fn setup() -> anyhow::Result> { EntryPoint::new(client.clone(), entry_point.address), ); let mempools = Box::new(MemoryMempool::default()); - let reputation = Box::new(MemoryReputation::default()); + let mut reputation = Box::new(MemoryReputation::default()); + reputation.init(10, 10, 10, 1u64.into(), 1u64.into()); let pool = UoPool::new( EntryPoint::new(client.clone(), entry_point.address), mempools, @@ -506,10 +507,9 @@ async fn fail_with_unstaked_paymaster_returning_context() -> anyhow::Result<()> signature: Bytes::default(), }; let res = validate(&context, user_op).await; - println!("{res:?}"); assert!(matches!( res, - Err(SimulateValidationError::StorageAccessValidation { .. }) + Err(SimulateValidationError::CallStackValidation { .. }) )); Ok(()) } From df87e4e3c75e28c0c47b0380ba5fbbab7732554c Mon Sep 17 00:00:00 2001 From: Will Qiu Date: Mon, 29 May 2023 12:03:19 +0800 Subject: [PATCH 4/4] fix: simulation forbid recursive call --- crates/uopool/src/canonical/simulation.rs | 15 +++++++++++++++ tests/src/validate_tests.rs | 17 +++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/crates/uopool/src/canonical/simulation.rs b/crates/uopool/src/canonical/simulation.rs index d69ad932..9ffcd6f5 100644 --- a/crates/uopool/src/canonical/simulation.rs +++ b/crates/uopool/src/canonical/simulation.rs @@ -550,6 +550,21 @@ impl UoPool { let mut calls: Vec = vec![]; self.parse_call_stack(trace, &mut calls)?; + // check recursive call entrypoint method + let call_into_entry_point = calls.iter().find(|call| { + call.to.unwrap_or_default() == self.entry_point.address() + && call.from.unwrap_or_default() != self.entry_point.address() + && (call.method.is_some() + && call.method.clone().unwrap_or_default() != *"depositTo") + }); + if call_into_entry_point.is_some() { + return Err(SimulateValidationError::CallStackValidation { + message: format!( + "illegal call into EntryPoint during validation {call_into_entry_point:?}" + ), + }); + } + for (index, stake_info) in stake_info_by_entity.iter().enumerate() { if LEVEL_TO_ENTITY[index] == "paymaster" { let call = calls.iter().find(|call| { diff --git a/tests/src/validate_tests.rs b/tests/src/validate_tests.rs index 2ba55331..cb454e13 100644 --- a/tests/src/validate_tests.rs +++ b/tests/src/validate_tests.rs @@ -1,5 +1,5 @@ use aa_bundler_contracts::EntryPoint; -use aa_bundler_primitives::{Chain, UserOperation}; +use aa_bundler_primitives::{Chain, UoPoolMode, UserOperation}; use aa_bundler_uopool::canonical::simulation::{SimulateValidationError, SimulationResult}; use aa_bundler_uopool::{mempool_id, MemoryMempool, MemoryReputation, Reputation, UoPool}; use ethers::abi::Token; @@ -93,6 +93,7 @@ async fn setup() -> anyhow::Result> { U256::from(1500000000_u64), U256::from(1u64), Chain::from(chain_id), + UoPoolMode::Standard, ); Ok(TestContext:: { @@ -331,7 +332,6 @@ async fn fail_with_bad_opcode_in_paymaster() -> anyhow::Result<()> { context.opcodes_factory.address, ) .await; - println!("{res:?}"); assert!(matches!( res, Err(SimulateValidationError::OpcodeValidation { entity, opcode }) if entity=="paymaster" && opcode == "COINBASE" @@ -354,7 +354,6 @@ async fn fail_with_bad_opcode_in_validation() -> anyhow::Result<()> { context.opcodes_factory.address, ) .await; - println!("{res:?}"); assert!(matches!( res, Err(SimulateValidationError::OpcodeValidation { entity, opcode }) if entity=="account" && opcode == "BLOCKHASH" @@ -377,7 +376,6 @@ async fn fail_if_create_too_many() -> anyhow::Result<()> { context.opcodes_factory.address, ) .await; - println!("{res:?}"); assert!(matches!( res, Err(SimulateValidationError::OpcodeValidation { entity, opcode }) if entity=="account" && opcode == "CREATE2" @@ -400,7 +398,6 @@ async fn fail_referencing_self_token() -> anyhow::Result<()> { context.storage_factory.address, ) .await; - println!("{res:?}"); assert!(matches!( res, Err(SimulateValidationError::StorageAccessValidation { .. }) @@ -411,14 +408,12 @@ async fn fail_referencing_self_token() -> anyhow::Result<()> { #[tokio::test] async fn account_succeeds_referecing_its_own_balance() { let res = test_existing_user_op("balance-self".to_string(), "".to_string()).await; - println!("{res:?}"); assert!(matches!(res, Ok(..))); } #[tokio::test] async fn account_fail_to_read_allowance_of_address() { let res = test_existing_user_op("allowance-self-1".to_string(), "".to_string()).await; - println!("{res:?}"); assert!(matches!( res, Err(SimulateValidationError::StorageAccessValidation { .. }) @@ -428,21 +423,18 @@ async fn account_fail_to_read_allowance_of_address() { #[tokio::test] async fn account_can_reference_its_own_allowance_on_other_contract_balance() { let res = test_existing_user_op("allowance-1-self".to_string(), "".to_string()).await; - println!("{res:?}"); assert!(matches!(res, Ok(..))); } #[tokio::test] async fn access_self_struct_data() { let res = test_existing_user_op("struct-self".to_string(), "".to_string()).await; - println!("{res:?}"); assert!(matches!(res, Ok(..))); } #[tokio::test] async fn fail_to_access_other_address_struct_data() { let res = test_existing_user_op("struct-1".to_string(), "".to_string()).await; - println!("{res:?}"); assert!(matches!( res, Err(SimulateValidationError::StorageAccessValidation { .. }) @@ -464,7 +456,6 @@ async fn fail_if_referencing_other_token_balance() -> anyhow::Result<()> { context.storage_factory.address, ) .await; - println!("{res:?}"); assert!(matches!( res, Err(SimulateValidationError::StorageAccessValidation { .. }) @@ -475,7 +466,6 @@ async fn fail_if_referencing_other_token_balance() -> anyhow::Result<()> { #[tokio::test] async fn fail_if_referencing_self_token_balance_after_wallet_creation() { let res = test_existing_user_op("balance-self".to_string(), "".to_string()).await; - println!("{res:?}"); assert!(matches!(res, Ok(..))); } @@ -534,10 +524,9 @@ async fn fail_with_validation_recursively_calls_handle_ops() -> anyhow::Result<( signature: Bytes::from("handleOps".as_bytes().to_vec()), }; let res = validate(&context, user_op).await; - println!("{res:?}"); assert!(matches!( res, - Err(SimulateValidationError::StorageAccessValidation { .. }) + Err(SimulateValidationError::CallStackValidation { .. }) )); Ok(()) }