package anvil import ( "context" "encoding/hex" "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "math/big" ) const ( anvilNamespace = "anvil" evmNamespace = "evm" ) // Client is a client for interacting with anvil. // Note: this should not be used directly unless dealing with an external anvil instance. // Please use the Backend instead. type Client struct { *rpc.Client } // nolint: wrapcheck func (c *Client) callAnvilContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { return c.CallContext(ctx, result, fmt.Sprintf("%s_%s", anvilNamespace, method), args...) } // nolint: wrapcheck func (c *Client) callEvmContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { return c.CallContext(ctx, result, fmt.Sprintf("%s_%s", evmNamespace, method), args...) } // Dial dials an anvil instance. func Dial(ctx context.Context, rpcURL string) (*Client, error) { c, err := rpc.DialContext(ctx, rpcURL) if err != nil { return nil, fmt.Errorf("could not create client: %w", err) } return &Client{c}, nil } // ImpersonateAccount impersonates an account. // Send transactions impersonating an externally owned account or contract. // While impersonating a contract, the contract functions can not be called. anvil_stopImpersonatingAccount must be used if the contract's functions are to be called again. See also EIP-3607. func (c *Client) ImpersonateAccount(ctx context.Context, address common.Address) error { return c.callAnvilContext(ctx, nil, "impersonateAccount", address.String()) } // StopImpersonatingAccount stops impersonating an account or contract if previously set with anvil_impersonateAccount. func (c *Client) StopImpersonatingAccount(ctx context.Context, address common.Address) error { return c.callAnvilContext(ctx, nil, "stopImpersonatingAccount", address.String()) } // GetAutomine returns whether automine is enabled. func (c *Client) GetAutomine(ctx context.Context) (isAutomining bool, err error) { err = c.callAnvilContext(ctx, &isAutomining, "getAutomine") return } // Mine mines a number of blocks. func (c *Client) Mine(ctx context.Context, blockCount uint) error { return c.callAnvilContext(ctx, nil, "mine", blockCount) } // DropTransaction drops a transactions from the memopool. func (c *Client) DropTransaction(ctx context.Context, tx common.Hash) error { return c.callAnvilContext(ctx, nil, "dropTransaction", tx) } // Reset the fork to a fresh forked state, and optionally update the fork config. func (c *Client) Reset(ctx context.Context) error { return c.callAnvilContext(ctx, nil, "reset") } // SetRPCURL sets the rpc url. func (c *Client) SetRPCURL(ctx context.Context, rpcURL string) error { return c.callAnvilContext(ctx, nil, "setRpcUrl", rpcURL) } // SetBalance sets the balance of an account. func (c *Client) SetBalance(ctx context.Context, address common.Address, balance uint64) error { return c.callAnvilContext(ctx, nil, "setBalance", address.String(), balance) } // SetCode sets the code of an account. func (c *Client) SetCode(ctx context.Context, address common.Address, code []byte) error { return c.callAnvilContext(ctx, nil, "setCode", address.String(), hexutil.Encode(code)) } // SetNonce sets the nonce of an account. func (c *Client) SetNonce(ctx context.Context, address common.Address, nonce uint64) error { return c.callAnvilContext(ctx, nil, "setNonce", address.String(), nonce) } // SetStorageAt sets the storage of an account. func (c *Client) SetStorageAt(ctx context.Context, address common.Address, key common.Hash, value common.Hash) error { return c.callAnvilContext(ctx, nil, "setStorageAt", address.String(), key, value) } // SetCoinbase sets the coinbase of the chain. func (c *Client) SetCoinbase(ctx context.Context, coinbase common.Address) error { return c.callAnvilContext(ctx, nil, "setCoinbase", coinbase.String()) } // SetLoggingEnabled Enable or disable logging. func (c *Client) SetLoggingEnabled(ctx context.Context, enabled bool) error { return c.callAnvilContext(ctx, nil, "setLoggingEnabled", enabled) } // SetMinGasPrice sets the minimum gas price. // this is disabled when eip-1559 is enabled. func (c *Client) SetMinGasPrice(ctx context.Context, minGasPrice *big.Int) error { return c.callAnvilContext(ctx, nil, "setMinGasPrice", minGasPrice) } // SetNextBlockBaseFeePerGas sets the base fee per gas for the next block. func (c *Client) SetNextBlockBaseFeePerGas(ctx context.Context, baseFee *big.Int) error { return c.callAnvilContext(ctx, nil, "setNextBlockBaseFeePerGas", baseFee) } // DumpState returns a hex string representing the complete state of the chain. // Can be re-imported into a fresh/restarted instance of Anvil to reattain the same state. func (c *Client) DumpState(ctx context.Context) (string, error) { var stateHex string err := c.callAnvilContext(ctx, &stateHex, "dumpState") return stateHex, err } // LoadState merges the contents of a hex string previously returned by DumpState // into the current chain state. Will overwrite any colliding accounts/storage slots. func (c *Client) LoadState(ctx context.Context, stateHex string) error { return c.callAnvilContext(ctx, nil, "loadState", stateHex) } // NodeInfo retrieves the configuration params for the currently running Anvil node. func (c *Client) NodeInfo(ctx context.Context) (map[string]interface{}, error) { var nodeInfo map[string]interface{} err := c.callAnvilContext(ctx, &nodeInfo, "nodeInfo") return nodeInfo, err } // SetAutomine enables or disables automatic mining of new blocks with each new transaction. func (c *Client) SetAutomine(ctx context.Context, enabled bool) error { return c.callEvmContext(ctx, nil, "setAutomine", enabled) } // SetIntervalMining sets the mining behavior to interval with the given interval (seconds). func (c *Client) SetIntervalMining(ctx context.Context, interval int) error { return c.callEvmContext(ctx, nil, "setIntervalMining", interval) } // Snapshot captures the state of the blockchain at the current block. func (c *Client) Snapshot(ctx context.Context) (snapshotID string, err error) { err = c.callEvmContext(ctx, &snapshotID, "snapshot") return } // Revert reverts the state of the blockchain to a previous snapshot. func (c *Client) Revert(ctx context.Context, snapshotID string) error { return c.callEvmContext(ctx, nil, "revert", snapshotID) } // IncreaseTime jumps forward in time by the given amount of time, in seconds. func (c *Client) IncreaseTime(ctx context.Context, seconds int64) error { return c.callEvmContext(ctx, nil, "increaseTime", seconds) } // SetNextBlockTimestamp sets the exact timestamp for the next block. func (c *Client) SetNextBlockTimestamp(ctx context.Context, timestamp int64) error { return c.callEvmContext(ctx, nil, "setNextBlockTimestamp", timestamp) } // SetBlockTimestampInterval sets a block timestamp interval. func (c *Client) SetBlockTimestampInterval(ctx context.Context, interval int64) error { return c.callAnvilContext(ctx, nil, "setBlockTimestampInterval", interval) } // RemoveBlockTimestampInterval removes an anvil_setBlockTimestampInterval if it exists. func (c *Client) RemoveBlockTimestampInterval(ctx context.Context) error { return c.callAnvilContext(ctx, nil, "removeBlockTimestampInterval") } // SetBlockGasLimit sets the block gas limit for the following blocks. func (c *Client) SetBlockGasLimit(ctx context.Context, gasLimit uint64) error { return c.callEvmContext(ctx, nil, "setBlockGasLimit", gasLimit) } // EvmMine mines a single block. func (c *Client) EvmMine(ctx context.Context) error { return c.callEvmContext(ctx, nil, "mine") } // EnableTraces enables call traces for transactions that are returned to the user when they execute a transaction. func (c *Client) EnableTraces(ctx context.Context) error { return c.callAnvilContext(ctx, nil, "enableTraces") } // anvilTransactionLegacy represents a transaction that will serialize to the correct JSON. type anvilTransactionLegacy struct { From string `json:"from"` To string `json:"to"` GasPrice string `json:"gasPrice"` Gas string `json:"gas"` Value string `json:"value,omitempty"` Data string `json:"data"` Nonce string `json:"nonce"` TransactionType string `json:"type"` } // anvilTransactionDynamic represents a transaction that will serialize to the correct JSON. type anvilTransactionDynamic struct { From string `json:"from"` To string `json:"to"` MaxFeePerGas string `json:"maxFeePerGas"` MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas"` Gas string `json:"gas"` Value string `json:"value,omitempty"` Data string `json:"data"` Nonce string `json:"nonce"` TransactionType string `json:"type"` } // SendUnsignedTransaction sends a transaction to the anvil node. // It is the responsibility of the caller to call impersonateAccount and revertImpersonatedAccount. func (c *Client) SendUnsignedTransaction(ctx context.Context, from common.Address, tx *types.Transaction) error { var anTx interface{} if tx.Type() == types.LegacyTxType { anTx = anvilTransactionLegacy{ From: from.Hex(), To: tx.To().Hex(), GasPrice: bigIntToString(tx.GasPrice().Int64()), Gas: bigIntToString(int64(tx.Gas())), Data: hex.EncodeToString(tx.Data()), Nonce: bigIntToString(int64(tx.Nonce())), Value: fmt.Sprintf("%x", tx.Value()), TransactionType: bigIntToString(int64(tx.Type())), } } else { anTx = anvilTransactionDynamic{ From: from.Hex(), To: tx.To().Hex(), MaxFeePerGas: bigIntToString(tx.GasPrice().Int64()), MaxPriorityFeePerGas: bigIntToString(tx.GasPrice().Int64()), Gas: bigIntToString(int64(tx.Gas())), Data: hex.EncodeToString(tx.Data()), Nonce: bigIntToString(int64(tx.Nonce())), Value: fmt.Sprintf("%x", tx.Value()), TransactionType: bigIntToString(int64(tx.Type())), } } // nolint: wrapcheck return c.CallContext(ctx, nil, "eth_sendTransaction", anTx) } func bigIntToString(n int64) string { b := big.NewInt(n).Bytes() if len(b) == 0 { return "0x00" } return hexutil.Encode(b) } // Close closes the conn. func (c *Client) Close() { c.Client.Close() }