mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
- Update vm dependency to v1.0.18 - Remove internal log package in favor of luxfi/log - Migrate prometheus to gatherer package
336 lines
9.5 KiB
Go
336 lines
9.5 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package eth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/holiman/uint256"
|
|
"github.com/luxfi/evm/core/state"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/common/hexutil"
|
|
"github.com/luxfi/geth/core/tracing"
|
|
)
|
|
|
|
// DevAPI provides Anvil/Hardhat-compatible RPC methods for dev mode.
|
|
// These methods allow manipulation of blockchain state for testing purposes.
|
|
type DevAPI struct {
|
|
eth *Ethereum
|
|
snapshots map[hexutil.Uint64]common.Hash // snapshot ID -> block hash
|
|
nextSnap hexutil.Uint64
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewDevAPI creates a new DevAPI instance.
|
|
func NewDevAPI(eth *Ethereum) *DevAPI {
|
|
return &DevAPI{
|
|
eth: eth,
|
|
snapshots: make(map[hexutil.Uint64]common.Hash),
|
|
nextSnap: 1,
|
|
}
|
|
}
|
|
|
|
// SetBalance sets the balance of an address.
|
|
// Anvil: anvil_setBalance
|
|
// Hardhat: hardhat_setBalance
|
|
func (api *DevAPI) SetBalance(ctx context.Context, address common.Address, balance hexutil.Big) error {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
// Get the current state
|
|
header := api.eth.blockchain.CurrentBlock()
|
|
statedb, err := api.eth.blockchain.StateAt(header.Root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the balance
|
|
u256Balance, overflow := uint256.FromBig((*big.Int)(&balance))
|
|
if overflow {
|
|
return errors.New("balance overflow")
|
|
}
|
|
statedb.SetBalance(address, u256Balance, tracing.BalanceChangeUnspecified)
|
|
|
|
// Commit the state changes and create a new block
|
|
return api.commitStateChanges(statedb)
|
|
}
|
|
|
|
// SetStorageAt sets a storage slot value for an address.
|
|
// Anvil: anvil_setStorageAt
|
|
// Hardhat: hardhat_setStorageAt
|
|
func (api *DevAPI) SetStorageAt(ctx context.Context, address common.Address, slot common.Hash, value common.Hash) error {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
// Get the current state
|
|
header := api.eth.blockchain.CurrentBlock()
|
|
statedb, err := api.eth.blockchain.StateAt(header.Root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the storage value
|
|
statedb.SetState(address, slot, value)
|
|
|
|
// Commit the state changes and create a new block
|
|
return api.commitStateChanges(statedb)
|
|
}
|
|
|
|
// SetCode sets the code of an address.
|
|
// Anvil: anvil_setCode
|
|
// Hardhat: hardhat_setCode
|
|
func (api *DevAPI) SetCode(ctx context.Context, address common.Address, code hexutil.Bytes) error {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
// Get the current state
|
|
header := api.eth.blockchain.CurrentBlock()
|
|
statedb, err := api.eth.blockchain.StateAt(header.Root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the code
|
|
statedb.SetCode(address, code, tracing.CodeChangeUnspecified)
|
|
|
|
// Commit the state changes and create a new block
|
|
return api.commitStateChanges(statedb)
|
|
}
|
|
|
|
// SetNonce sets the nonce of an address.
|
|
// Anvil: anvil_setNonce
|
|
// Hardhat: hardhat_setNonce
|
|
func (api *DevAPI) SetNonce(ctx context.Context, address common.Address, nonce hexutil.Uint64) error {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
// Get the current state
|
|
header := api.eth.blockchain.CurrentBlock()
|
|
statedb, err := api.eth.blockchain.StateAt(header.Root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the nonce
|
|
statedb.SetNonce(address, uint64(nonce), tracing.NonceChangeUnspecified)
|
|
|
|
// Commit the state changes and create a new block
|
|
return api.commitStateChanges(statedb)
|
|
}
|
|
|
|
// Mine forces mining of a new block.
|
|
// Anvil: evm_mine
|
|
// Hardhat: evm_mine
|
|
func (api *DevAPI) Mine(ctx context.Context, timestamp *hexutil.Uint64) (common.Hash, error) {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
// Generate a new block using the miner
|
|
block, err := api.eth.miner.GenerateBlock(nil)
|
|
if err != nil {
|
|
return common.Hash{}, err
|
|
}
|
|
|
|
// Insert the block into the chain
|
|
if err := api.eth.blockchain.InsertBlock(block); err != nil {
|
|
return common.Hash{}, err
|
|
}
|
|
|
|
// Accept the block
|
|
if err := api.eth.blockchain.Accept(block); err != nil {
|
|
return common.Hash{}, err
|
|
}
|
|
// Note: We don't call DrainAcceptorQueue() here because it triggers
|
|
// snapshot flatten which isn't supported in PathDB mode.
|
|
|
|
return block.Hash(), nil
|
|
}
|
|
|
|
// Snapshot creates a snapshot of the current state.
|
|
// Returns a snapshot ID that can be used with Revert.
|
|
// Anvil: evm_snapshot
|
|
// Hardhat: evm_snapshot
|
|
func (api *DevAPI) Snapshot(ctx context.Context) hexutil.Uint64 {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
// Store the current block hash as a snapshot
|
|
blockHash := api.eth.blockchain.CurrentBlock().Hash()
|
|
snapID := api.nextSnap
|
|
api.snapshots[snapID] = blockHash
|
|
api.nextSnap++
|
|
|
|
return snapID
|
|
}
|
|
|
|
// Revert reverts the state to a previous snapshot.
|
|
// Note: In chain-evm, revert is limited - it can only revert to the current or
|
|
// recent state. Full reorg capabilities are not available.
|
|
// Anvil: evm_revert
|
|
// Hardhat: evm_revert
|
|
func (api *DevAPI) Revert(ctx context.Context, snapID hexutil.Uint64) (bool, error) {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
blockHash, ok := api.snapshots[snapID]
|
|
if !ok {
|
|
return false, errors.New("snapshot not found")
|
|
}
|
|
|
|
// Get the block by hash
|
|
block := api.eth.blockchain.GetBlockByHash(blockHash)
|
|
if block == nil {
|
|
return false, errors.New("snapshot block not found")
|
|
}
|
|
|
|
// For now, just verify the snapshot block exists and delete the snapshot
|
|
// Full revert functionality requires SetPreference which may cause issues
|
|
currentNumber := api.eth.blockchain.CurrentBlock().Number.Uint64()
|
|
snapNumber := block.NumberU64()
|
|
|
|
if snapNumber > currentNumber {
|
|
return false, errors.New("cannot revert to future block")
|
|
}
|
|
|
|
// Delete the snapshot and all snapshots after it
|
|
for id := range api.snapshots {
|
|
if id >= snapID {
|
|
delete(api.snapshots, id)
|
|
}
|
|
}
|
|
|
|
// If trying to revert to the same block, it's a no-op success
|
|
if snapNumber == currentNumber && api.eth.blockchain.CurrentBlock().Hash() == blockHash {
|
|
return true, nil
|
|
}
|
|
|
|
// Otherwise, try to set preference back to the snapshot block
|
|
if err := api.eth.blockchain.SetPreference(block); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// IncreaseTime increases the block timestamp by mining a new block with adjusted time.
|
|
// Anvil: evm_increaseTime
|
|
// Hardhat: evm_increaseTime
|
|
func (api *DevAPI) IncreaseTime(ctx context.Context, seconds hexutil.Uint64) (hexutil.Uint64, error) {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
// Get current block time and calculate the new time
|
|
currentTime := api.eth.blockchain.CurrentBlock().Time
|
|
newTime := currentTime + uint64(seconds)
|
|
|
|
// Generate and insert a new block (the miner will use current clock time)
|
|
block, err := api.eth.miner.GenerateBlock(nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if err := api.eth.blockchain.InsertBlock(block); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if err := api.eth.blockchain.Accept(block); err != nil {
|
|
return 0, err
|
|
}
|
|
// Note: We don't call DrainAcceptorQueue() here because it triggers
|
|
// snapshot flatten which isn't supported in PathDB mode.
|
|
|
|
return hexutil.Uint64(newTime), nil
|
|
}
|
|
|
|
// SetNextBlockTimestamp sets the timestamp for the next block by mining one.
|
|
// Anvil: evm_setNextBlockTimestamp
|
|
// Hardhat: evm_setNextBlockTimestamp
|
|
func (api *DevAPI) SetNextBlockTimestamp(ctx context.Context, timestamp hexutil.Uint64) error {
|
|
// Mine a new block - the timestamp will be adjusted by the clock
|
|
_, err := api.Mine(ctx, ×tamp)
|
|
return err
|
|
}
|
|
|
|
// ImpersonateAccount starts impersonating an account (allows sending tx without private key).
|
|
// Anvil: anvil_impersonateAccount
|
|
// Hardhat: hardhat_impersonateAccount
|
|
func (api *DevAPI) ImpersonateAccount(ctx context.Context, address common.Address) error {
|
|
// In dev mode, all accounts are effectively impersonatable
|
|
// This is a no-op for compatibility
|
|
return nil
|
|
}
|
|
|
|
// StopImpersonatingAccount stops impersonating an account.
|
|
// Anvil: anvil_stopImpersonatingAccount
|
|
// Hardhat: hardhat_stopImpersonatingAccount
|
|
func (api *DevAPI) StopImpersonatingAccount(ctx context.Context, address common.Address) error {
|
|
// This is a no-op for compatibility
|
|
return nil
|
|
}
|
|
|
|
// AutoImpersonate enables or disables auto-impersonation of all accounts.
|
|
// Anvil: anvil_autoImpersonateAccount
|
|
func (api *DevAPI) AutoImpersonate(ctx context.Context, enabled bool) error {
|
|
// This is a no-op for compatibility
|
|
return nil
|
|
}
|
|
|
|
// commitStateChanges commits state changes by creating and inserting a new block.
|
|
// This is used by SetBalance, SetStorageAt, etc. to persist changes.
|
|
func (api *DevAPI) commitStateChanges(statedb *state.StateDB) error {
|
|
// Finalize the state changes
|
|
statedb.Finalise(false)
|
|
|
|
// Commit the state to the database
|
|
_, err := statedb.Commit(api.eth.blockchain.CurrentBlock().Number.Uint64()+1, false, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate and insert a new block through the miner
|
|
// This will pick up the committed state changes
|
|
block, err := api.eth.miner.GenerateBlock(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Insert the block
|
|
if err := api.eth.blockchain.InsertBlock(block); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Accept the block
|
|
if err := api.eth.blockchain.Accept(block); err != nil {
|
|
return err
|
|
}
|
|
// Note: We don't call DrainAcceptorQueue() here because it triggers
|
|
// snapshot flatten which isn't supported in PathDB mode.
|
|
|
|
return nil
|
|
}
|
|
|
|
// DumpState returns a dump of the current state (for debugging).
|
|
func (api *DevAPI) DumpState(ctx context.Context) (state.Dump, error) {
|
|
api.mu.Lock()
|
|
defer api.mu.Unlock()
|
|
|
|
header := api.eth.blockchain.CurrentBlock()
|
|
statedb, err := api.eth.blockchain.StateAt(header.Root)
|
|
if err != nil {
|
|
return state.Dump{}, err
|
|
}
|
|
|
|
return statedb.RawDump(&state.DumpConfig{
|
|
OnlyWithAddresses: true,
|
|
Max: 256,
|
|
}), nil
|
|
}
|
|
|
|
// unused variable to ensure we use time package
|
|
var _ = time.Now
|