@@ -56,7 +56,7 @@ use alloy_rpc_types::{
56
56
} ,
57
57
request:: TransactionRequest ,
58
58
simulate:: { SimulatePayload , SimulatedBlock } ,
59
- state:: EvmOverrides ,
59
+ state:: { AccountOverride , EvmOverrides , StateOverridesBuilder } ,
60
60
trace:: {
61
61
filter:: TraceFilter ,
62
62
geth:: { GethDebugTracingCallOptions , GethDebugTracingOptions , GethTrace } ,
@@ -67,6 +67,7 @@ use alloy_rpc_types::{
67
67
EIP1186AccountProofResponse , FeeHistory , Filter , FilteredParams , Index , Log , Work ,
68
68
} ;
69
69
use alloy_serde:: WithOtherFields ;
70
+ use alloy_sol_types:: { sol, SolCall , SolValue } ;
70
71
use alloy_transport:: TransportErrorKind ;
71
72
use anvil_core:: {
72
73
eth:: {
@@ -355,6 +356,9 @@ impl EthApi {
355
356
EthRequest :: SetBalance ( addr, val) => {
356
357
self . anvil_set_balance ( addr, val) . await . to_rpc_result ( )
357
358
}
359
+ EthRequest :: DealERC20 ( addr, token_addr, val) => {
360
+ self . anvil_deal_erc20 ( addr, token_addr, val) . await . to_rpc_result ( )
361
+ }
358
362
EthRequest :: SetCode ( addr, code) => {
359
363
self . anvil_set_code ( addr, code) . await . to_rpc_result ( )
360
364
}
@@ -1852,6 +1856,76 @@ impl EthApi {
1852
1856
Ok ( ( ) )
1853
1857
}
1854
1858
1859
+ /// Deals ERC20 tokens to a address
1860
+ ///
1861
+ /// Handler for RPC call: `anvil_dealERC20`
1862
+ pub async fn anvil_deal_erc20 (
1863
+ & self ,
1864
+ address : Address ,
1865
+ token_address : Address ,
1866
+ balance : U256 ,
1867
+ ) -> Result < ( ) > {
1868
+ node_info ! ( "anvil_dealERC20" ) ;
1869
+
1870
+ sol ! {
1871
+ #[ sol( rpc) ]
1872
+ contract IERC20 {
1873
+ function balanceOf( address target) external view returns ( uint256) ;
1874
+ }
1875
+ }
1876
+
1877
+ let calldata = IERC20 :: balanceOfCall { target : address } . abi_encode ( ) ;
1878
+ let tx = TransactionRequest :: default ( ) . with_to ( token_address) . with_input ( calldata. clone ( ) ) ;
1879
+
1880
+ // first collect all the slots that are used by the balanceOf call
1881
+ let access_list_result =
1882
+ self . create_access_list ( WithOtherFields :: new ( tx. clone ( ) ) , None ) . await ?;
1883
+ let access_list = access_list_result. access_list ;
1884
+
1885
+ // now we can iterate over all the accessed slots and try to find the one that contains the
1886
+ // balance by overriding the slot and checking the `balanceOfCall` of
1887
+ for item in access_list. 0 {
1888
+ if item. address != token_address {
1889
+ continue ;
1890
+ } ;
1891
+ for slot in & item. storage_keys {
1892
+ let account_override = AccountOverride :: default ( )
1893
+ . with_state_diff ( std:: iter:: once ( ( * slot, B256 :: from ( balance. to_be_bytes ( ) ) ) ) ) ;
1894
+
1895
+ let state_override = StateOverridesBuilder :: default ( )
1896
+ . append ( token_address, account_override)
1897
+ . build ( ) ;
1898
+
1899
+ let evm_override = EvmOverrides :: state ( Some ( state_override) ) ;
1900
+
1901
+ let Ok ( result) =
1902
+ self . call ( WithOtherFields :: new ( tx. clone ( ) ) , None , evm_override) . await
1903
+ else {
1904
+ // overriding this slot failed
1905
+ continue ;
1906
+ } ;
1907
+
1908
+ let Ok ( result_balance) = U256 :: abi_decode ( & result) else {
1909
+ // response returned something other than a U256
1910
+ continue ;
1911
+ } ;
1912
+
1913
+ if result_balance == balance {
1914
+ self . anvil_set_storage_at (
1915
+ token_address,
1916
+ U256 :: from_be_bytes ( slot. 0 ) ,
1917
+ B256 :: from ( balance. to_be_bytes ( ) ) ,
1918
+ )
1919
+ . await ?;
1920
+ return Ok ( ( ) ) ;
1921
+ }
1922
+ }
1923
+ }
1924
+
1925
+ // unable to set the balance
1926
+ Err ( BlockchainError :: Message ( "Unable to set ERC20 balance, no slot found" . to_string ( ) ) )
1927
+ }
1928
+
1855
1929
/// Sets the code of a contract.
1856
1930
///
1857
1931
/// Handler for RPC call: `anvil_setCode`
0 commit comments