@@ -1873,41 +1873,45 @@ impl EthApi {
18731873 Ok ( ( ) )
18741874 }
18751875
1876- /// Deals ERC20 tokens to a address
1877- ///
1878- /// Handler for RPC call: `anvil_dealERC20`
1879- pub async fn anvil_deal_erc20 (
1876+ /// Helper function to find the storage slot for an ERC20 function call by testing slots
1877+ /// from an access list until one produces the expected result.
1878+ ///
1879+ /// Rather than trying to reverse-engineer the storage layout, this function uses a
1880+ /// "trial and error" approach: try overriding each slot that the function accesses,
1881+ /// and see which one actually affects the function's return value.
1882+ ///
1883+ /// ## Parameters
1884+ /// - `token_address`: The ERC20 token contract address
1885+ /// - `calldata`: The encoded function call (e.g., `balanceOf(user)` or `allowance(owner,
1886+ /// spender)`)
1887+ /// - `expected_value`: The value we want to set (balance or allowance amount)
1888+ ///
1889+ /// ## Returns
1890+ /// The storage slot (B256) that contains the target ERC20 data, or an error if no slot is
1891+ /// found.
1892+ async fn find_erc20_storage_slot (
18801893 & self ,
1881- address : Address ,
18821894 token_address : Address ,
1883- balance : U256 ,
1884- ) -> Result < ( ) > {
1885- node_info ! ( "anvil_dealERC20" ) ;
1886-
1887- sol ! {
1888- #[ sol( rpc) ]
1889- contract IERC20 {
1890- function balanceOf( address target) external view returns ( uint256) ;
1891- }
1892- }
1893-
1894- let calldata = IERC20 :: balanceOfCall { target : address } . abi_encode ( ) ;
1895+ calldata : Bytes ,
1896+ expected_value : U256 ,
1897+ ) -> Result < B256 > {
18951898 let tx = TransactionRequest :: default ( ) . with_to ( token_address) . with_input ( calldata. clone ( ) ) ;
18961899
1897- // first collect all the slots that are used by the balanceOf call
1900+ // first collect all the slots that are used by the function call
18981901 let access_list_result =
18991902 self . create_access_list ( WithOtherFields :: new ( tx. clone ( ) ) , None ) . await ?;
19001903 let access_list = access_list_result. access_list ;
19011904
1902- // now we can iterate over all the accessed slots and try to find the one that contains the
1903- // balance by overriding the slot and checking the `balanceOfCall` of
1905+ // iterate over all the accessed slots and try to find the one that contains the
1906+ // target value by overriding the slot and checking the function call result
19041907 for item in access_list. 0 {
19051908 if item. address != token_address {
19061909 continue ;
19071910 } ;
19081911 for slot in & item. storage_keys {
1909- let account_override = AccountOverride :: default ( )
1910- . with_state_diff ( std:: iter:: once ( ( * slot, B256 :: from ( balance. to_be_bytes ( ) ) ) ) ) ;
1912+ let account_override = AccountOverride :: default ( ) . with_state_diff ( std:: iter:: once (
1913+ ( * slot, B256 :: from ( expected_value. to_be_bytes ( ) ) ) ,
1914+ ) ) ;
19111915
19121916 let state_override = StateOverridesBuilder :: default ( )
19131917 . append ( token_address, account_override)
@@ -1922,25 +1926,55 @@ impl EthApi {
19221926 continue ;
19231927 } ;
19241928
1925- let Ok ( result_balance ) = U256 :: abi_decode ( & result) else {
1929+ let Ok ( result_value ) = U256 :: abi_decode ( & result) else {
19261930 // response returned something other than a U256
19271931 continue ;
19281932 } ;
19291933
1930- if result_balance == balance {
1931- self . anvil_set_storage_at (
1932- token_address,
1933- U256 :: from_be_bytes ( slot. 0 ) ,
1934- B256 :: from ( balance. to_be_bytes ( ) ) ,
1935- )
1936- . await ?;
1937- return Ok ( ( ) ) ;
1934+ if result_value == expected_value {
1935+ return Ok ( * slot) ;
19381936 }
19391937 }
19401938 }
19411939
1942- // unable to set the balance
1943- Err ( BlockchainError :: Message ( "Unable to set ERC20 balance, no slot found" . to_string ( ) ) )
1940+ Err ( BlockchainError :: Message ( "Unable to find storage slot" . to_string ( ) ) )
1941+ }
1942+
1943+ /// Deals ERC20 tokens to a address
1944+ ///
1945+ /// Handler for RPC call: `anvil_dealERC20`
1946+ pub async fn anvil_deal_erc20 (
1947+ & self ,
1948+ address : Address ,
1949+ token_address : Address ,
1950+ balance : U256 ,
1951+ ) -> Result < ( ) > {
1952+ node_info ! ( "anvil_dealERC20" ) ;
1953+
1954+ sol ! {
1955+ #[ sol( rpc) ]
1956+ contract IERC20 {
1957+ function balanceOf( address target) external view returns ( uint256) ;
1958+ }
1959+ }
1960+
1961+ let calldata = IERC20 :: balanceOfCall { target : address } . abi_encode ( ) . into ( ) ;
1962+
1963+ // Find the storage slot that contains the balance
1964+ let slot =
1965+ self . find_erc20_storage_slot ( token_address, calldata, balance) . await . map_err ( |_| {
1966+ BlockchainError :: Message ( "Unable to set ERC20 balance, no slot found" . to_string ( ) )
1967+ } ) ?;
1968+
1969+ // Set the storage slot to the desired balance
1970+ self . anvil_set_storage_at (
1971+ token_address,
1972+ U256 :: from_be_bytes ( slot. 0 ) ,
1973+ B256 :: from ( balance. to_be_bytes ( ) ) ,
1974+ )
1975+ . await ?;
1976+
1977+ Ok ( ( ) )
19441978 }
19451979
19461980 /// Sets the ERC20 allowance for a spender
@@ -1962,56 +1996,23 @@ impl EthApi {
19621996 }
19631997 }
19641998
1965- let calldata = IERC20 :: allowanceCall { owner, spender } . abi_encode ( ) ;
1966- let tx = TransactionRequest :: default ( ) . with_to ( token_address) . with_input ( calldata. clone ( ) ) ;
1967-
1968- // first collect all the slots that are used by the allowance call
1969- let access_list_result =
1970- self . create_access_list ( WithOtherFields :: new ( tx. clone ( ) ) , None ) . await ?;
1971- let access_list = access_list_result. access_list ;
1972-
1973- // now we can iterate over all the accessed slots and try to find the one that contains the
1974- // allowance by overriding the slot and checking the `allowanceCall` result
1975- for item in access_list. 0 {
1976- if item. address != token_address {
1977- continue ;
1978- } ;
1979- for slot in & item. storage_keys {
1980- let account_override = AccountOverride :: default ( )
1981- . with_state_diff ( std:: iter:: once ( ( * slot, B256 :: from ( amount. to_be_bytes ( ) ) ) ) ) ;
1982-
1983- let state_override = StateOverridesBuilder :: default ( )
1984- . append ( token_address, account_override)
1985- . build ( ) ;
1986-
1987- let evm_override = EvmOverrides :: state ( Some ( state_override) ) ;
1999+ let calldata = IERC20 :: allowanceCall { owner, spender } . abi_encode ( ) . into ( ) ;
19882000
1989- let Ok ( result) =
1990- self . call ( WithOtherFields :: new ( tx. clone ( ) ) , None , evm_override) . await
1991- else {
1992- // overriding this slot failed
1993- continue ;
1994- } ;
2001+ // Find the storage slot that contains the allowance
2002+ let slot =
2003+ self . find_erc20_storage_slot ( token_address, calldata, amount) . await . map_err ( |_| {
2004+ BlockchainError :: Message ( "Unable to set ERC20 allowance, no slot found" . to_string ( ) )
2005+ } ) ?;
19952006
1996- let Ok ( result_allowance) = U256 :: abi_decode ( & result) else {
1997- // response returned something other than a U256
1998- continue ;
1999- } ;
2000-
2001- if result_allowance == amount {
2002- self . anvil_set_storage_at (
2003- token_address,
2004- U256 :: from_be_bytes ( slot. 0 ) ,
2005- B256 :: from ( amount. to_be_bytes ( ) ) ,
2006- )
2007- . await ?;
2008- return Ok ( ( ) ) ;
2009- }
2010- }
2011- }
2007+ // Set the storage slot to the desired allowance
2008+ self . anvil_set_storage_at (
2009+ token_address,
2010+ U256 :: from_be_bytes ( slot. 0 ) ,
2011+ B256 :: from ( amount. to_be_bytes ( ) ) ,
2012+ )
2013+ . await ?;
20122014
2013- // unable to set the allowance
2014- Err ( BlockchainError :: Message ( "Unable to set ERC20 allowance, no slot found" . to_string ( ) ) )
2015+ Ok ( ( ) )
20152016 }
20162017
20172018 /// Sets the code of a contract.
0 commit comments