From 9452c0faaa352e3ba4b316869b404dd2a9787b2e Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Wed, 6 Sep 2023 11:00:37 +0200 Subject: [PATCH] One Simulation To Rule Them All (#1842) > One simulation to find them > One simulation to bring them all > And in the Kubernetes cluster bind them Now that we can simulation account balances and ERC-1271 signature for orders with pre-hooks without any additional node requirements, the existing parameterization of the "strategies" for these simulations (naive web3 RPC requests vs simulation) is no longer needed. We can just use the new `StorageAccessible` simulation trick everywhere. This allows us to remove/simplify code an rely on a single method for simulating these order related properties. ### Test Plan E2E tests continue to pass after removing flags. --- crates/autopilot/src/lib.rs | 4 +- crates/e2e/tests/e2e/colocation_hooks.rs | 14 +- crates/e2e/tests/e2e/hooks.rs | 12 +- crates/orderbook/src/run.rs | 4 +- crates/shared/src/account_balances.rs | 44 +- .../shared/src/account_balances/arguments.rs | 89 --- crates/shared/src/account_balances/cached.rs | 18 +- crates/shared/src/account_balances/web3.rs | 617 ------------------ crates/shared/src/arguments.rs | 10 - crates/shared/src/signature_validator.rs | 28 +- crates/solver/src/run.rs | 2 +- 11 files changed, 71 insertions(+), 771 deletions(-) delete mode 100644 crates/shared/src/account_balances/arguments.rs delete mode 100644 crates/shared/src/account_balances/web3.rs diff --git a/crates/autopilot/src/lib.rs b/crates/autopilot/src/lib.rs index 4aadc2d34d..0dc1e678c8 100644 --- a/crates/autopilot/src/lib.rs +++ b/crates/autopilot/src/lib.rs @@ -165,7 +165,7 @@ pub async fn main(args: arguments::Arguments) { .or_else(|| shared::network::block_interval(&network, chain_id)) .expect("unknown network block interval"); - let signature_validator = args.shared.signatures.validator( + let signature_validator = signature_validator::validator( signature_validator::Contracts { chain_id, settlement: settlement_contract.address(), @@ -174,7 +174,7 @@ pub async fn main(args: arguments::Arguments) { web3.clone(), ); - let balance_fetcher = args.shared.balances.cached( + let balance_fetcher = account_balances::cached( account_balances::Contracts { chain_id, settlement: settlement_contract.address(), diff --git a/crates/e2e/tests/e2e/colocation_hooks.rs b/crates/e2e/tests/e2e/colocation_hooks.rs index ed890ea41a..59ac90e60e 100644 --- a/crates/e2e/tests/e2e/colocation_hooks.rs +++ b/crates/e2e/tests/e2e/colocation_hooks.rs @@ -58,15 +58,11 @@ async fn allowance(web3: Web3) { let services = Services::new(onchain.contracts()).await; services.start_autopilot(vec![ - "--account-balances=simulation".to_string(), "--enable-colocation=true".to_string(), "--drivers=http://localhost:11088/test_solver".to_string(), ]); services - .start_api(vec![ - "--account-balances=simulation".to_string(), - "--enable-custom-interactions=true".to_string(), - ]) + .start_api(vec!["--enable-custom-interactions=true".to_string()]) .await; let order = OrderCreation { @@ -248,17 +244,11 @@ async fn signature(web3: Web3) { let services = Services::new(onchain.contracts()).await; services.start_autopilot(vec![ - "--account-balances=simulation".to_string(), - "--eip1271-signature-validator=simulation".to_string(), "--enable-colocation=true".to_string(), "--drivers=http://localhost:11088/test_solver".to_string(), ]); services - .start_api(vec![ - "--account-balances=simulation".to_string(), - "--eip1271-signature-validator=simulation".to_string(), - "--enable-custom-interactions=true".to_string(), - ]) + .start_api(vec!["--enable-custom-interactions=true".to_string()]) .await; // Place Orders diff --git a/crates/e2e/tests/e2e/hooks.rs b/crates/e2e/tests/e2e/hooks.rs index 42d8d072bd..92b52b6389 100644 --- a/crates/e2e/tests/e2e/hooks.rs +++ b/crates/e2e/tests/e2e/hooks.rs @@ -46,12 +46,9 @@ async fn test(web3: Web3) { .await; let services = Services::new(onchain.contracts()).await; - services.start_autopilot(vec!["--account-balances=simulation".to_string()]); + services.start_autopilot(vec![]); services - .start_api(vec![ - "--account-balances=simulation".to_string(), - "--enable-custom-interactions=true".to_string(), - ]) + .start_api(vec!["--enable-custom-interactions=true".to_string()]) .await; let order = OrderCreation { @@ -86,10 +83,7 @@ async fn test(web3: Web3) { assert_eq!(balance, to_wei(5)); tracing::info!("Waiting for trade."); - services.start_old_driver( - solver.private_key(), - vec!["--account-balances=simulation".to_string()], - ); + services.start_old_driver(solver.private_key(), vec![]); let trade_happened = || async { cow.balance_of(trader.address()) .call() diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index e2afc75d38..18233d802f 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -109,7 +109,7 @@ pub async fn run(args: Arguments) { .expect("Failed to retrieve network version ID"); let network_name = network_name(&network, chain_id); - let signature_validator = args.shared.signatures.validator( + let signature_validator = signature_validator::validator( signature_validator::Contracts { chain_id, settlement: settlement_contract.address(), @@ -145,7 +145,7 @@ pub async fn run(args: Arguments) { let domain_separator = DomainSeparator::new(chain_id, settlement_contract.address()); let postgres = Postgres::new(args.db_url.as_str()).expect("failed to create database"); - let balance_fetcher = args.shared.balances.fetcher( + let balance_fetcher = account_balances::fetcher( account_balances::Contracts { chain_id, settlement: settlement_contract.address(), diff --git a/crates/shared/src/account_balances.rs b/crates/shared/src/account_balances.rs index e37fe6dc4a..cbe23bd9c2 100644 --- a/crates/shared/src/account_balances.rs +++ b/crates/shared/src/account_balances.rs @@ -1,23 +1,16 @@ -mod arguments; -mod cached; -mod simulation; -mod web3; - use { anyhow::Result, + ethrpc::{current_block::CurrentBlockStream, Web3}, model::{ interaction::InteractionData, order::{Order, SellTokenSource}, }, primitive_types::{H160, U256}, + std::sync::Arc, }; -pub use self::{ - arguments::{Arguments, Contracts, Strategy}, - cached::CachingBalanceFetcher, - simulation::Balances as SimulationBalanceFetcher, - web3::Web3BalanceFetcher, -}; +mod cached; +mod simulation; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Query { @@ -71,3 +64,32 @@ pub trait BalanceFetching: Send + Sync { amount: U256, ) -> Result<(), TransferSimulationError>; } + +/// Contracts required for balance simulation. +pub struct Contracts { + pub chain_id: u64, + pub settlement: H160, + pub vault_relayer: H160, + pub vault: Option, +} + +/// Create the default [`BalanceFetching`] instance. +pub fn fetcher(contracts: Contracts, web3: Web3) -> Arc { + Arc::new(simulation::Balances::new( + web3, + contracts.settlement, + contracts.vault_relayer, + contracts.vault, + )) +} + +/// Create a cached [`BalanceFetching`] instance. +pub fn cached( + contracts: Contracts, + web3: Web3, + blocks: CurrentBlockStream, +) -> Arc { + let cached = Arc::new(cached::Balances::new(fetcher(contracts, web3))); + cached.spawn_background_task(blocks); + cached +} diff --git a/crates/shared/src/account_balances/arguments.rs b/crates/shared/src/account_balances/arguments.rs deleted file mode 100644 index 0e94057c16..0000000000 --- a/crates/shared/src/account_balances/arguments.rs +++ /dev/null @@ -1,89 +0,0 @@ -use { - super::{BalanceFetching, CachingBalanceFetcher, SimulationBalanceFetcher, Web3BalanceFetcher}, - ethcontract::H160, - ethrpc::{current_block::CurrentBlockStream, Web3}, - std::{ - fmt::{self, Display, Formatter}, - sync::Arc, - }, -}; - -/// Arguments related to the token owner finder. -#[derive(clap::Parser)] -#[group(skip)] -pub struct Arguments { - /// The account balance simulation strategy to use. - #[clap(long, env, default_value = "web3", value_enum)] - pub account_balances: Strategy, - - /// Whether or not to optimistically treat account balance queries with - /// pre-interactions as if sufficient token balance and allowance is always - /// available. Useful for partially supporting pre-interactions in - /// environments where the required simulation infrastructure is not - /// available (such as in E2E tests). - #[clap(long, env, action = clap::ArgAction::Set, default_value = "false")] - pub account_balances_optimistic_pre_interaction_handling: bool, -} - -/// Support token owner finding strategies. -#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)] -pub enum Strategy { - /// Use basic Ethereum RPC requests to query balances and allowances. - /// - /// Note that this strategy does not properly support fetching balances for - /// orders with custom interactions. - Web3, - - /// Use code simulation techniques to query balances and allowances. - /// - /// This strategy fully supports fetching balances for orders with custom - /// interactions. - Simulation, -} - -/// Contracts required for balance simulation. -pub struct Contracts { - pub chain_id: u64, - pub settlement: H160, - pub vault_relayer: H160, - pub vault: Option, -} - -impl Arguments { - pub fn fetcher(&self, contracts: Contracts, web3: Web3) -> Arc { - match self.account_balances { - Strategy::Web3 => Arc::new(Web3BalanceFetcher::new( - web3, - contracts.vault, - contracts.vault_relayer, - contracts.settlement, - self.account_balances_optimistic_pre_interaction_handling, - )), - Strategy::Simulation => Arc::new(SimulationBalanceFetcher::new( - web3, - contracts.settlement, - contracts.vault_relayer, - contracts.vault, - )), - } - } - - pub fn cached( - &self, - contracts: Contracts, - web3: Web3, - blocks: CurrentBlockStream, - ) -> Arc { - let cached = Arc::new(CachingBalanceFetcher::new(self.fetcher(contracts, web3))); - cached.spawn_background_task(blocks); - cached - } -} - -impl Display for Arguments { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - writeln!(f, "account_balances: {:?}", self.account_balances)?; - - Ok(()) - } -} diff --git a/crates/shared/src/account_balances/cached.rs b/crates/shared/src/account_balances/cached.rs index 4bf889d016..855d81b9d1 100644 --- a/crates/shared/src/account_balances/cached.rs +++ b/crates/shared/src/account_balances/cached.rs @@ -71,12 +71,12 @@ struct BalanceEntry { balance: U256, } -pub struct CachingBalanceFetcher { +pub struct Balances { inner: Arc, balance_cache: Arc>, } -impl CachingBalanceFetcher { +impl Balances { pub fn new(inner: Arc) -> Self { Self { inner, @@ -93,7 +93,7 @@ struct CacheResponse { requested_at: BlockNumber, } -impl CachingBalanceFetcher { +impl Balances { fn get_cached_balances(&self, queries: &[Query]) -> CacheResponse { let mut cache = self.balance_cache.lock().unwrap(); let (cached, missing) = queries @@ -156,7 +156,7 @@ impl CachingBalanceFetcher { } #[async_trait::async_trait] -impl BalanceFetching for CachingBalanceFetcher { +impl BalanceFetching for Balances { async fn get_balances(&self, queries: &[Query]) -> Vec> { let CacheResponse { mut cached, @@ -224,7 +224,7 @@ mod tests { .withf(|arg| arg == [query(1)]) .returning(|_| vec![Ok(1.into())]); - let fetcher = CachingBalanceFetcher::new(Arc::new(inner)); + let fetcher = Balances::new(Arc::new(inner)); // 1st call to `inner`. let result = fetcher.get_balances(&[query(1)]).await; assert_eq!(result[0].as_ref().unwrap(), &1.into()); @@ -242,7 +242,7 @@ mod tests { .withf(|arg| arg == [query(1)]) .returning(|_| vec![Err(anyhow::anyhow!("some error"))]); - let fetcher = CachingBalanceFetcher::new(Arc::new(inner)); + let fetcher = Balances::new(Arc::new(inner)); // 1st call to `inner`. assert!(fetcher.get_balances(&[query(1)]).await[0].is_err()); // 2nd call to `inner`. @@ -261,7 +261,7 @@ mod tests { .withf(|arg| arg == [query(1)]) .returning(|_| vec![Ok(U256::one())]); - let fetcher = CachingBalanceFetcher::new(Arc::new(inner)); + let fetcher = Balances::new(Arc::new(inner)); fetcher.spawn_background_task(receiver); // 1st call to `inner`. Balance gets cached. @@ -298,7 +298,7 @@ mod tests { .withf(|arg| arg == [query(2)]) .returning(|_| vec![Ok(2.into())]); - let fetcher = CachingBalanceFetcher::new(Arc::new(inner)); + let fetcher = Balances::new(Arc::new(inner)); // 1st call to `inner` putting balance 1 into the cache. let result = fetcher.get_balances(&[query(1)]).await; assert_eq!(result[0].as_ref().unwrap(), &1.into()); @@ -324,7 +324,7 @@ mod tests { .times(7) .returning(|_| vec![Ok(U256::one())]); - let fetcher = CachingBalanceFetcher::new(Arc::new(inner)); + let fetcher = Balances::new(Arc::new(inner)); fetcher.spawn_background_task(receiver); let cached_entry = || { diff --git a/crates/shared/src/account_balances/web3.rs b/crates/shared/src/account_balances/web3.rs deleted file mode 100644 index ef32c7b9b7..0000000000 --- a/crates/shared/src/account_balances/web3.rs +++ /dev/null @@ -1,617 +0,0 @@ -use { - super::{BalanceFetching, Query, TransferSimulationError}, - crate::ethrpc::{Web3, Web3Transport}, - anyhow::{anyhow, Context, Result}, - contracts::{BalancerV2Vault, ERC20}, - ethcontract::{batch::CallBatch, Account}, - futures::{FutureExt, StreamExt}, - model::order::SellTokenSource, - primitive_types::{H160, U256}, - std::future::Future, - web3::types::{BlockId, BlockNumber, CallRequest}, -}; - -pub struct Web3BalanceFetcher { - web3: Web3, - vault: Option, - vault_relayer: H160, - settlement_contract: H160, - optimistic_pre_interaction_handling: bool, -} - -impl Web3BalanceFetcher { - pub fn new( - web3: Web3, - vault: Option, - vault_relayer: H160, - settlement_contract: H160, - optimistic_pre_interaction_handling: bool, - ) -> Self { - let vault = vault.map(|address| contracts::BalancerV2Vault::at(&web3, address)); - Self { - web3, - vault, - vault_relayer, - settlement_contract, - optimistic_pre_interaction_handling, - } - } - - async fn can_transfer_call(&self, token: H160, from: H160, amount: U256) -> bool { - let instance = ERC20::at(&self.web3, token); - let calldata = instance - .transfer_from(from, self.settlement_contract, amount) - .tx - .data - .unwrap(); - let call_request = CallRequest { - from: Some(self.vault_relayer), - to: Some(token), - data: Some(calldata), - ..Default::default() - }; - let block = Some(BlockId::Number(BlockNumber::Latest)); - let response = self.web3.eth().call(call_request, block).await; - response - .map(|bytes| is_empty_or_truthy(bytes.0.as_slice())) - .unwrap_or(false) - } - - async fn can_manage_user_balance_call(&self, token: H160, from: H160, amount: U256) -> bool { - let vault = match self.vault.as_ref() { - Some(vault) => vault, - None => return false, - }; - - const USER_BALANCE_OP_TRANSFER_EXTERNAL: u8 = 3; - vault - .manage_user_balance(vec![( - USER_BALANCE_OP_TRANSFER_EXTERNAL, - token, - amount, - from, - self.settlement_contract, - )]) - .from(Account::Local(from, None)) - .call() - .await - .is_ok() - } -} - -struct Balance { - balance: U256, - allowance: U256, -} - -impl Balance { - fn zero() -> Self { - Self { - balance: U256::zero(), - allowance: U256::zero(), - } - } - - fn max() -> Self { - Self { - balance: U256::max_value(), - allowance: U256::max_value(), - } - } - - fn effective_balance(&self) -> U256 { - self.balance.min(self.allowance) - } -} - -fn erc20_balance_query( - batch: &mut CallBatch, - token: ERC20, - owner: H160, - spender: H160, -) -> impl Future> { - let balance = token.balance_of(owner).batch_call(batch); - let allowance = token.allowance(owner, spender).batch_call(batch); - async move { - let balance = balance.await.context("balance")?; - let allowance = allowance.await.context("allowance")?; - Ok(Balance { balance, allowance }) - } -} - -fn vault_external_balance_query( - batch: &mut CallBatch, - vault: BalancerV2Vault, - token: ERC20, - owner: H160, - relayer: H160, -) -> impl Future> { - let balance = erc20_balance_query(batch, token, owner, vault.address()); - let approval = vault.has_approved_relayer(owner, relayer).batch_call(batch); - async move { - Ok(match approval.await.context("allowance")? { - true => balance.await.context("balance")?, - false => Balance::zero(), - }) - } -} - -#[async_trait::async_trait] -impl BalanceFetching for Web3BalanceFetcher { - async fn get_balances(&self, queries: &[Query]) -> Vec> { - let mut batch = CallBatch::new(self.web3.transport().clone()); - let futures = queries - .iter() - .map(|query| { - if !query.interactions.is_empty() { - tracing::warn!( - ?query, - "fetching balances for orders with interactions is not fully supported" - ); - - if self.optimistic_pre_interaction_handling { - return async { Ok(Balance::max()) }.boxed(); - } - } - - let token = ERC20::at(&self.web3, query.token); - match (query.source, &self.vault) { - (SellTokenSource::Erc20, _) => { - erc20_balance_query(&mut batch, token, query.owner, self.vault_relayer) - .boxed() - } - (SellTokenSource::External, Some(vault)) => vault_external_balance_query( - &mut batch, - vault.clone(), - token, - query.owner, - self.vault_relayer, - ) - .boxed(), - (SellTokenSource::External, None) => { - async { Err(anyhow!("external balance but no vault")) }.boxed() - } - (SellTokenSource::Internal, _) => { - async { Err(anyhow!("internal balances are not supported")) }.boxed() - } - } - }) - .collect::>(); - batch.execute_all(usize::MAX).await; - futures::stream::iter(futures) - .then(|future| async { - let balance = future.await?; - Ok(balance.effective_balance()) - }) - .collect() - .await - } - - async fn can_transfer( - &self, - query: &Query, - amount: U256, - ) -> Result<(), TransferSimulationError> { - if !query.interactions.is_empty() { - tracing::warn!( - ?query, - "fetching balances for orders with interactions is not fully supported" - ); - - if self.optimistic_pre_interaction_handling { - return Ok(()); - } - } - - match (query.source, &self.vault) { - (SellTokenSource::Erc20, _) => { - // In the very likely case that we can transfer we only do one RPC call. - // Only do more calls in case we need to closer assess why the transfer is - // failing - if self - .can_transfer_call(query.token, query.owner, amount) - .await - { - return Ok(()); - } - let mut batch = CallBatch::new(self.web3.transport().clone()); - let token = ERC20::at(&self.web3, query.token); - let balance_future = - erc20_balance_query(&mut batch, token, query.owner, self.vault_relayer); - // Batch needs to execute before we can await the query result - batch.execute_all(usize::MAX).await; - let Balance { balance, allowance } = balance_future.await?; - if balance < amount { - return Err(TransferSimulationError::InsufficientBalance); - } - if allowance < amount { - return Err(TransferSimulationError::InsufficientAllowance); - } - return Err(TransferSimulationError::TransferFailed); - } - (SellTokenSource::External, Some(vault)) => { - if self - .can_manage_user_balance_call(query.token, query.owner, amount) - .await - { - return Ok(()); - } - let mut batch = CallBatch::new(self.web3.transport().clone()); - let token = ERC20::at(&self.web3, query.token); - let balance_future = - erc20_balance_query(&mut batch, token, query.owner, vault.address()); - // Batch needs to execute before we can await the query result - batch.execute_all(usize::MAX).await; - let Balance { balance, allowance } = balance_future.await?; - if balance < amount { - return Err(TransferSimulationError::InsufficientBalance); - } - if allowance < amount { - return Err(TransferSimulationError::InsufficientAllowance); - } - return Err(TransferSimulationError::TransferFailed); - } - (SellTokenSource::External, None) => { - return Err(TransferSimulationError::Other(anyhow!( - "External Vault balances require a deployed vault" - ))) - } - (SellTokenSource::Internal, _) => { - return Err(TransferSimulationError::Other(anyhow!( - "internal Vault balances not supported" - ))) - } - }; - } -} - -fn is_empty_or_truthy(bytes: &[u8]) -> bool { - match bytes.len() { - 0 => true, - 32 => bytes.iter().any(|byte| *byte > 0), - _ => false, - } -} - -#[cfg(test)] -mod tests { - use { - super::*, - crate::ethrpc::create_env_test_transport, - contracts::{vault, BalancerV2Authorizer, ERC20Mintable}, - hex_literal::hex, - }; - - #[tokio::test] - #[ignore] - async fn mainnet_can_transfer() { - let http = create_env_test_transport(); - let web3 = Web3::new(http); - let settlement = contracts::GPv2Settlement::deployed(&web3).await.unwrap(); - let vault_relayer = settlement.vault_relayer().call().await.unwrap(); - let fetcher = - Web3BalanceFetcher::new(web3, None, vault_relayer, settlement.address(), false); - let owner = H160(hex!("07c2af75788814BA7e5225b2F5c951eD161cB589")); - let token = H160(hex!("dac17f958d2ee523a2206206994597c13d831ec7")); - - let result = fetcher - .get_balances(&[Query { - owner, - token, - source: SellTokenSource::Erc20, - interactions: vec![], - }]) - .await - .into_iter() - .next() - .unwrap() - .unwrap(); - assert!(result >= U256::from(1000)); - - let call_result = fetcher.can_transfer_call(token, owner, 1000.into()).await; - assert!(call_result); - } - - #[tokio::test] - #[ignore] - async fn mainnet_cannot_transfer() { - // TODO: For this test to work we need to find a new address that has approved - // the contract for a token that takes a fee on transfer and still has - // balance nio that token. - - let http = create_env_test_transport(); - let web3 = Web3::new(http); - let settlement = contracts::GPv2Settlement::deployed(&web3).await.unwrap(); - let vault_relayer = settlement.vault_relayer().call().await.unwrap(); - let fetcher = - Web3BalanceFetcher::new(web3, None, vault_relayer, settlement.address(), false); - let owner = H160(hex!("401c51ebe418d2809921565e606b60851bace4ec")); - // Token takes a fee. - let token = H160(hex!("bae5f2d8a1299e5c4963eaff3312399253f27ccb")); - - let result = fetcher - .get_balances(&[Query { - owner, - token, - source: SellTokenSource::Erc20, - interactions: vec![], - }]) - .await - .into_iter() - .next() - .unwrap() - .unwrap(); - println!("{result}"); - assert!(result >= U256::from(811)); - - let call_result = fetcher.can_transfer_call(token, owner, 811.into()).await; - // The non trace method is less accurate and thinks the transfer is ok even - // though it isn't. - assert!(call_result); - } - - #[tokio::test] - #[ignore] - async fn watch_testnet_erc20_balance() { - let http = create_env_test_transport(); - let web3 = Web3::new(http); - - let accounts: Vec = web3.eth().accounts().await.expect("get accounts failed"); - let trader = Account::Local(accounts[0], None); - - let allowance_target = Account::Local(accounts[1], None); - - let token = ERC20Mintable::builder(&web3) - .deploy() - .await - .expect("MintableERC20 deployment failed"); - - let fetcher = Web3BalanceFetcher::new( - web3, - None, - allowance_target.address(), - H160::from_low_u64_be(1), - false, - ); - - let get_balance = || async { - fetcher - .get_balances(&[Query { - owner: trader.address(), - token: token.address(), - source: SellTokenSource::Erc20, - interactions: vec![], - }]) - .await - .into_iter() - .next() - .unwrap() - .unwrap() - }; - - assert_eq!(get_balance().await, U256::zero()); - - // Balance without approval should not affect available balance - token - .mint(trader.address(), 100.into()) - .send() - .await - .unwrap(); - assert_eq!(get_balance().await, U256::zero()); - - // Approving allowance_target should increase available balance - token - .approve(allowance_target.address(), 200.into()) - .send() - .await - .unwrap(); - assert_eq!(get_balance().await, 100.into()); - - // Spending balance should decrease available balance - token - .transfer(allowance_target.address(), 100.into()) - .send() - .await - .unwrap(); - assert_eq!(get_balance().await, U256::zero()); - } - - #[tokio::test] - #[ignore] - async fn can_transfer_testnet_vault_external_balance() { - let http = create_env_test_transport(); - let web3 = Web3::new(http); - - let accounts: Vec = web3.eth().accounts().await.expect("get accounts failed"); - let admin = Account::Local(accounts[0], None); - let trader = Account::Local(accounts[1], None); - let allowance_target = Account::Local(accounts[2], None); - - let authorizer = BalancerV2Authorizer::builder(&web3, admin.address()) - .deploy() - .await - .expect("BalancerV2Authorizer deployment failed"); - let vault = BalancerV2Vault::builder( - &web3, - authorizer.address(), - H160([0xef; 20]), // WETH address - not important for this test. - 0.into(), - 0.into(), - ) - .deploy() - .await - .expect("BalancerV2Vault deployment failed"); - - let token = ERC20Mintable::builder(&web3) - .deploy() - .await - .expect("MintableERC20 deployment failed"); - - let fetcher = Web3BalanceFetcher::new( - web3, - Some(vault.address()), - allowance_target.address(), - H160::from_low_u64_be(1), - false, - ); - - assert!(matches!( - fetcher - .can_transfer( - &Query { - token: token.address(), - owner: trader.address(), - source: SellTokenSource::External, - interactions: vec![], - }, - 100.into(), - ) - .await, - Err(TransferSimulationError::InsufficientBalance) - )); - - // Set authorization for allowance target to act as a Vault relayer - vault::grant_required_roles(&authorizer, vault.address(), allowance_target.address()) - .await - .unwrap(); - // Give the trader some balance - token - .mint(trader.address(), 1_000_000.into()) - .send() - .await - .unwrap(); - // Approve the Vault - token - .approve(vault.address(), 200.into()) - .from(trader.clone()) - .send() - .await - .unwrap(); - // Set relayer approval for the allowance target - vault - .set_relayer_approval(trader.address(), allowance_target.address(), true) - .from(trader.clone()) - .send() - .await - .unwrap(); - - assert!(fetcher - .can_transfer( - &Query { - token: token.address(), - owner: trader.address(), - source: SellTokenSource::External, - interactions: vec![], - }, - 100.into(), - ) - .await - .is_ok()); - assert!(matches!( - fetcher - .can_transfer( - &Query { - token: token.address(), - owner: trader.address(), - source: SellTokenSource::External, - interactions: vec![], - }, - 1_000_000.into(), - ) - .await, - Err(TransferSimulationError::InsufficientAllowance) - )); - } - - #[tokio::test] - #[ignore] - async fn watch_testnet_vault_external_balance() { - let http = create_env_test_transport(); - let web3 = Web3::new(http); - - let accounts: Vec = web3.eth().accounts().await.expect("get accounts failed"); - let admin = Account::Local(accounts[0], None); - let trader = Account::Local(accounts[1], None); - let allowance_target = Account::Local(accounts[2], None); - - let authorizer = BalancerV2Authorizer::builder(&web3, admin.address()) - .deploy() - .await - .expect("BalancerV2Authorizer deployment failed"); - let vault = BalancerV2Vault::builder( - &web3, - authorizer.address(), - H160([0xef; 20]), // WETH address - not important for this test. - 0.into(), - 0.into(), - ) - .deploy() - .await - .expect("BalancerV2Vault deployment failed"); - - let token = ERC20Mintable::builder(&web3) - .deploy() - .await - .expect("MintableERC20 deployment failed"); - - let fetcher = Web3BalanceFetcher::new( - web3, - Some(vault.address()), - allowance_target.address(), - H160::from_low_u64_be(1), - false, - ); - - let get_balance = || async { - fetcher - .get_balances(&[Query { - owner: trader.address(), - token: token.address(), - source: SellTokenSource::External, - interactions: vec![], - }]) - .await - .into_iter() - .next() - .unwrap() - .unwrap() - }; - - assert_eq!(get_balance().await, U256::zero()); - - // Balance without allowance and approval should not affect available balance - token - .mint(trader.address(), 100.into()) - .send() - .await - .unwrap(); - assert_eq!(get_balance().await, U256::zero()); - - // Balance without approval should not affect available balance - token - .approve(vault.address(), 50.into()) - .from(trader.clone()) - .send() - .await - .unwrap(); - assert_eq!(get_balance().await, U256::zero()); - - // Approving allowance_target as a relayer increase available balance - vault - .set_relayer_approval(trader.address(), allowance_target.address(), true) - .from(trader.clone()) - .send() - .await - .unwrap(); - assert_eq!(get_balance().await, 50.into()); - - // Spending balance should decrease available balance - token - .transfer(allowance_target.address(), 50.into()) - .from(trader.clone()) - .send() - .await - .unwrap(); - assert_eq!(get_balance().await, 50.into()); - } -} diff --git a/crates/shared/src/arguments.rs b/crates/shared/src/arguments.rs index 4d9218e7c9..ab6c0b9bef 100644 --- a/crates/shared/src/arguments.rs +++ b/crates/shared/src/arguments.rs @@ -3,11 +3,9 @@ use { crate::{ - account_balances, gas_price_estimation::GasEstimatorType, price_estimation::PriceEstimators, rate_limiter::RateLimitingStrategy, - signature_validator, sources::{ balancer_v2::BalancerFactoryKind, uniswap_v2::UniV2BaselineSourceParameters, @@ -147,12 +145,6 @@ pub struct Arguments { #[clap(flatten)] pub tenderly: tenderly_api::Arguments, - #[clap(flatten)] - pub balances: account_balances::Arguments, - - #[clap(flatten)] - pub signatures: signature_validator::Arguments, - #[clap(flatten)] pub logging: LoggingArguments, @@ -406,8 +398,6 @@ impl Display for Arguments { write!(f, "{}", self.ethrpc)?; write!(f, "{}", self.current_block)?; write!(f, "{}", self.tenderly)?; - write!(f, "{}", self.balances)?; - write!(f, "{}", self.signatures)?; writeln!(f, "log_filter: {}", self.logging.log_filter)?; writeln!( f, diff --git a/crates/shared/src/signature_validator.rs b/crates/shared/src/signature_validator.rs index a18f2b8fd3..912eccc971 100644 --- a/crates/shared/src/signature_validator.rs +++ b/crates/shared/src/signature_validator.rs @@ -1,20 +1,14 @@ -mod arguments; -mod simulation; -mod web3; - use { ethcontract::Bytes, + ethrpc::Web3, hex_literal::hex, model::interaction::InteractionData, primitive_types::H160, + std::sync::Arc, thiserror::Error, }; -pub use self::{ - arguments::*, - simulation::Validator as SimulationSignatureValidator, - web3::Web3SignatureValidator, -}; +mod simulation; /// Structure used to represent a signature. #[derive(Clone, Debug, Eq, PartialEq)] @@ -63,3 +57,19 @@ pub fn check_erc1271_result(result: Bytes<[u8; 4]>) -> Result<(), SignatureValid Err(SignatureValidationError::Invalid) } } + +/// Contracts required for signature verification simulation. +pub struct Contracts { + pub chain_id: u64, + pub settlement: H160, + pub vault_relayer: H160, +} + +/// Creates the default [`SignatureValidating`] instance. +pub fn validator(contracts: Contracts, web3: Web3) -> Arc { + Arc::new(simulation::Validator::new( + web3, + contracts.settlement, + contracts.vault_relayer, + )) +} diff --git a/crates/solver/src/run.rs b/crates/solver/src/run.rs index 3b499838d0..f4a73328dd 100644 --- a/crates/solver/src/run.rs +++ b/crates/solver/src/run.rs @@ -533,7 +533,7 @@ pub async fn run(args: Arguments) { .or_else(|| shared::network::block_interval(&network_id, chain_id)) .expect("unknown network block interval"); - let balance_fetcher = args.shared.balances.fetcher( + let balance_fetcher = account_balances::fetcher( account_balances::Contracts { chain_id, settlement: settlement_contract.address(),