From c971b6756b151ab13c1e03f681eb2c0a48c21994 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Mon, 6 Jan 2025 11:22:25 +0100 Subject: [PATCH 1/9] Replace `once-cell` and `lazy-static` with `std` types (#3208) # Description Since the functionality of `once-cell` and `lazy-static` was moved into the standard library it's the idiomatic thing to use the `std` types instead of the crates. # Changes Replaced all uses of `once-cell` and `lazy-static` with the equivalent `std` type. --- Cargo.lock | 7 ---- Cargo.toml | 2 -- crates/driver/Cargo.toml | 1 - crates/driver/src/infra/time.rs | 6 ++-- crates/e2e/src/api/zeroex.rs | 28 ++++++++-------- crates/ethrpc/Cargo.toml | 1 - crates/ethrpc/src/multicall.rs | 11 +++---- crates/model/Cargo.toml | 1 - crates/model/src/lib.rs | 24 +++++++------- crates/observe/Cargo.toml | 1 - crates/observe/src/metrics.rs | 10 ++++-- crates/shared/Cargo.toml | 1 - crates/shared/src/external_prices.rs | 10 +++--- .../shared/src/price_estimation/native/mod.rs | 5 ++- .../sources/balancer_v2/swap/fixed_point.rs | 21 ++++++------ .../swap/fixed_point/logexpmath.rs | 33 +++++++++++-------- .../sources/balancer_v2/swap/stable_math.rs | 6 ++-- .../sources/balancer_v2/swap/weighted_math.rs | 12 +++---- .../src/sources/uniswap_v2/pool_fetching.rs | 10 +++--- crates/solver/Cargo.toml | 2 -- crates/solver/src/interactions/balancer_v2.rs | 11 +++---- crates/solver/src/liquidity/slippage.rs | 12 ++++--- crates/solver/src/liquidity_collector.rs | 12 ++++--- 23 files changed, 109 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2bbe2848c..05cfd11c17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1816,7 +1816,6 @@ dependencies = [ "hyper", "indexmap 2.2.6", "itertools 0.12.1", - "lazy_static", "maplit", "mimalloc", "mockall 0.12.1", @@ -2077,7 +2076,6 @@ dependencies = [ "hex", "hex-literal", "itertools 0.12.1", - "lazy_static", "maplit", "mockall 0.12.1", "observe", @@ -3118,7 +3116,6 @@ dependencies = [ "derive_more 1.0.0", "hex", "hex-literal", - "lazy_static", "maplit", "num", "number", @@ -3331,7 +3328,6 @@ dependencies = [ "atty", "console-subscriber", "futures", - "once_cell", "pin-project-lite", "prometheus", "prometheus-metric-storage", @@ -4485,7 +4481,6 @@ dependencies = [ "humantime", "indexmap 2.2.6", "itertools 0.12.1", - "lazy_static", "maplit", "mockall 0.12.1", "model", @@ -4581,14 +4576,12 @@ dependencies = [ "hex", "hex-literal", "itertools 0.12.1", - "lazy_static", "maplit", "mockall 0.12.1", "model", "num", "number", "observe", - "once_cell", "primitive-types", "prometheus", "prometheus-metric-storage", diff --git a/Cargo.toml b/Cargo.toml index f2ce7391f6..6c819517e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,11 +28,9 @@ humantime-serde = "1.1.1" hyper = "0.14.29" indexmap = "2.2.6" itertools = "0.12.1" -lazy_static = "1.4.0" maplit = "1.0.2" mockall = "0.12.1" num = "0.4.3" -once_cell = "1.19.0" primitive-types = "0.12" prometheus = "0.13.4" prometheus-metric-storage = "0.5.0" diff --git a/crates/driver/Cargo.toml b/crates/driver/Cargo.toml index 0675c6a046..83c2a1983d 100644 --- a/crates/driver/Cargo.toml +++ b/crates/driver/Cargo.toml @@ -35,7 +35,6 @@ hex-literal = { workspace = true } humantime = { workspace = true } humantime-serde = { workspace = true } hyper = { workspace = true } -lazy_static = { workspace = true } indexmap = { workspace = true, features = ["serde"] } itertools = { workspace = true } mimalloc = { workspace = true } diff --git a/crates/driver/src/infra/time.rs b/crates/driver/src/infra/time.rs index f954ea3f64..e5bc83ee73 100644 --- a/crates/driver/src/infra/time.rs +++ b/crates/driver/src/infra/time.rs @@ -7,7 +7,7 @@ pub fn now() -> chrono::DateTime { /// During tests, the time is fixed. #[cfg(test)] pub fn now() -> chrono::DateTime { - use std::sync::OnceLock; - static TIME: OnceLock> = OnceLock::new(); - TIME.get_or_init(chrono::Utc::now).to_owned() + use std::sync::LazyLock; + static TIME: LazyLock> = LazyLock::new(chrono::Utc::now); + *TIME } diff --git a/crates/e2e/src/api/zeroex.rs b/crates/e2e/src/api/zeroex.rs index bef51dffe9..f3db4a4d73 100644 --- a/crates/e2e/src/api/zeroex.rs +++ b/crates/e2e/src/api/zeroex.rs @@ -3,17 +3,14 @@ use { autopilot::domain::eth::U256, chrono::{DateTime, NaiveDateTime, Utc}, driver::domain::eth::H256, - ethcontract::{ - common::abi::{encode, Token}, - private::lazy_static, - }, + ethcontract::common::abi::{encode, Token}, hex_literal::hex, model::DomainSeparator, shared::{ zeroex_api, zeroex_api::{Order, OrderMetadata, OrderRecord, ZeroExSignature}, }, - std::net::SocketAddr, + std::{net::SocketAddr, sync::LazyLock}, warp::{Filter, Reply}, web3::{signing, types::H160}, }; @@ -155,18 +152,19 @@ struct ZeroExDomainSeparator([u8; 32]); impl ZeroExDomainSeparator { // See pub fn new(chain_id: u64, contract_addr: H160) -> Self { - lazy_static! { - /// The EIP-712 domain name used for computing the domain separator. - static ref DOMAIN_NAME: [u8; 32] = signing::keccak256(b"ZeroEx"); + /// The EIP-712 domain name used for computing the domain separator. + static DOMAIN_NAME: LazyLock<[u8; 32]> = LazyLock::new(|| signing::keccak256(b"ZeroEx")); - /// The EIP-712 domain version used for computing the domain separator. - static ref DOMAIN_VERSION: [u8; 32] = signing::keccak256(b"1.0.0"); + /// The EIP-712 domain version used for computing the domain separator. + static DOMAIN_VERSION: LazyLock<[u8; 32]> = LazyLock::new(|| signing::keccak256(b"1.0.0")); + + /// The EIP-712 domain type used computing the domain separator. + static DOMAIN_TYPE_HASH: LazyLock<[u8; 32]> = LazyLock::new(|| { + signing::keccak256( + b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", + ) + }); - /// The EIP-712 domain type used computing the domain separator. - static ref DOMAIN_TYPE_HASH: [u8; 32] = signing::keccak256( - b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", - ); - } let abi_encode_string = encode(&[ Token::FixedBytes((*DOMAIN_TYPE_HASH).into()), Token::FixedBytes((*DOMAIN_NAME).into()), diff --git a/crates/ethrpc/Cargo.toml b/crates/ethrpc/Cargo.toml index a6e628dabc..fa6a0f9b24 100644 --- a/crates/ethrpc/Cargo.toml +++ b/crates/ethrpc/Cargo.toml @@ -16,7 +16,6 @@ async-trait = { workspace = true } futures = { workspace = true } hex = { workspace = true } hex-literal = { workspace = true } -lazy_static = { workspace = true } mockall = { workspace = true } observe = { path = "../observe" } primitive-types = { workspace = true } diff --git a/crates/ethrpc/src/multicall.rs b/crates/ethrpc/src/multicall.rs index 38d1e72f03..855d5b753e 100644 --- a/crates/ethrpc/src/multicall.rs +++ b/crates/ethrpc/src/multicall.rs @@ -8,8 +8,7 @@ use { tokens::{self, Tokenize as _}, }, hex_literal::hex, - lazy_static::lazy_static, - std::iter, + std::{iter, sync::LazyLock}, web3::{ self, api::Eth, @@ -160,12 +159,12 @@ fn decode(len: usize, return_data: Bytes) -> Vec, ExecutionError> type ReturnData = Vec<(bool, ethcontract::Bytes>)>; fn decode_return_data(len: usize, return_data: Bytes) -> Result { - lazy_static! { - static ref KIND: [ParamType; 1] = [ParamType::Array(Box::new(ParamType::Tuple(vec![ + static KIND: LazyLock<[ParamType; 1]> = LazyLock::new(|| { + [ParamType::Array(Box::new(ParamType::Tuple(vec![ ParamType::Bool, ParamType::Bytes, - ])),)]; - } + ])))] + }); let tokens = ethabi::decode(&*KIND, &return_data.0)?; let (results,) = <(ReturnData,)>::from_token(Token::Tuple(tokens))?; diff --git a/crates/model/Cargo.toml b/crates/model/Cargo.toml index d6a8f19af1..b27956955f 100644 --- a/crates/model/Cargo.toml +++ b/crates/model/Cargo.toml @@ -17,7 +17,6 @@ chrono = { workspace = true, features = ["serde", "clock"] } derive_more = { workspace = true } hex = { workspace = true, default-features = false } hex-literal = { workspace = true } -lazy_static = { workspace = true } number = { path = "../number" } num = { workspace = true } primitive-types = { workspace = true } diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index 044bb67bf5..34f8ab64c0 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -12,9 +12,8 @@ pub mod trade; use { hex::{FromHex, FromHexError}, - lazy_static::lazy_static, primitive_types::H160, - std::fmt, + std::{fmt, sync::LazyLock}, web3::{ ethabi::{encode, Token}, signing, @@ -115,18 +114,19 @@ impl std::fmt::Debug for DomainSeparator { impl DomainSeparator { pub fn new(chain_id: u64, contract_address: H160) -> Self { - lazy_static! { - /// The EIP-712 domain name used for computing the domain separator. - static ref DOMAIN_NAME: [u8; 32] = signing::keccak256(b"Gnosis Protocol"); + /// The EIP-712 domain name used for computing the domain separator. + static DOMAIN_NAME: LazyLock<[u8; 32]> = + LazyLock::new(|| signing::keccak256(b"Gnosis Protocol")); - /// The EIP-712 domain version used for computing the domain separator. - static ref DOMAIN_VERSION: [u8; 32] = signing::keccak256(b"v2"); + /// The EIP-712 domain version used for computing the domain separator. + static DOMAIN_VERSION: LazyLock<[u8; 32]> = LazyLock::new(|| signing::keccak256(b"v2")); - /// The EIP-712 domain type used computing the domain separator. - static ref DOMAIN_TYPE_HASH: [u8; 32] = signing::keccak256( - b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", - ); - } + /// The EIP-712 domain type used computing the domain separator. + static DOMAIN_TYPE_HASH: LazyLock<[u8; 32]> = LazyLock::new(|| { + signing::keccak256( + b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)", + ) + }); let abi_encode_string = encode(&[ Token::Uint((*DOMAIN_TYPE_HASH).into()), Token::Uint((*DOMAIN_NAME).into()), diff --git a/crates/observe/Cargo.toml b/crates/observe/Cargo.toml index d02cbfc0ce..f2b1029ef0 100644 --- a/crates/observe/Cargo.toml +++ b/crates/observe/Cargo.toml @@ -10,7 +10,6 @@ atty = "0.2" async-trait = { workspace = true } console-subscriber = "0.3.0" futures = { workspace = true } -once_cell = { workspace = true } pin-project-lite = "0.2.14" prometheus = { workspace = true } prometheus-metric-storage = { workspace = true } diff --git a/crates/observe/src/metrics.rs b/crates/observe/src/metrics.rs index 6afb4175e0..b62023590c 100644 --- a/crates/observe/src/metrics.rs +++ b/crates/observe/src/metrics.rs @@ -1,13 +1,17 @@ use { - once_cell::sync::OnceCell, prometheus::Encoder, - std::{collections::HashMap, convert::Infallible, net::SocketAddr, sync::Arc}, + std::{ + collections::HashMap, + convert::Infallible, + net::SocketAddr, + sync::{Arc, OnceLock}, + }, tokio::task::{self, JoinHandle}, warp::{Filter, Rejection, Reply}, }; /// Global metrics registry used by all components. -static REGISTRY: OnceCell = OnceCell::new(); +static REGISTRY: OnceLock = OnceLock::new(); /// Configure global metrics registry. /// diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 492b6bfb6e..87cb190f1f 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -34,7 +34,6 @@ hex-literal = { workspace = true } humantime = { workspace = true } indexmap = { workspace = true } itertools = { workspace = true } -lazy_static = { workspace = true } maplit = { workspace = true } mockall = { workspace = true } model = { path = "../model" } diff --git a/crates/shared/src/external_prices.rs b/crates/shared/src/external_prices.rs index f7fb0b427b..6c3dc66c84 100644 --- a/crates/shared/src/external_prices.rs +++ b/crates/shared/src/external_prices.rs @@ -9,10 +9,12 @@ use { crate::conversions::U256Ext, anyhow::{bail, Result}, ethcontract::{H160, U256}, - lazy_static::lazy_static, model::order::BUY_ETH_ADDRESS, num::{BigInt, BigRational, One as _, ToPrimitive as _}, - std::collections::{BTreeMap, HashMap}, + std::{ + collections::{BTreeMap, HashMap}, + sync::LazyLock, + }, }; /// A collection of external prices used for converting token amounts to native @@ -72,9 +74,7 @@ impl Default for ExternalPrices { } } -lazy_static! { - static ref UNIT: BigInt = BigInt::from(1_000_000_000_000_000_000_u128); -} +static UNIT: LazyLock = LazyLock::new(|| BigInt::from(1_000_000_000_000_000_000_u128)); /// Converts a token price from the orderbook API `/auction` endpoint to an /// native token exchange rate. diff --git a/crates/shared/src/price_estimation/native/mod.rs b/crates/shared/src/price_estimation/native/mod.rs index 31ca840575..9a81d44538 100644 --- a/crates/shared/src/price_estimation/native/mod.rs +++ b/crates/shared/src/price_estimation/native/mod.rs @@ -1,12 +1,11 @@ use { crate::price_estimation::{PriceEstimating, PriceEstimationError, Query}, bigdecimal::{BigDecimal, ToPrimitive}, - cached::once_cell::sync::Lazy, futures::FutureExt, model::order::OrderKind, number::nonzero::U256 as NonZeroU256, primitive_types::{H160, U256}, - std::sync::Arc, + std::sync::{Arc, LazyLock}, }; mod coingecko; @@ -19,7 +18,7 @@ pub type NativePriceEstimateResult = Result; /// Convert from normalized price to floating point price pub fn from_normalized_price(price: BigDecimal) -> Option { - static ONE_E18: Lazy = Lazy::new(|| BigDecimal::try_from(1e18).unwrap()); + static ONE_E18: LazyLock = LazyLock::new(|| BigDecimal::try_from(1e18).unwrap()); // Divide by 1e18 to reverse the multiplication by 1e18 let normalized_price = price / ONE_E18.clone(); diff --git a/crates/shared/src/sources/balancer_v2/swap/fixed_point.rs b/crates/shared/src/sources/balancer_v2/swap/fixed_point.rs index f4edd33701..94015f9ed9 100644 --- a/crates/shared/src/sources/balancer_v2/swap/fixed_point.rs +++ b/crates/shared/src/sources/balancer_v2/swap/fixed_point.rs @@ -7,13 +7,13 @@ use { super::error::Error, anyhow::{bail, ensure, Context, Result}, ethcontract::U256, - lazy_static::lazy_static, num::{BigInt, BigRational}, number::conversions::{big_int_to_u256, u256_to_big_int}, std::{ convert::TryFrom, fmt::{self, Debug, Formatter}, str::FromStr, + sync::LazyLock, }, }; @@ -27,16 +27,13 @@ mod logexpmath; /// including error codes, from which the name (Balancer Fixed Point). pub struct Bfp(U256); -lazy_static! { - static ref ONE_18: U256 = U256::exp10(18); - static ref ONE_18_BIGINT: BigInt = u256_to_big_int(&ONE_18); - static ref ZERO: Bfp = Bfp(U256::zero()); - static ref EPSILON: Bfp = Bfp(U256::one()); - static ref ONE: Bfp = Bfp(*ONE_18); - static ref TWO: Bfp = Bfp(*ONE_18 * 2); - static ref FOUR: Bfp = Bfp(*ONE_18 * 4); - static ref MAX_POW_RELATIVE_ERROR: Bfp = Bfp(10000_usize.into()); -} +static ONE_18: LazyLock = LazyLock::new(|| U256::exp10(18)); +static ONE_18_BIGINT: LazyLock = LazyLock::new(|| u256_to_big_int(&ONE_18)); +static ZERO: LazyLock = LazyLock::new(|| Bfp(U256::zero())); +static ONE: LazyLock = LazyLock::new(|| Bfp(*ONE_18)); +static TWO: LazyLock = LazyLock::new(|| Bfp(*ONE_18 * 2)); +static FOUR: LazyLock = LazyLock::new(|| Bfp(*ONE_18 * 4)); +static MAX_POW_RELATIVE_ERROR: LazyLock = LazyLock::new(|| Bfp(10000_usize.into())); impl From for Bfp { fn from(num: usize) -> Self { @@ -223,6 +220,8 @@ mod tests { num::{BigInt, One, Zero}, }; + static EPSILON: LazyLock = LazyLock::new(|| Bfp(U256::one())); + #[test] fn parsing() { assert_eq!("1".parse::().unwrap(), Bfp::one()); diff --git a/crates/shared/src/sources/balancer_v2/swap/fixed_point/logexpmath.rs b/crates/shared/src/sources/balancer_v2/swap/fixed_point/logexpmath.rs index 660049a026..302053b7f6 100644 --- a/crates/shared/src/sources/balancer_v2/swap/fixed_point/logexpmath.rs +++ b/crates/shared/src/sources/balancer_v2/swap/fixed_point/logexpmath.rs @@ -5,27 +5,32 @@ use { super::super::error::Error, ethcontract::{I256, U256}, - lazy_static::lazy_static, - std::convert::TryFrom, + std::{convert::TryFrom, sync::LazyLock}, }; /// Fixed point number stored in a type of bit size 256 that stores exactly 18 /// decimal digits. type Ufixed256x18 = U256; -lazy_static! { - static ref ONE_18: I256 = I256::exp10(18); - static ref ONE_20: I256 = I256::exp10(20); - static ref ONE_36: I256 = I256::exp10(36); - static ref UFIXED256X18_ONE: Ufixed256x18 = U256::try_from(*ONE_18).unwrap(); - static ref MAX_NATURAL_EXPONENT: I256 = ONE_18.checked_mul(I256::from(130_i128)).unwrap(); - static ref MIN_NATURAL_EXPONENT: I256 = ONE_18.checked_mul(I256::from(-41_i128)).unwrap(); - static ref LN_36_LOWER_BOUND: I256 = ONE_18.checked_sub(I256::exp10(17)).unwrap(); - static ref LN_36_UPPER_BOUND: I256 = ONE_18.checked_add(I256::exp10(17)).unwrap(); - static ref MILD_EXPONENT_BOUND: Ufixed256x18 = (U256::one() << 254_u32) +static ONE_18: LazyLock = LazyLock::new(|| I256::exp10(18)); +static ONE_20: LazyLock = LazyLock::new(|| I256::exp10(20)); +static ONE_36: LazyLock = LazyLock::new(|| I256::exp10(36)); +static UFIXED256X18_ONE: LazyLock = + LazyLock::new(|| U256::try_from(*ONE_18).unwrap()); +static MAX_NATURAL_EXPONENT: LazyLock = + LazyLock::new(|| ONE_18.checked_mul(I256::from(130_i128)).unwrap()); +static MIN_NATURAL_EXPONENT: LazyLock = + LazyLock::new(|| ONE_18.checked_mul(I256::from(-41_i128)).unwrap()); +static LN_36_LOWER_BOUND: LazyLock = + LazyLock::new(|| ONE_18.checked_sub(I256::exp10(17)).unwrap()); +static LN_36_UPPER_BOUND: LazyLock = + LazyLock::new(|| ONE_18.checked_add(I256::exp10(17)).unwrap()); +static MILD_EXPONENT_BOUND: LazyLock = LazyLock::new(|| { + (U256::one() << 254_u32) .checked_div(U256::try_from(*ONE_20).unwrap()) - .unwrap(); -} + .unwrap() +}); + fn constant_x_20(i: u32) -> I256 { match i { 2 => 3_200_000_000_000_000_000_000_i128, diff --git a/crates/shared/src/sources/balancer_v2/swap/stable_math.rs b/crates/shared/src/sources/balancer_v2/swap/stable_math.rs index bedd679b78..f7610ef2b0 100644 --- a/crates/shared/src/sources/balancer_v2/swap/stable_math.rs +++ b/crates/shared/src/sources/balancer_v2/swap/stable_math.rs @@ -6,12 +6,10 @@ use { super::error::Error, crate::sources::balancer_v2::swap::{fixed_point::Bfp, math::BalU256}, ethcontract::U256, - lazy_static::lazy_static, + std::sync::LazyLock, }; -lazy_static! { - pub static ref AMP_PRECISION: U256 = U256::from(1000); -} +pub static AMP_PRECISION: LazyLock = LazyLock::new(|| U256::from(1000)); /// https://github.com/balancer-labs/balancer-v2-monorepo/blob/9eb7e44a4e9ebbadfe3c6242a086118298cadc9f/pkg/pool-stable-phantom/contracts/StableMath.sol#L57-L119 fn calculate_invariant(amplification_parameter: U256, balances: &[Bfp]) -> Result { diff --git a/crates/shared/src/sources/balancer_v2/swap/weighted_math.rs b/crates/shared/src/sources/balancer_v2/swap/weighted_math.rs index ae9fb72268..5b0aafa701 100644 --- a/crates/shared/src/sources/balancer_v2/swap/weighted_math.rs +++ b/crates/shared/src/sources/balancer_v2/swap/weighted_math.rs @@ -5,16 +5,14 @@ use { super::{error::Error, fixed_point::Bfp}, ethcontract::U256, - lazy_static::lazy_static, + std::sync::LazyLock, }; // https://github.com/balancer-labs/balancer-v2-monorepo/blob/6c9e24e22d0c46cca6dd15861d3d33da61a60b98/pkg/core/contracts/pools/weighted/WeightedMath.sol#L36-L37 -lazy_static! { - static ref MAX_IN_RATIO: Bfp = - Bfp::from_wei(U256::exp10(17).checked_mul(3_u32.into()).unwrap()); - static ref MAX_OUT_RATIO: Bfp = - Bfp::from_wei(U256::exp10(17).checked_mul(3_u32.into()).unwrap()); -} +static MAX_IN_RATIO: LazyLock = + LazyLock::new(|| Bfp::from_wei(U256::exp10(17).checked_mul(3_u32.into()).unwrap())); +static MAX_OUT_RATIO: LazyLock = + LazyLock::new(|| Bfp::from_wei(U256::exp10(17).checked_mul(3_u32.into()).unwrap())); /// https://github.com/balancer-labs/balancer-v2-monorepo/blob/6c9e24e22d0c46cca6dd15861d3d33da61a60b98/pkg/core/contracts/pools/weighted/WeightedMath.sol#L69-L100 /// It is not possible for the following addition balance_in.add(amount_in) to diff --git a/crates/shared/src/sources/uniswap_v2/pool_fetching.rs b/crates/shared/src/sources/uniswap_v2/pool_fetching.rs index 45bc54c536..b7966663f9 100644 --- a/crates/shared/src/sources/uniswap_v2/pool_fetching.rs +++ b/crates/shared/src/sources/uniswap_v2/pool_fetching.rs @@ -10,15 +10,17 @@ use { }, model::TokenPair, num::rational::Ratio, - std::{collections::HashSet, sync::RwLock, time::Duration}, + std::{ + collections::HashSet, + sync::{LazyLock, RwLock}, + time::Duration, + }, ttl_cache::TtlCache, }; const POOL_SWAP_GAS_COST: usize = 60_000; -lazy_static::lazy_static! { - static ref POOL_MAX_RESERVES: U256 = U256::from((1u128 << 112) - 1); -} +static POOL_MAX_RESERVES: LazyLock = LazyLock::new(|| U256::from((1u128 << 112) - 1)); /// This type denotes `(reserve_a, reserve_b, token_b)` where /// `reserve_a` refers to the reserve of the excluded token. diff --git a/crates/solver/Cargo.toml b/crates/solver/Cargo.toml index aa80153249..fdb0d4d768 100644 --- a/crates/solver/Cargo.toml +++ b/crates/solver/Cargo.toml @@ -22,13 +22,11 @@ observe = { path = "../observe" } hex = { workspace = true } hex-literal = { workspace = true } itertools = { workspace = true } -lazy_static = { workspace = true } maplit = { workspace = true } mockall = { workspace = true } model = { path = "../model" } num = { workspace = true } number = { path = "../number" } -once_cell = { workspace = true } primitive-types = { workspace = true } prometheus = { workspace = true } prometheus-metric-storage = { workspace = true } diff --git a/crates/solver/src/interactions/balancer_v2.rs b/crates/solver/src/interactions/balancer_v2.rs index abf6f2b4c1..6239843568 100644 --- a/crates/solver/src/interactions/balancer_v2.rs +++ b/crates/solver/src/interactions/balancer_v2.rs @@ -6,6 +6,7 @@ use { http_solver::model::TokenAmount, interaction::{EncodedInteraction, Interaction}, }, + std::sync::LazyLock, }; #[derive(Clone, Debug)] @@ -18,12 +19,10 @@ pub struct BalancerSwapGivenOutInteraction { pub user_data: Bytes>, } -lazy_static::lazy_static! { - /// An impossibly distant future timestamp. Note that we use `0x80000...00` - /// as the value so that it is mostly 0's to save small amounts of gas on - /// calldata. - pub static ref NEVER: U256 = U256::from(1) << 255; -} +/// An impossibly distant future timestamp. Note that we use `0x80000...00` +/// as the value so that it is mostly 0's to save small amounts of gas on +/// calldata. +pub static NEVER: LazyLock = LazyLock::new(|| U256::from(1) << 255); impl BalancerSwapGivenOutInteraction { pub fn encode_swap(&self) -> EncodedInteraction { diff --git a/crates/solver/src/liquidity/slippage.rs b/crates/solver/src/liquidity/slippage.rs index a4185aa909..60940314c4 100644 --- a/crates/solver/src/liquidity/slippage.rs +++ b/crates/solver/src/liquidity/slippage.rs @@ -5,9 +5,8 @@ use { anyhow::{Context as _, Result}, ethcontract::U256, num::{BigInt, BigRational, CheckedDiv, Integer as _, ToPrimitive as _}, - once_cell::sync::OnceCell, shared::{external_prices::ExternalPrices, http_solver::model::TokenAmount}, - std::{borrow::Cow, cmp}, + std::{borrow::Cow, cmp, sync::LazyLock}, }; /// Constant maximum slippage of 10 BPS (0.1%) to use for on-chain liquidity. @@ -86,9 +85,12 @@ impl SlippageContext<'_> { impl Default for SlippageContext<'static> { fn default() -> Self { - static CONTEXT: OnceCell<(ExternalPrices, SlippageCalculator)> = OnceCell::new(); - let (prices, calculator) = CONTEXT.get_or_init(Default::default); - Self { prices, calculator } + static CONTEXT: LazyLock<(ExternalPrices, SlippageCalculator)> = + LazyLock::new(Default::default); + Self { + prices: &CONTEXT.0, + calculator: &CONTEXT.1, + } } } diff --git a/crates/solver/src/liquidity_collector.rs b/crates/solver/src/liquidity_collector.rs index 52a3fed2bc..eefd0c6ccd 100644 --- a/crates/solver/src/liquidity_collector.rs +++ b/crates/solver/src/liquidity_collector.rs @@ -2,9 +2,13 @@ use { crate::liquidity::Liquidity, anyhow::Result, model::TokenPair, - once_cell::sync::OnceCell, shared::{baseline_solver::BaseTokens, recent_block_cache::Block}, - std::{collections::HashSet, future::Future, sync::Arc, time::Duration}, + std::{ + collections::HashSet, + future::Future, + sync::{Arc, OnceLock}, + time::Duration, + }, tracing::Instrument, }; @@ -50,7 +54,7 @@ impl LiquidityCollecting for LiquidityCollector { /// succeeds. Until the liquidity source has been initialised no liquidity will /// be provided. pub struct BackgroundInitLiquiditySource { - liquidity_source: Arc>, + liquidity_source: Arc>, } impl BackgroundInitLiquiditySource { @@ -66,7 +70,7 @@ impl BackgroundInitLiquiditySource { .liquidity_enabled .with_label_values(&[label]) .set(0); - let liquidity_source = Arc::new(OnceCell::new()); + let liquidity_source = Arc::new(OnceLock::new()); let inner = liquidity_source.clone(); let inner_label = label.to_owned(); tokio::task::spawn( From 0db8f43f9810124cd1dcca45d4c53d3a4ffb9eaf Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Mon, 6 Jan 2025 11:31:11 +0100 Subject: [PATCH 2/9] Remove `ttl-cache` dependency (#3207) # Description We currently have 2 dependencies for size and time based caching strategies. Given that we only have 1 usage of `ttl-cache` I replaced it with `cached` to reduce the total number of dependencies. One minor downside is that this now requires to take one more write lock but since there shouldn't be significant contention on the lock to begin with that should be okay. --- Cargo.lock | 16 ---------------- crates/shared/Cargo.toml | 1 - .../src/sources/uniswap_v2/pool_fetching.rs | 14 ++++++-------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05cfd11c17..cdeebd27b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2925,12 +2925,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -4506,7 +4500,6 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "ttl_cache", "url", "web3", ] @@ -5413,15 +5406,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttl_cache" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4189890526f0168710b6ee65ceaedf1460c48a14318ceec933cb26baa492096a" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "typenum" version = "1.17.0" diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 87cb190f1f..622f3cf536 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -22,7 +22,6 @@ contracts = { path = "../contracts" } dashmap = { workspace = true } database = { path = "../database" } derive_more = { workspace = true } -ttl_cache = "0.5" derivative = { workspace = true } ethcontract = { workspace = true } ethrpc = { path = "../ethrpc" } diff --git a/crates/shared/src/sources/uniswap_v2/pool_fetching.rs b/crates/shared/src/sources/uniswap_v2/pool_fetching.rs index b7966663f9..377ecdcc6d 100644 --- a/crates/shared/src/sources/uniswap_v2/pool_fetching.rs +++ b/crates/shared/src/sources/uniswap_v2/pool_fetching.rs @@ -2,6 +2,7 @@ use { super::pair_provider::PairProvider, crate::{baseline_solver::BaselineSolvable, ethrpc::Web3, recent_block_cache::Block}, anyhow::Result, + cached::{Cached, TimedCache}, contracts::{errors::EthcontractErrorType, IUniswapLikePair, ERC20}, ethcontract::{errors::MethodError, BlockId, H160, U256}, futures::{ @@ -15,7 +16,6 @@ use { sync::{LazyLock, RwLock}, time::Duration, }, - ttl_cache::TtlCache, }; const POOL_SWAP_GAS_COST: usize = 60_000; @@ -184,8 +184,7 @@ impl BaselineSolvable for Pool { pub struct PoolFetcher { pub pool_reader: Reader, pub web3: Web3, - pub cache_time: Duration, - pub non_existent_pools: RwLock>, + pub non_existent_pools: RwLock>, } impl PoolFetcher { @@ -193,8 +192,7 @@ impl PoolFetcher { Self { pool_reader: reader, web3, - cache_time, - non_existent_pools: RwLock::new(TtlCache::new(usize::MAX)), + non_existent_pools: RwLock::new(TimedCache::with_lifespan(cache_time.as_secs())), } } } @@ -207,8 +205,8 @@ where async fn fetch(&self, token_pairs: HashSet, at_block: Block) -> Result> { let mut token_pairs: Vec<_> = token_pairs.into_iter().collect(); { - let non_existent_pools = self.non_existent_pools.read().unwrap(); - token_pairs.retain(|pair| !non_existent_pools.contains_key(pair)); + let mut non_existent_pools = self.non_existent_pools.write().unwrap(); + token_pairs.retain(|pair| non_existent_pools.cache_get(pair).is_none()); } let block = BlockId::Number(at_block.into()); let futures = token_pairs @@ -230,7 +228,7 @@ where tracing::debug!(token_pairs = ?new_missing_pairs, "stop indexing liquidity"); let mut non_existent_pools = self.non_existent_pools.write().unwrap(); for pair in new_missing_pairs { - non_existent_pools.insert(pair, (), self.cache_time); + non_existent_pools.cache_set(pair, ()); } } Ok(pools) From 07f4db6a02f8c263319c94e018a1195ddbe96b72 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Mon, 6 Jan 2025 11:37:28 +0100 Subject: [PATCH 3/9] Move `time` dependency into `observe` crate (#3209) # Description Moves the `time` dependency into the `observe` crate because it's only used there. Also updates it to the latest version. --- Cargo.lock | 8 ++++---- Cargo.toml | 1 - crates/observe/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdeebd27b2..d91055a2bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5063,9 +5063,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -5086,9 +5086,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", diff --git a/Cargo.toml b/Cargo.toml index 6c819517e9..fbbd160162 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ serde_with = "3.8.1" sqlx = { version = "0.7", default-features = false, features = ["runtime-tokio", "tls-native-tls", "bigdecimal", "chrono", "postgres", "macros"] } strum = { version = "0.26.2", features = ["derive"] } tempfile = "3.10.1" -time = { version = "0.3.36", features = ["macros"] } thiserror = "1.0.61" toml = "0.8.14" tokio = { version = "1.38.0", features = ["tracing"] } diff --git a/crates/observe/Cargo.toml b/crates/observe/Cargo.toml index f2b1029ef0..75ebe24279 100644 --- a/crates/observe/Cargo.toml +++ b/crates/observe/Cargo.toml @@ -13,7 +13,7 @@ futures = { workspace = true } pin-project-lite = "0.2.14" prometheus = { workspace = true } prometheus-metric-storage = { workspace = true } -time = { workspace = true } +time = { version = "0.3.37", features = ["macros"] } tokio = { workspace = true, features = [ "fs" ] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "time"] } From 4d8206811464aaf3f9ac30c0c15a689f99ae91dd Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 6 Jan 2025 11:12:01 +0000 Subject: [PATCH 4/9] Get token first trade block API (#3197) # Description This is a pre-requisite for improving `internal buffers at risk` alert by eliminating false positives. The idea is to provide the token's first trade timestamp so https://github.com/cowprotocol/tenderly-alerts/ can then decide whether the token should be ignored. Currently, there is no way to return the timestamp since the `order_events` table gets cleaned up periodically. Technically, it can be used to get a timestamp, but the result will be useless if a token was deployed more than 30 days ago and wasn't traded for the last 30 days. Instead, the block number is returned, so Tenderly RPC must be used to fetch the block's timestamp. Otherwise, this would require access to an archive node from the orderbook side. ## Changes For some reason, this query takes around 20s on mainnet-prod for avg-popular tokens. Unfortunately, this couldn't be reproduced locally. I assume this is related to available resources on prod. I added indexes that improved the query speed ~x2.5 times. ## Caching To avoid querying the DB for the same tokens too often, I would consider introducing caching on the NGINX side rather than in memory since we often have multiple orderbook instances. Also, the first trade block never changes and can be kept in the NGINX cache forever. All the errors won't be kept in the cache. Requires an infra PR. ## How to test The query is tested on prod and locally. This would require further testing on prod by collecting metrics and adjusting resources. Potentially, `work_mem`, `max_parallel_workers_per_gather`, etc. --- crates/database/src/trades.rs | 52 +++++++++++++++++++ crates/orderbook/src/api.rs | 7 ++- .../orderbook/src/api/get_token_metadata.rs | 31 +++++++++++ crates/orderbook/src/database/orders.rs | 23 +++++++- crates/orderbook/src/dto/mod.rs | 8 +++ database/sql/V077__orders_token_indexes.sql | 3 ++ 6 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 crates/orderbook/src/api/get_token_metadata.rs create mode 100644 database/sql/V077__orders_token_indexes.sql diff --git a/crates/database/src/trades.rs b/crates/database/src/trades.rs index 36c4c07dfd..38d2ca06d0 100644 --- a/crates/database/src/trades.rs +++ b/crates/database/src/trades.rs @@ -106,6 +106,31 @@ AND t.log_index BETWEEN (SELECT * from previous_settlement) AND $2 .await } +pub async fn token_first_trade_block( + ex: &mut PgConnection, + token: Address, +) -> Result, sqlx::Error> { + const QUERY: &str = r#" +SELECT MIN(sub.block_number) AS earliest_block +FROM ( + SELECT MIN(t.block_number) AS block_number + FROM trades t + JOIN orders o ON t.order_uid = o.uid + WHERE o.sell_token = $1 OR o.buy_token = $1 + + UNION ALL + + SELECT MIN(t.block_number) AS block_number + FROM trades t + JOIN jit_orders j ON t.order_uid = j.uid + WHERE j.sell_token = $1 OR j.buy_token = $1 +) AS sub +"#; + + let (block_number,) = sqlx::query_as(QUERY).bind(token).fetch_one(ex).await?; + Ok(block_number) +} + #[cfg(test)] mod tests { use { @@ -579,4 +604,31 @@ mod tests { }] ); } + + #[tokio::test] + #[ignore] + async fn postgres_token_first_trade_block() { + let mut db = PgConnection::connect("postgresql://").await.unwrap(); + let mut db = db.begin().await.unwrap(); + crate::clear_DANGER_(&mut db).await.unwrap(); + + let token = Default::default(); + assert_eq!(token_first_trade_block(&mut db, token).await.unwrap(), None); + + let (owners, order_ids) = generate_owners_and_order_ids(2, 2).await; + let event_index_a = EventIndex { + block_number: 123, + log_index: 0, + }; + let event_index_b = EventIndex { + block_number: 124, + log_index: 0, + }; + add_order_and_trade(&mut db, owners[0], order_ids[0], event_index_a, None, None).await; + add_order_and_trade(&mut db, owners[1], order_ids[1], event_index_b, None, None).await; + assert_eq!( + token_first_trade_block(&mut db, token).await.unwrap(), + Some(123) + ); + } } diff --git a/crates/orderbook/src/api.rs b/crates/orderbook/src/api.rs index a272950148..8eb7321f1f 100644 --- a/crates/orderbook/src/api.rs +++ b/crates/orderbook/src/api.rs @@ -23,6 +23,7 @@ mod get_order_by_uid; mod get_order_status; mod get_orders_by_tx; mod get_solver_competition; +mod get_token_metadata; mod get_total_surplus; mod get_trades; mod get_user_orders; @@ -105,7 +106,11 @@ pub fn handle_all_routes( ), ( "v1/get_total_surplus", - box_filter(get_total_surplus::get(database)), + box_filter(get_total_surplus::get(database.clone())), + ), + ( + "v1/get_token_metadata", + box_filter(get_token_metadata::get_token_metadata(database)), ), ]; diff --git a/crates/orderbook/src/api/get_token_metadata.rs b/crates/orderbook/src/api/get_token_metadata.rs new file mode 100644 index 0000000000..a08ec3a09d --- /dev/null +++ b/crates/orderbook/src/api/get_token_metadata.rs @@ -0,0 +1,31 @@ +use { + crate::database::Postgres, + hyper::StatusCode, + primitive_types::H160, + std::convert::Infallible, + warp::{reply, Filter, Rejection}, +}; + +fn get_native_prices_request() -> impl Filter + Clone { + warp::path!("v1" / "token" / H160 / "metadata").and(warp::get()) +} + +pub fn get_token_metadata( + db: Postgres, +) -> impl Filter + Clone { + get_native_prices_request().and_then(move |token: H160| { + let db = db.clone(); + async move { + let result = db.token_metadata(&token).await; + let response = match result { + Ok(metadata) => reply::with_status(reply::json(&metadata), StatusCode::OK), + Err(err) => { + tracing::error!(?err, ?token, "Failed to fetch token's first trade block"); + crate::api::internal_error_reply() + } + }; + + Result::<_, Infallible>::Ok(response) + } + }) +} diff --git a/crates/orderbook/src/database/orders.rs b/crates/orderbook/src/database/orders.rs index d077b1c8a2..51bce8a8c6 100644 --- a/crates/orderbook/src/database/orders.rs +++ b/crates/orderbook/src/database/orders.rs @@ -1,6 +1,6 @@ use { super::Postgres, - crate::orderbook::AddOrderError, + crate::{dto::TokenMetadata, orderbook::AddOrderError}, anyhow::{Context as _, Result}, app_data::AppDataHash, async_trait::async_trait, @@ -492,6 +492,27 @@ impl Postgres { .map(full_order_into_model_order) .collect::>>() } + + pub async fn token_metadata(&self, token: &H160) -> Result { + let timer = super::Metrics::get() + .database_queries + .with_label_values(&["token_first_trade_block"]) + .start_timer(); + + let mut ex = self.pool.acquire().await?; + let block_number = database::trades::token_first_trade_block(&mut ex, ByteArray(token.0)) + .await + .map_err(anyhow::Error::from)? + .map(u32::try_from) + .transpose() + .map_err(anyhow::Error::from)?; + + timer.stop_and_record(); + + Ok(TokenMetadata { + first_trade_block: block_number, + }) + } } #[async_trait] diff --git a/crates/orderbook/src/dto/mod.rs b/crates/orderbook/src/dto/mod.rs index fb3365dd95..b706e79d4d 100644 --- a/crates/orderbook/src/dto/mod.rs +++ b/crates/orderbook/src/dto/mod.rs @@ -5,3 +5,11 @@ pub use { auction::{Auction, AuctionId, AuctionWithId}, order::Order, }; +use {serde::Serialize, serde_with::serde_as}; + +#[serde_as] +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenMetadata { + pub first_trade_block: Option, +} diff --git a/database/sql/V077__orders_token_indexes.sql b/database/sql/V077__orders_token_indexes.sql new file mode 100644 index 0000000000..d212427efd --- /dev/null +++ b/database/sql/V077__orders_token_indexes.sql @@ -0,0 +1,3 @@ +CREATE INDEX orders_sell_buy_tokens ON orders (sell_token, buy_token); + +CREATE INDEX jit_orders_sell_buy_tokens ON jit_orders (sell_token, buy_token); From 9b6743aac8323ee82cfe288ea84beea7f6714655 Mon Sep 17 00:00:00 2001 From: m-lord-renkse <160488334+m-lord-renkse@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:37:56 +0100 Subject: [PATCH 5/9] Add block number to mempool_executed logs (#3200) # Description Add block number to mempool_executed logs, so we can simulate failed submissions in order to debug the reason of the failure. --------- Co-authored-by: ilya --- crates/driver/src/domain/mempools.rs | 24 ++++++++++++++++-------- crates/driver/src/infra/notify/mod.rs | 4 ++-- crates/driver/src/infra/observe/mod.rs | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/crates/driver/src/domain/mempools.rs b/crates/driver/src/domain/mempools.rs index 3d4ed86135..f0cedd26af 100644 --- a/crates/driver/src/domain/mempools.rs +++ b/crates/driver/src/domain/mempools.rs @@ -105,7 +105,7 @@ impl Mempools { // settlement. This way we only run iterations in blocks that can potentially // include the settlement. let mut block_stream = into_stream(self.ethereum.current_block().clone()); - block_stream.next().await; + let block = block_stream.next().await; // The tx is simulated before submitting the solution to the competition, but a // delay between that and the actual execution can cause the simulation to be @@ -116,7 +116,7 @@ impl Mempools { ?err, "settlement tx simulation reverted before submitting to the mempool" ); - return Err(Error::SimulationRevert); + return Err(Error::SimulationRevert(block.map(|block| block.number))); } else { tracing::warn!( ?err, @@ -142,7 +142,12 @@ impl Mempools { }); match receipt { TxStatus::Executed => return Ok(hash.clone()), - TxStatus::Reverted => return Err(Error::Revert(hash.clone())), + TxStatus::Reverted => { + return Err(Error::Revert { + tx_id: hash.clone(), + block_number: block.number, + }) + } TxStatus::Pending => { // Check if the current block reached the submission deadline block number if block.number >= submission_deadline { @@ -172,7 +177,7 @@ impl Mempools { ?err, "tx started failing in mempool, cancelling" ); - return Err(Error::SimulationRevert); + return Err(Error::SimulationRevert(Some(block.number))); } else { tracing::warn!(?hash, ?err, "couldn't re-simulate tx"); } @@ -235,10 +240,13 @@ pub enum RevertProtection { #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Mined reverted transaction: {0:?}")] - Revert(eth::TxId), - #[error("Simulation started reverting during submission")] - SimulationRevert, + #[error("Mined reverted transaction: {tx_id:?}, block number: {block_number}")] + Revert { + tx_id: eth::TxId, + block_number: BlockNo, + }, + #[error("Simulation started reverting during submission, block number: {0:?}")] + SimulationRevert(Option), #[error("Settlement did not get included in time")] Expired, #[error("Strategy disabled for this tx")] diff --git a/crates/driver/src/infra/notify/mod.rs b/crates/driver/src/infra/notify/mod.rs index 4f33f3e5e6..edb4c55458 100644 --- a/crates/driver/src/infra/notify/mod.rs +++ b/crates/driver/src/infra/notify/mod.rs @@ -105,8 +105,8 @@ pub fn executed( ) { let kind = match res { Ok(hash) => notification::Settlement::Success(hash.clone()), - Err(Error::Revert(hash)) => notification::Settlement::Revert(hash.clone()), - Err(Error::SimulationRevert) => notification::Settlement::SimulationRevert, + Err(Error::Revert { tx_id: hash, .. }) => notification::Settlement::Revert(hash.clone()), + Err(Error::SimulationRevert { .. }) => notification::Settlement::SimulationRevert, Err(Error::Expired) => notification::Settlement::Expired, Err(Error::Other(_) | Error::Disabled) => notification::Settlement::Fail, }; diff --git a/crates/driver/src/infra/observe/mod.rs b/crates/driver/src/infra/observe/mod.rs index f8c1394e54..0dff50d918 100644 --- a/crates/driver/src/infra/observe/mod.rs +++ b/crates/driver/src/infra/observe/mod.rs @@ -339,7 +339,7 @@ pub fn mempool_executed( } let result = match res { Ok(_) => "Success", - Err(mempools::Error::Revert(_) | mempools::Error::SimulationRevert) => "Revert", + Err(mempools::Error::Revert { .. } | mempools::Error::SimulationRevert { .. }) => "Revert", Err(mempools::Error::Expired) => "Expired", Err(mempools::Error::Other(_)) => "Other", Err(mempools::Error::Disabled) => "Disabled", From 9463faa2f71d64ecca957176264d095539894496 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 6 Jan 2025 13:15:39 +0000 Subject: [PATCH 6/9] [TRIVIAL] Run forked-node tests on main (#3210) After https://github.com/cowprotocol/services/pull/3201, the forked-node tests are no longer triggered on the `main` branch since the filter is configured for pull requests only. This PR fixes this. --- .github/workflows/pull-request.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 372f1670bf..c3613f34b7 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -121,7 +121,9 @@ jobs: test-forked-node: # Do not run this job on forks since some secrets are required for it. - if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + if: | + (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || + (github.event_name == 'push' && github.ref == 'refs/heads/main') timeout-minutes: 60 runs-on: ubuntu-latest env: From c784e142befd6f3c94b60bb5f793ab84d611b926 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 6 Jan 2025 18:29:21 +0000 Subject: [PATCH 7/9] Update `ethcontract-rs` (#3211) Updates `ethcontract-rs` to the latest version where a small bug was fixed. --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d91055a2bd..9ce09b15ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1966,9 +1966,9 @@ dependencies = [ [[package]] name = "ethcontract" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de7f5ab3529a815a6f637e6ed6e62335cdeb535dce0dabd805941bfbfbd22e3f" +checksum = "6c83ea43ad75c3c78468966184a489c195822185df81d5fcaba4b1ba87f382c5" dependencies = [ "arrayvec", "aws-config 0.55.3", @@ -1993,9 +1993,9 @@ dependencies = [ [[package]] name = "ethcontract-common" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb731ef73bc8837d0002887263f43641786e34853f438779444293813f24992" +checksum = "51c3253a19d093d249024012ed2f4117d819958f79339fd24d8158fb05306747" dependencies = [ "ethabi", "hex", @@ -2009,9 +2009,9 @@ dependencies = [ [[package]] name = "ethcontract-derive" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e8f1e55f0f1c341859821a045d33867323fe8b778944bbf60cff883574cca1" +checksum = "d0f2a75ebcd8fe443014a9f1157fab5bb8950cff7c3a62079b748201ca32570c" dependencies = [ "anyhow", "ethcontract-common", @@ -2023,9 +2023,9 @@ dependencies = [ [[package]] name = "ethcontract-generate" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0983a5bc8284b4e7ac33a2818d63315cd19b3ca59ce90fea5a6837c1d65081" +checksum = "b0c0c474f16da0667eec38c2fc4c1ee7ad7cdfd879ffabfd57713444d851c6c7" dependencies = [ "Inflector", "anyhow", @@ -2039,9 +2039,9 @@ dependencies = [ [[package]] name = "ethcontract-mock" -version = "0.25.7" +version = "0.25.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d56f0a4e3aba1a4f4aa06182c3cbe329c0bfecdd3280662abeabbf7620f9569" +checksum = "ff5cf1c4d38e002747e5ecc69fe50cd8d00d4080f1de32acb4cb6373093b8a1e" dependencies = [ "ethcontract", "hex", diff --git a/Cargo.toml b/Cargo.toml index fbbd160162..ae3831d7e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,10 @@ clap = { version = "4.5.6", features = ["derive", "env"] } dashmap = "6.1.0" derivative = "2.2.0" derive_more = { version = "1.0.0", features = ["full"] } -ethcontract = { version = "0.25.7", default-features = false, features = ["aws-kms"] } +ethcontract = { version = "0.25.8", default-features = false, features = ["aws-kms"] } mimalloc = "0.1.43" -ethcontract-generate = { version = "0.25.7", default-features = false } -ethcontract-mock = { version = "0.25.7", default-features = false } +ethcontract-generate = { version = "0.25.8", default-features = false } +ethcontract-mock = { version = "0.25.8", default-features = false } ethereum-types = "0.14.1" flate2 = "1.0.30" futures = "0.3.30" From 2f54635690cab3be34bedbd80a7bac1cc4219a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Strug?= <47604705+mstrug@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:26:34 +0100 Subject: [PATCH 8/9] Added `auction_id` field to `/settle` and `/reveal` driver requests. (#3212) # Description This is a continuation of the previous PR #3131 which added `auction_id` field as optional on the driver `/settle` and `/reveal` requests. Because this is API breaking change we had to wait for Solvers teams to updated their driver version, so they will support new `auction_id` field. Current PR changes `auction_id` field from optional to mandatory and starts sending it from the autopilot. # Changes This is mainly port of PR #3113 which introduced API break (and was reverted with PR #3131) on to the current main branch: updated driver and autopilot DTOs, added code to use these new fields and added additional condition on driver side to verify if provided `solution_id` is competing in provided `auction_id`. OpenAPI definition is already up to date. ## How to test Existing tests, esp. `driver_handles_solutions_based_on_id()`. --- .../autopilot/src/infra/solvers/dto/reveal.rs | 4 ++-- .../autopilot/src/infra/solvers/dto/settle.rs | 4 ++-- crates/autopilot/src/run_loop.rs | 2 +- crates/autopilot/src/shadow.rs | 2 +- crates/driver/src/domain/competition/mod.rs | 22 ++++++------------- .../api/routes/reveal/dto/reveal_request.rs | 4 ++-- .../driver/src/infra/api/routes/reveal/mod.rs | 15 ++++++++----- .../api/routes/settle/dto/settle_request.rs | 4 ++-- .../driver/src/infra/api/routes/settle/mod.rs | 9 +++----- 9 files changed, 30 insertions(+), 36 deletions(-) diff --git a/crates/autopilot/src/infra/solvers/dto/reveal.rs b/crates/autopilot/src/infra/solvers/dto/reveal.rs index 41971d28b1..6bda360d77 100644 --- a/crates/autopilot/src/infra/solvers/dto/reveal.rs +++ b/crates/autopilot/src/infra/solvers/dto/reveal.rs @@ -11,8 +11,8 @@ pub struct Request { /// Unique ID of the solution (per driver competition), to reveal. pub solution_id: u64, /// Auction ID in which the specified solution ID is competing. - #[serde_as(as = "Option")] - pub auction_id: Option, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub auction_id: i64, } #[serde_as] diff --git a/crates/autopilot/src/infra/solvers/dto/settle.rs b/crates/autopilot/src/infra/solvers/dto/settle.rs index 5277eef76e..385ad68212 100644 --- a/crates/autopilot/src/infra/solvers/dto/settle.rs +++ b/crates/autopilot/src/infra/solvers/dto/settle.rs @@ -13,6 +13,6 @@ pub struct Request { /// The last block number in which the solution TX can be included pub submission_deadline_latest_block: u64, /// Auction ID in which the specified solution ID is competing. - #[serde_as(as = "Option")] - pub auction_id: Option, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub auction_id: i64, } diff --git a/crates/autopilot/src/run_loop.rs b/crates/autopilot/src/run_loop.rs index 53b70b4100..3b55738364 100644 --- a/crates/autopilot/src/run_loop.rs +++ b/crates/autopilot/src/run_loop.rs @@ -765,7 +765,7 @@ impl RunLoop { let request = settle::Request { solution_id, submission_deadline_latest_block, - auction_id: None, // Requires 2-stage release for API-break change + auction_id, }; driver .settle(&request, self.config.max_settlement_transaction_wait) diff --git a/crates/autopilot/src/shadow.rs b/crates/autopilot/src/shadow.rs index beddd86acc..44db5bd203 100644 --- a/crates/autopilot/src/shadow.rs +++ b/crates/autopilot/src/shadow.rs @@ -276,7 +276,7 @@ impl RunLoop { let revealed = driver .reveal(&reveal::Request { solution_id, - auction_id: None, // Requires 2-stage release for API-break change + auction_id: request.id, }) .await .map_err(Error::Reveal)?; diff --git a/crates/driver/src/domain/competition/mod.rs b/crates/driver/src/domain/competition/mod.rs index 2ecde0fdc7..e8c8d964ed 100644 --- a/crates/driver/src/domain/competition/mod.rs +++ b/crates/driver/src/domain/competition/mod.rs @@ -318,17 +318,14 @@ impl Competition { pub async fn reveal( &self, solution_id: u64, - auction_id: Option, + auction_id: auction::Id, ) -> Result { let settlement = self .settlements .lock() .unwrap() .iter() - .find(|s| { - s.solution().get() == solution_id - && auction_id.is_none_or(|id| s.auction_id.0 == id) - }) + .find(|s| s.solution().get() == solution_id && s.auction_id == auction_id) .cloned() .ok_or(Error::SolutionNotAvailable)?; Ok(Revealed { @@ -347,7 +344,7 @@ impl Competition { /// [`Competition::solve`] to generate the solution. pub async fn settle( &self, - auction_id: Option, + auction_id: auction::Id, solution_id: u64, submission_deadline: BlockNo, ) -> Result { @@ -413,16 +410,14 @@ impl Competition { tracing::error!(?err, "Failed to send /settle response"); } } - .instrument( - tracing::info_span!("/settle", solver, auction_id = ?auction_id.map(|id| id.0)), - ) + .instrument(tracing::info_span!("/settle", solver, %auction_id)) .await } } async fn process_settle_request( &self, - auction_id: Option, + auction_id: auction::Id, solution_id: u64, submission_deadline: BlockNo, ) -> Result { @@ -430,10 +425,7 @@ impl Competition { let mut lock = self.settlements.lock().unwrap(); let index = lock .iter() - .position(|s| { - s.solution().get() == solution_id - && auction_id.is_none_or(|id| s.auction_id == id) - }) + .position(|s| s.solution().get() == solution_id && s.auction_id == auction_id) .ok_or(Error::SolutionNotAvailable)?; // remove settlement to ensure we can't settle it twice by accident lock.swap_remove_front(index) @@ -537,7 +529,7 @@ fn merge(solutions: impl Iterator, auction: &Auction) -> Vec, + auction_id: auction::Id, solution_id: u64, submission_deadline: BlockNo, response_sender: oneshot::Sender>, diff --git a/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs b/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs index c782ed57fc..e0e762dd47 100644 --- a/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs +++ b/crates/driver/src/infra/api/routes/reveal/dto/reveal_request.rs @@ -7,6 +7,6 @@ pub struct RevealRequest { /// Unique ID of the solution (per driver competition), to reveal. pub solution_id: u64, /// Auction ID in which the specified solution ID is competing. - #[serde_as(as = "Option")] - pub auction_id: Option, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub auction_id: i64, } diff --git a/crates/driver/src/infra/api/routes/reveal/mod.rs b/crates/driver/src/infra/api/routes/reveal/mod.rs index b96ce290f8..7a2d6081e5 100644 --- a/crates/driver/src/infra/api/routes/reveal/mod.rs +++ b/crates/driver/src/infra/api/routes/reveal/mod.rs @@ -1,9 +1,12 @@ mod dto; use { - crate::infra::{ - api::{Error, State}, - observe, + crate::{ + domain::competition::auction, + infra::{ + api::{self, Error, State}, + observe, + }, }, tracing::Instrument, }; @@ -16,11 +19,13 @@ async fn route( state: axum::extract::State, req: axum::Json, ) -> Result, (hyper::StatusCode, axum::Json)> { + let auction_id = + auction::Id::try_from(req.auction_id).map_err(api::routes::AuctionError::from)?; let handle_request = async { observe::revealing(); let result = state .competition() - .reveal(req.solution_id, req.auction_id) + .reveal(req.solution_id, auction_id) .await; observe::revealed(state.solver().name(), &result); let result = result?; @@ -28,6 +33,6 @@ async fn route( }; handle_request - .instrument(tracing::info_span!("/reveal", solver = %state.solver().name(), req.auction_id)) + .instrument(tracing::info_span!("/reveal", solver = %state.solver().name(), %auction_id)) .await } diff --git a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs index 844a716738..6f5735fbce 100644 --- a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs +++ b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs @@ -9,6 +9,6 @@ pub struct SettleRequest { /// The last block number in which the solution TX can be included pub submission_deadline_latest_block: u64, /// Auction ID in which this solution is competing. - #[serde_as(as = "Option")] - pub auction_id: Option, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub auction_id: i64, } diff --git a/crates/driver/src/infra/api/routes/settle/mod.rs b/crates/driver/src/infra/api/routes/settle/mod.rs index 516e3efb70..1334693a6e 100644 --- a/crates/driver/src/infra/api/routes/settle/mod.rs +++ b/crates/driver/src/infra/api/routes/settle/mod.rs @@ -19,11 +19,8 @@ async fn route( state: axum::extract::State, req: axum::Json, ) -> Result<(), (hyper::StatusCode, axum::Json)> { - let auction_id = req - .auction_id - .map(auction::Id::try_from) - .transpose() - .map_err(Into::::into)?; + let auction_id = + auction::Id::try_from(req.auction_id).map_err(api::routes::AuctionError::from)?; let solver = state.solver().name().to_string(); let handle_request = async move { @@ -39,7 +36,7 @@ async fn route( observe::settled(state.solver().name(), &result); result.map(|_| ()).map_err(Into::into) } - .instrument(tracing::info_span!("/settle", solver, auction_id = ?auction_id.map(|id| id.0))); + .instrument(tracing::info_span!("/settle", solver, %auction_id)); // Handle `/settle` call in a background task to ensure that we correctly // submit the settlement (or cancellation) on-chain even if the server From 672b30fadc703560fc3dab624f485eb4455dd3a4 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Wed, 8 Jan 2025 12:34:14 +0100 Subject: [PATCH 9/9] execute `eth_estimateGas` and `eth_createAccessList` on latest block (#3220) # Description For a while we had mysteriously failing transactions. What I mean is that we execute `eth_estimateGas` to see if a transaction would still work. If it fails we log all the data needed to re-simulate it on tenderly. But when you then actually re-simulate on tenderly the transaction would work. The solution unravels with this [issue](https://github.com/tomusdrw/rust-web3/issues/290). The spec for `eth_estimateGas` mentions 2 arguments: the tx and an identifier for a block. The block is supposed to be optional but geth had a bug for a very long time. When you don't pass a block argument it should be sent over the wire as `null`. But geth tried to deserialize `null` which then caused these calls to fail. As a workaround the `web3` crate stopped serializing the block tag if it was `None`. Eventually geth fixed this issue and implemented it such that a missing block tag would default to `pending`. Also a rule of thumb in node development is "if in doubt do whatever geth does" so reth also implemented the default to `pending`. Defaulting to `pending` is what can make these reverts unpredictable. When you simulate the tx on `pending` the node takes the state of the `latest` block PLUS transactions that it currently has in its mempool. So whenever we log a reverting tx that works on tenderly it only failed in our node because some tx in the nodes mempool interfered with the solution. Interestingly there also a lot of cases where you can even simulate the tx on the next block. In those cases simulating on `pending` caused the driver completely erroneously since the problematic tx didn't even make it into the next block.
example [log](https://aws-es.cow.fi/_dashboards/app/discover#/doc/86e4a5a0-4e4b-11ef-85c5-3946a99ed1a7/cowlogs-prod-2025.01.06?id=1F1ROpQBOTpjfSxxfB9z) ``` 2025-01-06T06:33:25.748Z INFO request{id="4114843"}:/solve{solver=extzeroex-solve auction_id=9990122}: driver::infra::observe: discarded solution: settlement encoding id=Id { id: 17739308, merged_solutions: [22] } err=Simulation(Revert(RevertError { err: Blockchain(AccessList(String("execution reverted"))), tx: Tx { from: Address(0x28b1bd44996105b5c14c4de41093226ff78a4eb1), to: Address(0x9008d19f58aabd9ed0d60971565aa8510560ab41), value: Ether(0), input: 0x13d79a0b0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000003e00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000839d4a05d6d23a22365bd90c2114c199d53e674c000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000839d4a05d6d23a22365bd90c2114c199d53e674c000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000017c836d081141b7000000000000000000000000000000000000000000000034cafbfac2c90f727a000000000000000000000000000000000000000000000000017c836d081141b700000000000000000000000000000000000000000000003635c9adc5dea0000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000006a7fe93d1e194d31b8076c255779c4b12a45192c00000000000000000000000000000000000000000000003635c9adc5dea00000000000000000000000000000000000000000000000000000010df3f85654f86700000000000000000000000000000000000000000000000000000000677b7e7d5b203ebd4ff6f6667daa775bd3de4410b0b834f059fccecd85660cb72bf031e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003635c9adc5dea00000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000410fd3b1f82fc78a190ccf38bbfa08652ca518ef025802fe671c0b7e1b8cc1c8e762470c92b313dd597fde1022168575929770076f30cc6581377d174f76ce675e1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000300000000000000000000000000839d4a05d6d23a22365bd90c2114c199d53e674c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff00000000000000000000000000000000000000000000003635c9adc5dea0000000000000000000000000000000000000000000000000000000000000000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000128d9627aa4000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000003635c9adc5dea000000000000000000000000000000000000000000000000000000186565d4040733b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000839d4a05d6d23a22365bd90c2114c199d53e674c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2869584cd0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab410000000000000000000000000000000000000000b507dba868e8950578d34d3f000000000000000000000000000000000000000000000000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000017c836d081141b70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000986fea, access_list: AccessList({}) }, block: BlockNo(21563660) })) ``` Results in this [simulation](https://dashboard.tenderly.co/cow-protocol/staging/simulator/0b89b4d0-418f-40e3-8502-59c85e4da885) that even works 2 blocks past the block we logged.
# Changes Always estimate gas and create access lists on the `latest` block instead of `pending`. This should be the better default for 2 reasons: 1. it makes our simulation reverts consistent with what we see on tenderly 2. most blocks get built by sophisticated algorithms including and arranging blocks in a way that maximizes MEV. That means whatever transactions ordering the node applies in the `pending` likely isn't accurate anyway. --- crates/driver/src/infra/blockchain/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/driver/src/infra/blockchain/mod.rs b/crates/driver/src/infra/blockchain/mod.rs index a43a037bfe..6b85a00b08 100644 --- a/crates/driver/src/infra/blockchain/mod.rs +++ b/crates/driver/src/infra/blockchain/mod.rs @@ -182,7 +182,10 @@ impl Ethereum { .transport() .execute( "eth_createAccessList", - vec![serde_json::to_value(&tx).unwrap()], + vec![ + serde_json::to_value(&tx).unwrap(), + serde_json::Value::String("latest".into()), + ], ) .await?; if let Some(err) = json.get("error") { @@ -207,7 +210,7 @@ impl Ethereum { gas_price: self.simulation_gas_price().await, ..Default::default() }, - None, + Some(ethcontract::BlockNumber::Latest), ) .await .map(Into::into)