From f09b0e890aa025636de34fe5d2da09a2a8b69c71 Mon Sep 17 00:00:00 2001 From: Nicholas Rodrigues Lordello Date: Wed, 13 Nov 2024 17:45:24 +0100 Subject: [PATCH] Introduce Component for Balance Overrides This PR is the first in a stack to add a system for overriding balances so that more quotes can be verified. One issue with the current system, is that it requires that the account creating the quote has the balance available to the account (or available to the account after a pre-hook is executed) in order for the quote to be properly verified. This isn't always possible for all flows (and notably flows at Safe, where transactions to prepare the balance happens at the same time as the `setPreSignature` transaction executes and after the quote). The overall solution I would propose (hopefully a pragmatic one that isn't TOO hacky) would be enable special handling for the most commonly traded tokens, by configuring for each token how the storage slot is computed for the token balance. This way, you could maintain a file that contains a `token => computation_strategy` map for the most popular tokens allowing trades to be verified even for quotes from users without the balance available. If this strategy is accepted, in a follow up PRs I would: 1. Add the component to the trade verifier and use it to fund the trader when simulating quotes 2. Pipe configuration to the trade verifier and balance overrides component --- .../src/price_estimation/trade_verifier.rs | 2 + .../trade_verifier/balance_overrides.rs | 130 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index 02e20a24d9..7107ac5ff0 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -1,3 +1,5 @@ +pub mod balance_overrides; + use { super::{Estimate, Verification}, crate::{ diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs new file mode 100644 index 0000000000..e8992efb6c --- /dev/null +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs @@ -0,0 +1,130 @@ +use { + ethcontract::{Address, H256, U256}, + ethrpc::extensions::StateOverride, + maplit::hashmap, + std::collections::HashMap, + web3::signing, +}; + +/// A component that can provide balance overrides for tokens. +/// +/// This allows a wider range of verified quotes to work, even when balances +/// are not available for the quoter. +pub trait BalanceOverriding { + fn state_override(&self, request: &BalanceOverrideRequest) -> Option; +} + +/// Parameters for computing a balance override request. +pub struct BalanceOverrideRequest { + /// The token for the override. + pub token: Address, + /// The account to override the balance for. + pub holder: Address, + /// The token amount (in atoms) to set the balance to. + pub amount: U256, +} + +/// A simple configuration-based balance override provider. +#[derive(Clone, Debug, Default)] +pub struct ConfigurationBalanceOverrides(HashMap); + +#[derive(Clone, Debug)] +pub enum Strategy { + Mapping { slot: U256 }, +} + +impl ConfigurationBalanceOverrides { + pub fn new(config: HashMap) -> Self { + Self(config) + } +} + +impl BalanceOverriding for ConfigurationBalanceOverrides { + fn state_override(&self, request: &BalanceOverrideRequest) -> Option { + let strategy = self.0.get(&request.token)?; + match strategy { + Strategy::Mapping { slot } => Some(StateOverride { + state_diff: Some(hashmap! { + address_mapping_storage_slot(slot, &request.holder) => request.amount, + }), + ..Default::default() + }), + } + } +} + +/// Computes the storage slot where the value is stored for Solidity mappings +/// of the form `mapping(address => ...)`. +/// +/// See . +fn address_mapping_storage_slot(slot: &U256, address: &Address) -> H256 { + let mut buf = [0; 64]; + buf[12..32].copy_from_slice(address.as_fixed_bytes()); + slot.to_big_endian(&mut buf[32..64]); + H256(signing::keccak256(&buf)) +} + +#[cfg(test)] +mod tests { + use {super::*, hex_literal::hex}; + + #[test] + fn balance_override_computation() { + let balance_overrides = ConfigurationBalanceOverrides::new(hashmap! { + addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB") => Strategy::Mapping { + slot: U256::from(0), + }, + }); + + assert_eq!( + balance_overrides.state_override(&BalanceOverrideRequest { + token: addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"), + holder: addr!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"), + amount: 0x42_u64.into(), + }), + Some(StateOverride { + state_diff: Some(hashmap! { + H256(hex!("fca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33")) => 0x42_u64.into() + }), + ..Default::default() + }), + ); + + // You can verify the state override computation is correct by running: + // ``` + // curl -X POST $RPC -H 'Content-Type: application/data' --data '{ + // "jsonrpc": "2.0", + // "id": 0, + // "method": "eth_call", + // "params": [ + // { + // "to": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", + // "data": "0x70a08231000000000000000000000000d8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + // }, + // "latest", + // { + // "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { + // "stateDiff": { + // "0xfca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33": + // "0x0000000000000000000000000000000000000000000000000000000000000042" + // } + // } + // } + // ] + // }' + // ``` + } + + #[test] + fn balance_overrides_none_for_unknown_tokens() { + let balance_overrides = ConfigurationBalanceOverrides::default(); + assert_eq!( + balance_overrides.state_override(&BalanceOverrideRequest { + token: addr!("0000000000000000000000000000000000000000"), + holder: addr!("0000000000000000000000000000000000000001"), + amount: U256::zero(), + }), + None, + ); + } +}