diff --git a/CHANGELOG.md b/CHANGELOG.md index a92d693794..0b8d1c8d83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#4032](https://github.com/sei-protocol/sei-chain/pull/4032) fix(config): the default `telemetry.prometheus-retention-time` drops from `7200` to `0`, so neither app.toml-generation pipeline (`seid init`, or the file a node writes for itself on any other subcommand) starts the Prometheus metrics sink unless an operator sets a positive retention. Freshly generated nodes keep the bounded in-memory telemetry sink used by SIGUSR1 dumps. Existing `app.toml` files are unchanged. * [#4021](https://github.com/sei-protocol/sei-chain/pull/4021) feat(grpc): per-IP rate-limit admission for the gRPC plane, off by default behind `[grpc] rate-limiting-enabled` (new `ip-rate-limit-rps` / `ip-rate-limit-burst` / `trusted-proxy-cidrs`, defaults 10 rps / 20 burst / trust no proxy). Native gRPC (:9090) is admitted by a tap handler and gRPC-Web (:9091) by HTTP middleware, both before the request is protobuf-decoded, so a throttled caller cannot spend the decoder; streams pay one token to establish and one per inbound message. Both planes draw from the same per-IP buckets. Over-budget callers get `ResourceExhausted` on :9090 and HTTP 429 on :9091, counted by `rpc_rate_limit_rejected_total{plane="grpc", method_namespace}`. * [#4078](https://github.com/sei-protocol/sei-chain/pull/4078) feat(grpc): bound concurrent in-flight RPCs and open connections per IP on the gRPC query plane. New `[grpc] max-connections-per-ip` and `[grpc-web] max-connections-per-ip` (default 0, unlimited) optionally cap one address's share of the global connection budget on :9090 and :9091, regardless of `rate-limiting-enabled`. New `[grpc] max-in-flight-per-ip` (default 100) caps concurrent RPCs per address when `rate-limiting-enabled = true`: the slot is taken at the HTTP/2 HEADERS frame and returned when the RPC ends. Both planes draw from the same per-IP pool. Concurrency rejections return `ResourceExhausted` on :9090 and HTTP 429 on :9091, counted by `rpc_inflight_rejected_total{plane, method_namespace}`; refused connections are counted by `rpc_connection_rejected_total{plane}`. +* [#4117](https://github.com/sei-protocol/sei-chain/pull/4117) chore(giga): remove the unused evmone/evmc execution path from the Giga executor. The Giga executor's production path already ran on go-ethereum's native interpreter; evmone was only reachable through a best-effort VM init that nothing consumed. Release images no longer ship `libevmone.*.so`/`.dylib` under `/usr/lib`, and the `SEI_EVMONE_LIB_DIR` operator override is removed. ### Upgrade guide * [#4032](https://github.com/sei-protocol/sei-chain/pull/4032) **The Prometheus telemetry sink is off by default.** A node whose `app.toml` is generated by this release gets `prometheus-retention-time = 0`, which leaves the sink uncreated even though `telemetry.enabled` stays `true`. `GET /metrics?format=prometheus` on the app API server (:1317) then returns `prometheus metrics are not enabled`, and the `seid` process exports no application Prometheus series. **Operators who scrape application metrics should set a positive `[telemetry] prometheus-retention-time` in `app.toml` (the previous default was `7200`) before generating a new configuration file.** Nodes that already have `prometheus-retention-time` written in `app.toml` are unaffected. diff --git a/Dockerfile b/Dockerfile index daa293b74a..361dbff7a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,10 +25,6 @@ RUN --mount=type=cache,target=/go/pkg/mod \ COPY . . -# Install the platform evmone shared library next to the other native libraries -# so the Giga executor can load it from a fixed, trusted absolute path (/usr/lib) -# at runtime instead of relying on the dynamic linker's search path. -RUN cp giga/executor/lib/libevmone.0.12.0_linux_${TARGETARCH}.so /go/lib/ ENV CGO_ENABLED=1 ARG SEI_CHAIN_REF="" ARG GO_BUILD_TAGS="" diff --git a/app/app.go b/app/app.go index ec1c7e8324..bd9f7ef77f 100644 --- a/app/app.go +++ b/app/app.go @@ -113,7 +113,6 @@ import ( evmrpcconfig "github.com/sei-protocol/sei-chain/evmrpc/config" gigaexecutor "github.com/sei-protocol/sei-chain/giga/executor" gigaconfig "github.com/sei-protocol/sei-chain/giga/executor/config" - gigalib "github.com/sei-protocol/sei-chain/giga/executor/lib" gigaprecompiles "github.com/sei-protocol/sei-chain/giga/executor/precompiles" gigautils "github.com/sei-protocol/sei-chain/giga/executor/utils" "github.com/sei-protocol/sei-chain/precompiles" @@ -758,12 +757,6 @@ func New( app.GigaOCCEnabled = gigaExecutorConfig.OCCEnabled tmtypes.SkipLastResultsHashValidation.Store(gigaExecutorConfig.Enabled) if gigaExecutorConfig.Enabled { - // evmone is loaded best-effort - if evmoneVM, err := gigalib.InitEvmoneVM(); err == nil { - app.GigaEvmKeeper.EvmoneVM = evmoneVM - } else { - logger.Debug("failed to load evmone VM", "error", err) - } // evm_giga_mixed_tests.sh matches these ENABLED/DISABLED strings to guard node roles; keep them in sync. if gigaExecutorConfig.OCCEnabled { logger.Info("benchmark: Giga Executor with OCC is ENABLED - using new EVM execution path with parallel execution") diff --git a/app/test_helpers.go b/app/test_helpers.go index e33512e322..b1f9be1660 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -13,7 +13,6 @@ import ( "testing" "time" - gigalib "github.com/sei-protocol/sei-chain/giga/executor/lib" "github.com/sei-protocol/sei-chain/sei-cosmos/client" slashingtypes "github.com/sei-protocol/sei-chain/sei-cosmos/x/slashing/types" "github.com/sei-protocol/sei-chain/sei-cosmos/x/staking" @@ -167,13 +166,6 @@ func NewGigaTestWrapperWithRegularStore(t *testing.T, tm time.Time, valPub crypt // Configure GigaBankKeeper to use regular KVStore instead of GigaKVStore wrapper.App.GigaBankKeeper.UseRegularStore = true - // Initialize evmone VM if not already initialized (best effort) - if wrapper.App.GigaEvmKeeper.EvmoneVM == nil { - if evmoneVM, err := gigalib.InitEvmoneVM(); err == nil { - wrapper.App.GigaEvmKeeper.EvmoneVM = evmoneVM - } - } - return wrapper } diff --git a/giga/deps/xevm/keeper/keeper.go b/giga/deps/xevm/keeper/keeper.go index af8a77d05f..dcab213ab1 100644 --- a/giga/deps/xevm/keeper/keeper.go +++ b/giga/deps/xevm/keeper/keeper.go @@ -10,7 +10,6 @@ import ( "sort" "sync" - "github.com/ethereum/evmc/v12/bindings/go/evmc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" @@ -81,9 +80,6 @@ type Keeper struct { latestCustomPrecompiles map[common.Address]vm.PrecompiledContract latestUpgrade string - // EvmoneVM holds the loaded evmone VM instance for the Giga executor - EvmoneVM *evmc.VM - // UseRegularStore when true causes PrefixStore to use ctx.KVStore instead of ctx.GigaKVStore. // This is for debugging/testing to isolate Giga executor logic from GigaKVStore layer. UseRegularStore bool diff --git a/giga/executor/executor.go b/giga/executor/executor.go index 4145c2b427..9f4fa0623b 100644 --- a/giga/executor/executor.go +++ b/giga/executor/executor.go @@ -3,7 +3,6 @@ package executor import ( "math/big" - "github.com/ethereum/evmc/v12/bindings/go/evmc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -16,17 +15,6 @@ type Executor struct { evm *vm.EVM } -func NewEvmoneExecutor(evmoneVM *evmc.VM, blockCtx vm.BlockContext, stateDB vm.StateDB, chainConfig *params.ChainConfig, config vm.Config, customPrecompiles map[common.Address]vm.PrecompiledContract) *Executor { - evm := vm.NewEVM(blockCtx, stateDB, chainConfig, config, customPrecompiles) - // Pre-compute HostContext config from chain config (avoids per-SSTORE overhead) - hostConfig := internal.NewHostContextConfig(chainConfig) - hostContext := internal.NewHostContext(evmoneVM, evm, hostConfig) - evm.EVMInterpreter = internal.NewEVMInterpreter(hostContext, evm) - return &Executor{ - evm: evm, - } -} - func NewGethExecutor(blockCtx vm.BlockContext, stateDB vm.StateDB, chainConfig *params.ChainConfig, config vm.Config, customPrecompiles map[common.Address]vm.PrecompiledContract) *Executor { evm := vm.NewEVM(blockCtx, stateDB, chainConfig, config, customPrecompiles) return &Executor{ diff --git a/giga/executor/internal/host_context.go b/giga/executor/internal/host_context.go deleted file mode 100644 index 01ce578c09..0000000000 --- a/giga/executor/internal/host_context.go +++ /dev/null @@ -1,402 +0,0 @@ -package internal - -import ( - "errors" - "fmt" - "math" - "sync/atomic" - - "github.com/ethereum/evmc/v12/bindings/go/evmc" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" - ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/params" - "github.com/holiman/uint256" - "github.com/sei-protocol/sei-chain/giga/executor/precompiles" -) - -var _ evmc.HostContext = (*HostContext)(nil) - -// StandardSstoreSetGasEIP2200 is the standard EIP-2200 SSTORE gas cost for setting -// storage from zero to non-zero. evmone uses this internally. -const StandardSstoreSetGasEIP2200 = uint64(20000) - -// HostContextConfig holds configuration for the HostContext. -// Values are pre-computed from ChainConfig at construction time for efficiency. -type HostContextConfig struct { - // SstoreGasDelta is the per-SSTORE gas delta (Sei custom cost - standard 20k). - // This is added to gas consumption for each StorageAdded operation. - // Can be negative if Sei cost is below standard (results in gas reduction). - SstoreGasDelta int64 -} - -// NewHostContextConfig creates a HostContextConfig from the chain config. -// This extracts and pre-computes values needed by HostContext. -func NewHostContextConfig(chainConfig *params.ChainConfig) HostContextConfig { - var delta int64 - if chainConfig != nil && chainConfig.SeiSstoreSetGasEIP2200 != nil { - seiSstoreGas := *chainConfig.SeiSstoreSetGasEIP2200 - - // Guard against overflow: seiSstoreGas is uint64, and casting to int64 would - // overflow if value > math.MaxInt64. This is a critical misconfiguration. - if seiSstoreGas > uint64(math.MaxInt64) { - panic(fmt.Sprintf("SeiSstoreSetGasEIP2200 (%d) exceeds maximum safe value (%d)", - seiSstoreGas, uint64(math.MaxInt64))) - } - - // Delta = Sei cost - standard cost (can be positive or negative) - // Safe to cast now that we've verified seiSstoreGas <= math.MaxInt64 - delta = int64(seiSstoreGas) - int64(StandardSstoreSetGasEIP2200) - } - return HostContextConfig{ - SstoreGasDelta: delta, - } -} - -type HostContext struct { - vm *evmc.VM - evm *vm.EVM - config HostContextConfig - // sstoreGasAdjustment accumulates the total extra gas to charge for SSTORE operations. - // This is applied after evmone execution completes. - sstoreGasAdjustment atomic.Int64 -} - -// NewHostContext creates a new HostContext with the given configuration. -func NewHostContext(vm *evmc.VM, evm *vm.EVM, config HostContextConfig) *HostContext { - return &HostContext{ - vm: vm, - evm: evm, - config: config, - } -} - -// GetSstoreGasAdjustment returns the total accumulated SSTORE gas adjustment. -// This should be called after execution to apply the extra gas charge. -func (h *HostContext) GetSstoreGasAdjustment() int64 { - return h.sstoreGasAdjustment.Load() -} - -// ResetSstoreGasAdjustment resets the accumulated SSTORE gas adjustment to zero. -func (h *HostContext) ResetSstoreGasAdjustment() { - h.sstoreGasAdjustment.Store(0) -} - -func (h *HostContext) AccountExists(addr evmc.Address) bool { - return h.evm.StateDB.Exist(common.Address(addr)) -} - -func (h *HostContext) GetStorage(addr evmc.Address, key evmc.Hash) evmc.Hash { - return evmc.Hash(h.evm.StateDB.GetState(common.Address(addr), common.Hash(key))) -} - -func (h *HostContext) SetStorage(addr evmc.Address, key evmc.Hash, value evmc.Hash) evmc.StorageStatus { - gethAddr := common.Address(addr) - gethKey := common.Hash(key) - - current := h.evm.StateDB.GetState(gethAddr, gethKey) - original := h.evm.StateDB.GetCommittedState(gethAddr, gethKey) - - dirty := original.Cmp(current) != 0 - restored := original.Cmp(common.Hash(value)) == 0 - currentIsZero := current.Cmp(common.Hash{}) == 0 - valueIsZero := common.Hash(value).Cmp(common.Hash{}) == 0 - - status := evmc.StorageAssigned - if !dirty && !restored { - if currentIsZero { - status = evmc.StorageAdded - } else if valueIsZero { - status = evmc.StorageDeleted - } else { - status = evmc.StorageModified - } - } else if dirty && !restored { - if currentIsZero && valueIsZero { - status = evmc.StorageDeletedAdded - } else if !currentIsZero && valueIsZero { - status = evmc.StorageModifiedDeleted - } - } else if dirty { - if currentIsZero { - status = evmc.StorageDeletedRestored - } else if valueIsZero { - status = evmc.StorageAddedDeleted - } else { - status = evmc.StorageModifiedRestored - } - } - - // Accumulate SSTORE gas adjustment for StorageAdded operations. - // evmone uses standard EIP-2200 gas (20k), but Sei may have a different cost. - // We track the delta here and apply it after execution. - // Delta can be positive (higher cost) or negative (lower cost). - if status == evmc.StorageAdded && h.config.SstoreGasDelta != 0 { - h.sstoreGasAdjustment.Add(h.config.SstoreGasDelta) - } - - h.evm.StateDB.SetState(gethAddr, gethKey, common.Hash(value)) - return status -} - -func (h *HostContext) GetBalance(addr evmc.Address) evmc.Hash { - return h.evm.StateDB.GetBalance(common.Address(addr)).Bytes32() -} - -func (h *HostContext) GetCodeSize(addr evmc.Address) int { - return h.evm.StateDB.GetCodeSize(common.Address(addr)) -} - -func (h *HostContext) GetCodeHash(addr evmc.Address) evmc.Hash { - return evmc.Hash(h.evm.StateDB.GetCodeHash(common.Address(addr))) -} - -func (h *HostContext) GetCode(addr evmc.Address) []byte { - return h.evm.StateDB.GetCode(common.Address(addr)) -} - -// todo(pdrobnjak): support historical selfdestruct logic as well -func (h *HostContext) Selfdestruct(addr evmc.Address, beneficiary evmc.Address) bool { - addrKey := common.Address(addr) - beneficiaryKey := common.Address(beneficiary) - amt := h.evm.StateDB.GetBalance(addrKey) - h.evm.StateDB.SubBalance(addrKey, amt, tracing.BalanceDecreaseSelfdestruct) - h.evm.StateDB.AddBalance(beneficiaryKey, amt, tracing.BalanceIncreaseSelfdestruct) - h.evm.StateDB.SelfDestruct6780(common.Address(addr)) - return true -} - -func (h *HostContext) GetTxContext() evmc.TxContext { - var gasPrice evmc.Hash - h.evm.GasPrice.FillBytes(gasPrice[:]) - - var prevRandao evmc.Hash - if h.evm.Context.Random != nil { - prevRandao = evmc.Hash(*h.evm.Context.Random) - } - - var chainID evmc.Hash - h.evm.ChainConfig().ChainID.FillBytes(chainID[:]) - - var baseFee evmc.Hash - h.evm.Context.BaseFee.FillBytes(baseFee[:]) - - var blobBaseFee evmc.Hash - h.evm.Context.BlobBaseFee.FillBytes(blobBaseFee[:]) - - //nolint:gosec // G115: safe integer conversions for Time and GasLimit - return evmc.TxContext{ - GasPrice: gasPrice, - Origin: evmc.Address(h.evm.Origin), - Coinbase: evmc.Address(h.evm.Context.Coinbase), - Number: h.evm.Context.BlockNumber.Int64(), - Timestamp: int64(h.evm.Context.Time), - GasLimit: int64(h.evm.Context.GasLimit), - PrevRandao: prevRandao, - ChainID: chainID, - BaseFee: baseFee, - BlobBaseFee: blobBaseFee, - } -} - -func (h *HostContext) GetBlockHash(number int64) evmc.Hash { - //nolint:gosec // G115: safe, block numbers are always positive - return evmc.Hash(h.evm.Context.GetHash(uint64(number))) -} - -func (h *HostContext) EmitLog(addr evmc.Address, topics []evmc.Hash, data []byte) { - gethTopics := make([]common.Hash, len(topics)) - for i, topic := range topics { - gethTopics[i] = common.Hash(topic) - } - h.evm.StateDB.AddLog(ðtypes.Log{Address: common.Address(addr), Topics: gethTopics, Data: data}) -} - -func (h *HostContext) Execute(kind evmc.CallKind, recipient evmc.Address, sender evmc.Address, value evmc.Hash, input []byte, gas int64, - depth int, static bool) ([]byte, int64, int64, evmc.Address, error) { - evmRevision := h.getEVMRevision() - delegated := kind == evmc.DelegateCall || kind == evmc.CallCode - - // For CREATE/CREATE2, the input contains the initcode (constructor bytecode) - // For regular calls, fetch the code from the target address - var code []byte - if kind == evmc.Create || kind == evmc.Create2 { - code = input // initcode is passed as input for contract creation - } else { - code = h.evm.StateDB.GetCode(common.Address(recipient)) - } - - executionResult, err := h.vm.Execute( - h, evmRevision, kind, static, delegated, depth, - gas, recipient, sender, input, value, code, - ) - - if err != nil { - return nil, 0, 0, evmc.Address{}, err - } - - // todo(pdrobnjak): calculate/propagate created address - var createAddr evmc.Address - if kind == evmc.Create || kind == evmc.Create2 { - // The created address should be set in the execution result - // For now, return empty - this needs to be populated from evmone's result - createAddr = evmc.Address{} - } - - return executionResult.Output, executionResult.GasLeft, executionResult.GasRefund, createAddr, nil -} - -func (h *HostContext) Call( - kind evmc.CallKind, recipient evmc.Address, sender evmc.Address, value evmc.Hash, input []byte, gas int64, - _ int, static bool, salt evmc.Hash, _ evmc.Address, -) ([]byte, int64, int64, evmc.Address, error) { - recipientAddr := common.Address(recipient) - senderAddr := common.Address(sender) - valueUint256 := new(uint256.Int).SetBytes(value[:]) - var ret []byte - var leftoverGas uint64 - var err error - var createAddr common.Address - - //nolint:gosec // G115: safe integer conversions for gas values - switch kind { - case evmc.Call: - if static { - ret, leftoverGas, err = h.evm.StaticCall(senderAddr, recipientAddr, input, uint64(gas)) - } else { - ret, leftoverGas, err = h.evm.Call(senderAddr, recipientAddr, input, uint64(gas), valueUint256) - } - case evmc.DelegateCall: - // todo(pdrobnjak): sender and recipient might not be correctly propagated in case of DELEGATECALL - ret, leftoverGas, err = h.evm.DelegateCall( - h.evm.Origin, senderAddr, recipientAddr, input, uint64(gas), valueUint256, - ) - case evmc.CallCode: - ret, leftoverGas, err = h.evm.CallCode(senderAddr, recipientAddr, input, uint64(gas), valueUint256) - case evmc.Create: - ret, createAddr, leftoverGas, err = h.evm.Create(senderAddr, input, uint64(gas), valueUint256) - return ret, int64(leftoverGas), 0, evmc.Address(createAddr), toEvmcError(err) - case evmc.Create2: - saltUint256 := new(uint256.Int).SetBytes(salt[:]) - ret, createAddr, leftoverGas, err = h.evm.Create2(senderAddr, input, uint64(gas), valueUint256, saltUint256) - return ret, int64(leftoverGas), 0, evmc.Address(createAddr), toEvmcError(err) - default: - panic("EofCreate is not supported") - } - - //nolint:gosec // G115: safe, leftoverGas won't exceed int64 max - return ret, int64(leftoverGas), 0, evmc.Address{}, toEvmcError(err) -} - -func (h *HostContext) AccessAccount(addr evmc.Address) evmc.AccessStatus { - gethAddr := common.Address(addr) - addrInAccessList := h.evm.StateDB.AddressInAccessList(gethAddr) - if addrInAccessList { - return evmc.WarmAccess - } - // After a cold access, add address to access list so subsequent accesses are warm - h.evm.StateDB.AddAddressToAccessList(gethAddr) - return evmc.ColdAccess -} - -func (h *HostContext) AccessStorage(addr evmc.Address, key evmc.Hash) evmc.AccessStatus { - gethAddr := common.Address(addr) - gethKey := common.Hash(key) - addrInAccessList, slotInAccessList := h.evm.StateDB.SlotInAccessList(gethAddr, gethKey) - if addrInAccessList && slotInAccessList { - return evmc.WarmAccess - } - // After a cold access, add slot to access list so subsequent accesses are warm - h.evm.StateDB.AddSlotToAccessList(gethAddr, gethKey) - return evmc.ColdAccess -} - -func (h *HostContext) GetTransientStorage(addr evmc.Address, key evmc.Hash) evmc.Hash { - return evmc.Hash(h.evm.StateDB.GetTransientState(common.Address(addr), common.Hash(key))) -} - -func (h *HostContext) SetTransientStorage(addr evmc.Address, key evmc.Hash, value evmc.Hash) { - h.evm.StateDB.SetTransientState(common.Address(addr), common.Hash(key), common.Hash(value)) -} - -// getEVMRevision determines the EVM revision based on the current chain configuration -func (h *HostContext) getEVMRevision() evmc.Revision { - chainConfig := h.evm.ChainConfig() - blockNumber := h.evm.Context.BlockNumber - time := h.evm.Context.Time - isMerge := h.evm.Context.Random != nil - - // Get the rules for the current block - rules := chainConfig.Rules(blockNumber, isMerge, time) - - // Check from newest to oldest using rules - // NOTE: Prague support in evmone 0.12.0 may have incomplete gas rules, - // so we cap at Cancun for now until evmone is updated - if rules.IsPrague || rules.IsCancun { - return evmc.Cancun - } - if rules.IsShanghai { - return evmc.Shanghai - } - if rules.IsMerge { - return evmc.Paris - } - if rules.IsLondon { - return evmc.London - } - if rules.IsBerlin { - return evmc.Berlin - } - if rules.IsIstanbul { - return evmc.Istanbul - } - if rules.IsPetersburg { - return evmc.Petersburg - } - if rules.IsConstantinople { - return evmc.Constantinople - } - if rules.IsByzantium { - return evmc.Byzantium - } - if rules.IsEIP158 { - return evmc.SpuriousDragon - } - if rules.IsEIP150 { - return evmc.TangerineWhistle - } - if rules.IsHomestead { - return evmc.Homestead - } - return evmc.Frontier -} - -// toEvmcError converts a Go error to an evmc.Error. -// The EVMC bindings expect Call() to return evmc.Error type, not standard Go errors. -// -// Note: The evmc Go bindings currently only expose evmc.Failure and evmc.Revert constants. -// Additional error codes like EVMC_OUT_OF_GAS (3), EVMC_INVALID_INSTRUCTION (4), etc. -// are defined in the C header (evmc/evmc.h) but not exported in the Go bindings. -// To add proper mapping for vm.ErrOutOfGas -> evmc.OutOfGas, the Go bindings in -// github.com/ethereum/evmc would need to be extended first. -func toEvmcError(err error) error { - switch { - case err == nil: - return nil - case errors.As(err, new(evmc.Error)): - // Already an evmc.Error, return as-is - return err - case errors.Is(err, vm.ErrExecutionReverted): - return evmc.Revert - default: - // All other errors map to generic failure - // TODO: Add evmc.OutOfGas mapping once the Go bindings expose it - return evmc.Failure - } -} - -// To be called by an exported EVM create function which knows how to instantiate params like statedb. -func createEVMWithFailFastPrecompile(blockContext vm.BlockContext, statedb vm.StateDB, chainConfig *params.ChainConfig, vmConfig vm.Config) *vm.EVM { - return vm.NewEVM(blockContext, statedb, chainConfig, vmConfig, precompiles.AllCustomPrecompilesFailFast) -} diff --git a/giga/executor/internal/host_context_test.go b/giga/executor/internal/host_context_test.go deleted file mode 100644 index 33631d49d9..0000000000 --- a/giga/executor/internal/host_context_test.go +++ /dev/null @@ -1,642 +0,0 @@ -package internal - -import ( - "math" - "testing" - - "github.com/ethereum/evmc/v12/bindings/go/evmc" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/params" - "github.com/stretchr/testify/require" -) - -// TestStorageStatusLogic tests the storage status determination logic -// This tests the algorithm used in SetStorage without needing a full StateDB -func TestStorageStatusLogic(t *testing.T) { - tests := []struct { - name string - original common.Hash // committed state - current common.Hash // current state in tx - newValue common.Hash // value being set - expectedStatus evmc.StorageStatus - }{ - { - name: "StorageAdded - zero to non-zero, clean", - original: common.Hash{}, - current: common.Hash{}, - newValue: common.Hash{1, 2, 3}, - expectedStatus: evmc.StorageAdded, - }, - { - name: "StorageDeleted - non-zero to zero, clean", - original: common.Hash{1, 2, 3}, - current: common.Hash{1, 2, 3}, - newValue: common.Hash{}, - expectedStatus: evmc.StorageDeleted, - }, - { - name: "StorageModified - non-zero to different non-zero, clean", - original: common.Hash{1, 2, 3}, - current: common.Hash{1, 2, 3}, - newValue: common.Hash{4, 5, 6}, - expectedStatus: evmc.StorageModified, - }, - { - name: "StorageAssigned - dirty, not restored, same value", - original: common.Hash{1, 1, 1}, - current: common.Hash{2, 2, 2}, - newValue: common.Hash{2, 2, 2}, // same as current - expectedStatus: evmc.StorageAssigned, - }, - { - name: "StorageDeletedRestored - was deleted, restore to original", - original: common.Hash{1, 2, 3}, - current: common.Hash{}, // deleted - newValue: common.Hash{1, 2, 3}, // restore - expectedStatus: evmc.StorageDeletedRestored, - }, - { - name: "StorageAddedDeleted - was added, now delete", - original: common.Hash{}, - current: common.Hash{1, 2, 3}, // added - newValue: common.Hash{}, // delete (restore to original) - expectedStatus: evmc.StorageAddedDeleted, - }, - { - name: "StorageModifiedRestored - was modified, restore to original", - original: common.Hash{1, 2, 3}, - current: common.Hash{4, 5, 6}, // modified - newValue: common.Hash{1, 2, 3}, // restore - expectedStatus: evmc.StorageModifiedRestored, - }, - { - name: "StorageDeletedAdded - was deleted, add back different", - original: common.Hash{1, 2, 3}, - current: common.Hash{}, // deleted - newValue: common.Hash{4, 5, 6}, // different non-zero - expectedStatus: evmc.StorageAssigned, // dirty && !restored && !(currentIsZero && valueIsZero) - }, - { - name: "StorageModifiedDeleted - was modified, now delete", - original: common.Hash{1, 2, 3}, - current: common.Hash{4, 5, 6}, // modified, not zero - newValue: common.Hash{}, // delete - expectedStatus: evmc.StorageModifiedDeleted, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - status := computeStorageStatus(tt.original, tt.current, tt.newValue) - require.Equal(t, tt.expectedStatus, status, "Storage status mismatch") - }) - } -} - -// computeStorageStatus replicates the logic from SetStorage for testing -func computeStorageStatus(original, current, value common.Hash) evmc.StorageStatus { - dirty := original.Cmp(current) != 0 - restored := original.Cmp(value) == 0 - currentIsZero := current.Cmp(common.Hash{}) == 0 - valueIsZero := value.Cmp(common.Hash{}) == 0 - - status := evmc.StorageAssigned - if !dirty && !restored { - if currentIsZero { - status = evmc.StorageAdded - } else if valueIsZero { - status = evmc.StorageDeleted - } else { - status = evmc.StorageModified - } - } else if dirty && !restored { - if currentIsZero && valueIsZero { - status = evmc.StorageDeletedAdded - } else if !currentIsZero && valueIsZero { - status = evmc.StorageModifiedDeleted - } - } else if dirty { - if currentIsZero { - status = evmc.StorageDeletedRestored - } else if valueIsZero { - status = evmc.StorageAddedDeleted - } else { - status = evmc.StorageModifiedRestored - } - } - - return status -} - -// TestAccessStatusLogic tests the access list status determination -func TestAccessStatusLogic(t *testing.T) { - // Simulate access list behavior - accessList := make(map[common.Address]map[common.Hash]bool) - - addr1 := common.Address{1, 2, 3} - addr2 := common.Address{4, 5, 6} - slot1 := common.Hash{7, 8, 9} - slot2 := common.Hash{10, 11, 12} - - // Test address access - t.Run("AddressAccess_ColdThenWarm", func(t *testing.T) { - // First access to addr1 should be cold - _, exists := accessList[addr1] - require.False(t, exists, "First access should be cold") - - // Add to access list - accessList[addr1] = make(map[common.Hash]bool) - - // Second access should be warm - _, exists = accessList[addr1] - require.True(t, exists, "Second access should be warm") - }) - - t.Run("StorageAccess_ColdThenWarm", func(t *testing.T) { - // Ensure address is in access list - accessList[addr2] = make(map[common.Hash]bool) - - // First slot access should be cold - slotExists := accessList[addr2][slot1] - require.False(t, slotExists, "First slot access should be cold") - - // Add slot to access list - accessList[addr2][slot1] = true - - // Second access should be warm - slotExists = accessList[addr2][slot1] - require.True(t, slotExists, "Second slot access should be warm") - - // Different slot should still be cold - slot2Exists := accessList[addr2][slot2] - require.False(t, slot2Exists, "Different slot should be cold") - }) -} - -// TestEvmcAddressConversion tests address type conversions -func TestEvmcAddressConversion(t *testing.T) { - gethAddr := common.Address{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} - evmcAddr := evmc.Address(gethAddr) - - // Convert back - recoveredAddr := common.Address(evmcAddr) - - require.Equal(t, gethAddr, recoveredAddr) -} - -// TestEvmcHashConversion tests hash type conversions -func TestEvmcHashConversion(t *testing.T) { - gethHash := common.Hash{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} - evmcHash := evmc.Hash(gethHash) - - // Convert back - recoveredHash := common.Hash(evmcHash) - - require.Equal(t, gethHash, recoveredHash) -} - -// TestTransientStorageLogic tests transient storage behavior -func TestTransientStorageLogic(t *testing.T) { - transient := make(map[common.Address]map[common.Hash]common.Hash) - - addr := common.Address{1, 2, 3} - key := common.Hash{4, 5, 6} - value := common.Hash{7, 8, 9} - - // Initial read should return empty - if transient[addr] == nil { - transient[addr] = make(map[common.Hash]common.Hash) - } - got := transient[addr][key] - require.Equal(t, common.Hash{}, got) - - // Set value - transient[addr][key] = value - - // Read should return value - got = transient[addr][key] - require.Equal(t, value, got) -} - -// TestCallKindValues tests that evmc call kinds have expected values -func TestCallKindValues(t *testing.T) { - // Ensure the call kinds we use are defined - require.NotEqual(t, evmc.Call, evmc.Create) - require.NotEqual(t, evmc.Call, evmc.Create2) - require.NotEqual(t, evmc.Call, evmc.DelegateCall) - require.NotEqual(t, evmc.Call, evmc.CallCode) -} - -// TestAccessStatusValues tests evmc access status values -func TestAccessStatusValues(t *testing.T) { - require.NotEqual(t, evmc.ColdAccess, evmc.WarmAccess) -} - -// TestStorageStatusValues tests that all storage status values are distinct -func TestStorageStatusValues(t *testing.T) { - statuses := []evmc.StorageStatus{ - evmc.StorageAssigned, - evmc.StorageAdded, - evmc.StorageDeleted, - evmc.StorageModified, - evmc.StorageDeletedAdded, - evmc.StorageModifiedDeleted, - evmc.StorageDeletedRestored, - evmc.StorageAddedDeleted, - evmc.StorageModifiedRestored, - } - - // Check all values are unique - seen := make(map[evmc.StorageStatus]bool) - for _, s := range statuses { - require.False(t, seen[s], "Duplicate storage status value: %v", s) - seen[s] = true - } -} - -// TestExecuteCodeSelection tests the logic for selecting code in Execute -func TestExecuteCodeSelection(t *testing.T) { - tests := []struct { - name string - kind evmc.CallKind - input []byte - recipientCode []byte - expectedCode []byte - }{ - { - name: "CREATE uses input as initcode", - kind: evmc.Create, - input: []byte{0x60, 0x00, 0xf3}, // initcode - recipientCode: []byte{0xfe}, // should not be used - expectedCode: []byte{0x60, 0x00, 0xf3}, - }, - { - name: "CREATE2 uses input as initcode", - kind: evmc.Create2, - input: []byte{0x60, 0x01, 0xf3}, // initcode - recipientCode: []byte{0xfe}, // should not be used - expectedCode: []byte{0x60, 0x01, 0xf3}, - }, - { - name: "CALL uses recipient code", - kind: evmc.Call, - input: []byte{0x12, 0x34}, // call data - recipientCode: []byte{0x60, 0x00, 0x52, 0x60, 0x20, 0xf3}, - expectedCode: []byte{0x60, 0x00, 0x52, 0x60, 0x20, 0xf3}, - }, - { - name: "DELEGATECALL uses recipient code", - kind: evmc.DelegateCall, - input: []byte{0x12, 0x34}, - recipientCode: []byte{0x60, 0x00, 0xf3}, - expectedCode: []byte{0x60, 0x00, 0xf3}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var code []byte - if tt.kind == evmc.Create || tt.kind == evmc.Create2 { - code = tt.input // initcode is passed as input for contract creation - } else { - code = tt.recipientCode // fetch from recipient for calls - } - require.Equal(t, tt.expectedCode, code) - }) - } -} - -// TestNewHostContextConfig tests the HostContextConfig creation from ChainConfig -func TestNewHostContextConfig(t *testing.T) { - tests := []struct { - name string - chainConfig *params.ChainConfig - expectedDelta int64 - }{ - { - name: "Nil chain config", - chainConfig: nil, - expectedDelta: 0, - }, - { - name: "Nil SeiSstoreSetGasEIP2200", - chainConfig: ¶ms.ChainConfig{ - SeiSstoreSetGasEIP2200: nil, - }, - expectedDelta: 0, - }, - { - name: "Standard value (20k) - no delta", - chainConfig: ¶ms.ChainConfig{ - SeiSstoreSetGasEIP2200: func() *uint64 { v := uint64(20000); return &v }(), - }, - expectedDelta: 0, - }, - { - name: "Higher value (72k) - 52k delta", - chainConfig: ¶ms.ChainConfig{ - SeiSstoreSetGasEIP2200: func() *uint64 { v := uint64(72000); return &v }(), - }, - expectedDelta: 52000, - }, - { - name: "Higher value (100k) - 80k delta", - chainConfig: ¶ms.ChainConfig{ - SeiSstoreSetGasEIP2200: func() *uint64 { v := uint64(100000); return &v }(), - }, - expectedDelta: 80000, - }, - { - name: "Lower than standard (10k) - negative delta", - chainConfig: ¶ms.ChainConfig{ - SeiSstoreSetGasEIP2200: func() *uint64 { v := uint64(10000); return &v }(), - }, - expectedDelta: -10000, - }, - { - name: "Zero value - max negative delta", - chainConfig: ¶ms.ChainConfig{ - SeiSstoreSetGasEIP2200: func() *uint64 { v := uint64(0); return &v }(), - }, - expectedDelta: -20000, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := NewHostContextConfig(tt.chainConfig) - require.Equal(t, tt.expectedDelta, config.SstoreGasDelta, - "SstoreGasDelta mismatch for %s", tt.name) - }) - } -} - -// TestNewHostContextConfig_OverflowPanic tests that an overflow-causing value panics -func TestNewHostContextConfig_OverflowPanic(t *testing.T) { - // Value that would overflow when cast to int64 - overflowValue := uint64(math.MaxInt64) + 1 - - chainConfig := ¶ms.ChainConfig{ - SeiSstoreSetGasEIP2200: &overflowValue, - } - - require.Panics(t, func() { - NewHostContextConfig(chainConfig) - }, "Should panic when SeiSstoreSetGasEIP2200 exceeds math.MaxInt64") -} - -// TestNewHostContextConfig_MaxSafeValue tests boundary at math.MaxInt64 -func TestNewHostContextConfig_MaxSafeValue(t *testing.T) { - // Maximum safe value (exactly math.MaxInt64) should not panic - maxSafeValue := uint64(math.MaxInt64) - - chainConfig := ¶ms.ChainConfig{ - SeiSstoreSetGasEIP2200: &maxSafeValue, - } - - require.NotPanics(t, func() { - config := NewHostContextConfig(chainConfig) - // Delta = MaxInt64 - 20000 = a very large positive delta - expectedDelta := int64(math.MaxInt64) - int64(StandardSstoreSetGasEIP2200) - require.Equal(t, expectedDelta, config.SstoreGasDelta) - }, "Should not panic when SeiSstoreSetGasEIP2200 equals math.MaxInt64") -} - -// TestSstoreGasDeltaCalculation tests the SSTORE gas delta calculation logic -// that determines how much extra/less gas to charge for Sei's custom SSTORE cost. -func TestSstoreGasDeltaCalculation(t *testing.T) { - tests := []struct { - name string - seiSstoreGas uint64 - expectedDelta int64 - }{ - { - name: "Standard EIP-2200 (20k) - no adjustment", - seiSstoreGas: 20000, - expectedDelta: 0, - }, - { - name: "Higher value (72k) - 52k delta", - seiSstoreGas: 72000, - expectedDelta: 52000, - }, - { - name: "Higher custom (100k) - 80k delta", - seiSstoreGas: 100000, - expectedDelta: 80000, - }, - { - name: "Lower than standard (10k) - negative delta", - seiSstoreGas: 10000, - expectedDelta: -10000, - }, - { - name: "Zero - max negative delta", - seiSstoreGas: 0, - expectedDelta: -20000, - }, - { - name: "Just above standard (20001) - 1 delta", - seiSstoreGas: 20001, - expectedDelta: 1, - }, - { - name: "Just below standard (19999) - -1 delta", - seiSstoreGas: 19999, - expectedDelta: -1, - }, - { - name: "Exactly standard - no adjustment", - seiSstoreGas: StandardSstoreSetGasEIP2200, - expectedDelta: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Replicate the delta calculation logic: Sei cost - standard cost - delta := int64(tt.seiSstoreGas) - int64(StandardSstoreSetGasEIP2200) - - require.Equal(t, tt.expectedDelta, delta, - "Delta for seiSstoreGas=%d should be %d", tt.seiSstoreGas, tt.expectedDelta) - }) - } -} - -// TestSstoreGasAdjustmentAccumulation tests the atomic accumulation of gas adjustments -func TestSstoreGasAdjustmentAccumulation(t *testing.T) { - tests := []struct { - name string - deltas []int64 // deltas to add - expectedTotal int64 - resetMidway bool // reset after half the deltas - expectedAfterReset int64 - }{ - { - name: "Single delta", - deltas: []int64{52000}, - expectedTotal: 52000, - }, - { - name: "Multiple deltas accumulate", - deltas: []int64{52000, 52000, 52000}, - expectedTotal: 156000, - }, - { - name: "Mixed delta values", - deltas: []int64{52000, 28000, 80000}, - expectedTotal: 160000, - }, - { - name: "No deltas - zero total", - deltas: []int64{}, - expectedTotal: 0, - }, - { - name: "Reset clears accumulation", - deltas: []int64{52000, 52000, 52000, 52000}, - resetMidway: true, - expectedAfterReset: 104000, // Only last 2 deltas - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create a minimal HostContext (only need the atomic counter) - hc := &HostContext{} - - // Reset to ensure clean state - hc.ResetSstoreGasAdjustment() - require.Equal(t, int64(0), hc.GetSstoreGasAdjustment(), "Should start at 0") - - if tt.resetMidway { - // Add half the deltas - half := len(tt.deltas) / 2 - for i := 0; i < half; i++ { - hc.sstoreGasAdjustment.Add(tt.deltas[i]) - } - - // Reset - hc.ResetSstoreGasAdjustment() - require.Equal(t, int64(0), hc.GetSstoreGasAdjustment(), "Should be 0 after reset") - - // Add remaining deltas - for i := half; i < len(tt.deltas); i++ { - hc.sstoreGasAdjustment.Add(tt.deltas[i]) - } - - require.Equal(t, tt.expectedAfterReset, hc.GetSstoreGasAdjustment()) - } else { - // Add all deltas - for _, delta := range tt.deltas { - hc.sstoreGasAdjustment.Add(delta) - } - - require.Equal(t, tt.expectedTotal, hc.GetSstoreGasAdjustment()) - } - }) - } -} - -// TestSstoreGasAdjustmentWithStorageStatus tests that only StorageAdded triggers adjustment -func TestSstoreGasAdjustmentWithStorageStatus(t *testing.T) { - tests := []struct { - name string - status evmc.StorageStatus - shouldAdjust bool - }{ - { - name: "StorageAdded triggers adjustment", - status: evmc.StorageAdded, - shouldAdjust: true, - }, - { - name: "StorageModified does NOT trigger adjustment", - status: evmc.StorageModified, - shouldAdjust: false, - }, - { - name: "StorageDeleted does NOT trigger adjustment", - status: evmc.StorageDeleted, - shouldAdjust: false, - }, - { - name: "StorageAssigned does NOT trigger adjustment", - status: evmc.StorageAssigned, - shouldAdjust: false, - }, - { - name: "StorageDeletedAdded does NOT trigger adjustment", - status: evmc.StorageDeletedAdded, - shouldAdjust: false, - }, - { - name: "StorageModifiedDeleted does NOT trigger adjustment", - status: evmc.StorageModifiedDeleted, - shouldAdjust: false, - }, - { - name: "StorageDeletedRestored does NOT trigger adjustment", - status: evmc.StorageDeletedRestored, - shouldAdjust: false, - }, - { - name: "StorageAddedDeleted does NOT trigger adjustment", - status: evmc.StorageAddedDeleted, - shouldAdjust: false, - }, - { - name: "StorageModifiedRestored does NOT trigger adjustment", - status: evmc.StorageModifiedRestored, - shouldAdjust: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Test the condition used in SetStorage - shouldAdjust := tt.status == evmc.StorageAdded - - require.Equal(t, tt.shouldAdjust, shouldAdjust, - "StorageStatus %v adjustment check", tt.status) - }) - } -} - -// TestStandardSstoreSetGasConstant verifies the constant matches EIP-2200 -func TestStandardSstoreSetGasConstant(t *testing.T) { - // EIP-2200 defines SstoreSetGas as 20000 - require.Equal(t, uint64(20000), StandardSstoreSetGasEIP2200, - "StandardSstoreSetGasEIP2200 should be 20000 per EIP-2200") -} - -// TestSstoreGasAdjustmentConcurrency tests thread-safety of gas adjustment -func TestSstoreGasAdjustmentConcurrency(t *testing.T) { - hc := &HostContext{} - hc.ResetSstoreGasAdjustment() - - const numGoroutines = 100 - const deltasPerGoroutine = 100 - const deltaValue = int64(52000) - - done := make(chan bool, numGoroutines) - - // Spawn goroutines that concurrently add to the adjustment - for i := 0; i < numGoroutines; i++ { - go func() { - for j := 0; j < deltasPerGoroutine; j++ { - hc.sstoreGasAdjustment.Add(deltaValue) - } - done <- true - }() - } - - // Wait for all goroutines - for i := 0; i < numGoroutines; i++ { - <-done - } - - expected := int64(numGoroutines * deltasPerGoroutine * deltaValue) - require.Equal(t, expected, hc.GetSstoreGasAdjustment(), - "Concurrent additions should accumulate correctly") -} diff --git a/giga/executor/internal/interpreter.go b/giga/executor/internal/interpreter.go deleted file mode 100644 index 849db20584..0000000000 --- a/giga/executor/internal/interpreter.go +++ /dev/null @@ -1,106 +0,0 @@ -package internal - -import ( - "github.com/ethereum/evmc/v12/bindings/go/evmc" - "github.com/ethereum/go-ethereum/core/vm" -) - -var _ vm.IEVMInterpreter = (*EVMInterpreter)(nil) - -// EVMInterpreter is a custom interpreter that delegates execution to evmone via EVMC. -type EVMInterpreter struct { - hostContext *HostContext - evm *vm.EVM - readOnly bool -} - -func NewEVMInterpreter(hostContext *HostContext, evm *vm.EVM) *EVMInterpreter { - return &EVMInterpreter{hostContext: hostContext, evm: evm} -} - -// Run executes the contract code via evmone. -func (e *EVMInterpreter) Run(callOpCode vm.OpCode, contract *vm.Contract, input []byte, readOnly bool) ([]byte, error) { - // Increment the call depth which is restricted to 1024 - e.evm.Depth++ - defer func() { e.evm.Depth-- }() - depth := e.evm.Depth - - // For CREATE/CREATE2, the initcode is in contract.Code, not in input. - // For regular calls, input contains the call data. - codeToExecute := input - if callOpCode == vm.CREATE || callOpCode == vm.CREATE2 { - codeToExecute = contract.Code - } - - // Make sure the readOnly is only set if we aren't in readOnly yet. - // This also makes sure that the readOnly flag isn't removed for child calls. - if readOnly && !e.readOnly { - e.readOnly = true - defer func() { e.readOnly = false }() - } - - var static bool - if callOpCode == vm.STATICCALL { - static = true - } - - var callKind evmc.CallKind - switch callOpCode { - case vm.STATICCALL: - fallthrough - case vm.CALL: - callKind = evmc.Call - case vm.DELEGATECALL: - callKind = evmc.DelegateCall - case vm.CREATE2: - callKind = evmc.Create2 - case vm.CREATE: - callKind = evmc.Create - case vm.CALLCODE: - callKind = evmc.CallCode - default: - panic("unsupported call type") - } - - // todo(pdrobnjak): sender and recipient might not be correctly propagated in case of DELEGATECALL - sender := evmc.Address(contract.Caller()) - recipient := evmc.Address(contract.Address()) - - // Reset SSTORE gas adjustment before execution - e.hostContext.ResetSstoreGasAdjustment() - - //nolint:gosec // gosec: safe gas conversion - output, gasLeft, gasRefund, _, err := e.hostContext.Execute(callKind, recipient, sender, contract.Value().Bytes32(), codeToExecute, - int64(contract.Gas), depth, static) - if err != nil { - return nil, err - } - - // Apply SSTORE gas adjustment for Sei's custom SSTORE cost. - // evmone uses standard EIP-2200 gas (20k), but Sei may have a different cost. - // The adjustment is tracked during SetStorage calls and applied here. - // Adjustment can be positive (charge more) or negative (refund/reduce). - sstoreAdjustment := e.hostContext.GetSstoreGasAdjustment() - if sstoreAdjustment != 0 { - gasLeft -= sstoreAdjustment - // If gas goes negative, execution would have failed with out of gas - if gasLeft < 0 { - return nil, vm.ErrOutOfGas - } - } - - // Update the contract's gas to reflect what evmone consumed - // This is critical for proper gas accounting! - //nolint:gosec // safe conversion - gasLeft is always <= contract.Gas - contract.Gas = uint64(gasLeft) - - // Apply gas refund to the EVM's refund counter - //nolint:gosec // safe conversion - e.evm.StateDB.AddRefund(uint64(gasRefund)) - - return output, nil -} - -func (e *EVMInterpreter) ReadOnly() bool { - return e.readOnly -} diff --git a/giga/executor/internal/interpreter_test.go b/giga/executor/internal/interpreter_test.go deleted file mode 100644 index a1d904a4f1..0000000000 --- a/giga/executor/internal/interpreter_test.go +++ /dev/null @@ -1,521 +0,0 @@ -package internal - -import ( - "testing" - - "github.com/ethereum/evmc/v12/bindings/go/evmc" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/stretchr/testify/require" -) - -// TestCallKindMapping tests that opcode to evmc call kind mapping is correct -func TestCallKindMapping(t *testing.T) { - tests := []struct { - name string - opCode vm.OpCode - expectedKind evmc.CallKind - expectedStatic bool - }{ - { - name: "CALL", - opCode: vm.CALL, - expectedKind: evmc.Call, - expectedStatic: false, - }, - { - name: "STATICCALL", - opCode: vm.STATICCALL, - expectedKind: evmc.Call, - expectedStatic: true, - }, - { - name: "DELEGATECALL", - opCode: vm.DELEGATECALL, - expectedKind: evmc.DelegateCall, - expectedStatic: false, - }, - { - name: "CALLCODE", - opCode: vm.CALLCODE, - expectedKind: evmc.CallCode, - expectedStatic: false, - }, - { - name: "CREATE", - opCode: vm.CREATE, - expectedKind: evmc.Create, - expectedStatic: false, - }, - { - name: "CREATE2", - opCode: vm.CREATE2, - expectedKind: evmc.Create2, - expectedStatic: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Replicate the mapping logic from Run() - var callKind evmc.CallKind - var static bool - - if tt.opCode == vm.STATICCALL { - static = true - } - - switch tt.opCode { - case vm.STATICCALL: - fallthrough - case vm.CALL: - callKind = evmc.Call - case vm.DELEGATECALL: - callKind = evmc.DelegateCall - case vm.CREATE2: - callKind = evmc.Create2 - case vm.CREATE: - callKind = evmc.Create - case vm.CALLCODE: - callKind = evmc.CallCode - default: - t.Fatalf("Unsupported opcode: %v", tt.opCode) - } - - require.Equal(t, tt.expectedKind, callKind) - require.Equal(t, tt.expectedStatic, static) - }) - } -} - -// TestCodeToExecuteForCreate tests that CREATE/CREATE2 use contract.Code -func TestCodeToExecuteForCreate(t *testing.T) { - tests := []struct { - name string - opCode vm.OpCode - contractCode []byte - input []byte - expectCode []byte // What code should be used for execution - }{ - { - name: "CREATE uses contract.Code", - opCode: vm.CREATE, - contractCode: []byte{0x60, 0x00, 0x60, 0x00, 0xf3}, // initcode - input: []byte{}, // empty for CREATE - expectCode: []byte{0x60, 0x00, 0x60, 0x00, 0xf3}, - }, - { - name: "CREATE2 uses contract.Code", - opCode: vm.CREATE2, - contractCode: []byte{0x60, 0x01, 0x60, 0x01, 0xf3}, // initcode - input: []byte{}, // empty for CREATE2 - expectCode: []byte{0x60, 0x01, 0x60, 0x01, 0xf3}, - }, - { - name: "CALL uses input (call data)", - opCode: vm.CALL, - contractCode: []byte{0xfe}, // not used - input: []byte{0x12, 0x34, 0x56, 0x78}, // call data - expectCode: []byte{0x12, 0x34, 0x56, 0x78}, - }, - { - name: "STATICCALL uses input (call data)", - opCode: vm.STATICCALL, - contractCode: []byte{0xfe}, - input: []byte{0xab, 0xcd}, - expectCode: []byte{0xab, 0xcd}, - }, - { - name: "DELEGATECALL uses input (call data)", - opCode: vm.DELEGATECALL, - contractCode: []byte{0xfe}, - input: []byte{0x11, 0x22, 0x33}, - expectCode: []byte{0x11, 0x22, 0x33}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Replicate the code selection logic from Run() - codeToExecute := tt.input - if tt.opCode == vm.CREATE || tt.opCode == vm.CREATE2 { - codeToExecute = tt.contractCode - } - require.Equal(t, tt.expectCode, codeToExecute) - }) - } -} - -// TestStaticCallFlag tests that STATICCALL sets the static flag -func TestStaticCallFlag(t *testing.T) { - tests := []struct { - opCode vm.OpCode - expected bool - }{ - {vm.CALL, false}, - {vm.STATICCALL, true}, - {vm.DELEGATECALL, false}, - {vm.CALLCODE, false}, - {vm.CREATE, false}, - {vm.CREATE2, false}, - } - - for _, tt := range tests { - t.Run(tt.opCode.String(), func(t *testing.T) { - static := tt.opCode == vm.STATICCALL - require.Equal(t, tt.expected, static) - }) - } -} - -// TestGasAccountingLogic tests the gas accounting math -func TestGasAccountingLogic(t *testing.T) { - tests := []struct { - name string - initialGas uint64 - gasLeft int64 - gasRefund int64 - expectedGas uint64 - expectedRefund uint64 - }{ - { - name: "Full execution with leftover", - initialGas: 1000000, - gasLeft: 900000, - gasRefund: 1000, - expectedGas: 900000, - expectedRefund: 1000, - }, - { - name: "No refund", - initialGas: 1000000, - gasLeft: 500000, - gasRefund: 0, - expectedGas: 500000, - expectedRefund: 0, - }, - { - name: "All gas used", - initialGas: 21000, - gasLeft: 0, - gasRefund: 0, - expectedGas: 0, - expectedRefund: 0, - }, - { - name: "Minimal gas left", - initialGas: 100000, - gasLeft: 1, - gasRefund: 50000, - expectedGas: 1, - expectedRefund: 50000, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Simulate the gas accounting logic from Run() - // contract.Gas = uint64(gasLeft) - contractGas := uint64(tt.gasLeft) - - // e.evm.StateDB.AddRefund(uint64(gasRefund)) - refundAccumulator := uint64(0) - refundAccumulator += uint64(tt.gasRefund) - - require.Equal(t, tt.expectedGas, contractGas) - require.Equal(t, tt.expectedRefund, refundAccumulator) - }) - } -} - -// TestReadOnlyPropagation tests that readOnly flag is properly managed -func TestReadOnlyPropagation(t *testing.T) { - tests := []struct { - name string - initialReadOnly bool - callReadOnly bool - expectReadOnly bool - }{ - { - name: "Not readOnly, call not readOnly", - initialReadOnly: false, - callReadOnly: false, - expectReadOnly: false, - }, - { - name: "Not readOnly, call is readOnly", - initialReadOnly: false, - callReadOnly: true, - expectReadOnly: true, - }, - { - name: "Already readOnly, call not readOnly", - initialReadOnly: true, - callReadOnly: false, - expectReadOnly: true, // stays readOnly - }, - { - name: "Already readOnly, call is readOnly", - initialReadOnly: true, - callReadOnly: true, - expectReadOnly: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Replicate the readOnly logic from Run() - readOnly := tt.initialReadOnly - - // if readOnly && !e.readOnly { - // e.readOnly = true - // } - if tt.callReadOnly && !readOnly { - readOnly = true - } - - require.Equal(t, tt.expectReadOnly, readOnly) - }) - } -} - -// TestDepthIncrement tests that depth is incremented and decremented -func TestDepthIncrement(t *testing.T) { - depth := 0 - - // Simulate entering a call - depth++ - require.Equal(t, 1, depth) - - // Simulate nested call - depth++ - require.Equal(t, 2, depth) - - // Simulate returning from nested call - depth-- - require.Equal(t, 1, depth) - - // Simulate returning from outer call - depth-- - require.Equal(t, 0, depth) -} - -// TestMaxDepth tests the depth limit constant -func TestMaxDepth(t *testing.T) { - // The EVM has a call depth limit of 1024 - const maxCallDepth = 1024 - - depth := 0 - for i := 0; i < maxCallDepth; i++ { - depth++ - } - require.Equal(t, maxCallDepth, depth) - - // Attempting to go deeper would fail in the real EVM -} - -// TestDelegatedCallDetection tests that DELEGATECALL and CALLCODE are detected -func TestDelegatedCallDetection(t *testing.T) { - tests := []struct { - kind evmc.CallKind - delegated bool - }{ - {evmc.Call, false}, - {evmc.DelegateCall, true}, - {evmc.CallCode, true}, - {evmc.Create, false}, - {evmc.Create2, false}, - } - - for i, tt := range tests { - t.Run(callKindName(tt.kind), func(t *testing.T) { - // Replicate the delegated detection from Execute - delegated := tt.kind == evmc.DelegateCall || tt.kind == evmc.CallCode - require.Equal(t, tt.delegated, delegated, "test case %d", i) - }) - } -} - -// callKindName returns a string name for an evmc.CallKind -func callKindName(kind evmc.CallKind) string { - switch kind { - case evmc.Call: - return "Call" - case evmc.DelegateCall: - return "DelegateCall" - case evmc.CallCode: - return "CallCode" - case evmc.Create: - return "Create" - case evmc.Create2: - return "Create2" - default: - return "Unknown" - } -} - -// TestOpCodeToString tests that opcodes have string representations -func TestOpCodeToString(t *testing.T) { - opcodes := []vm.OpCode{ - vm.CALL, - vm.STATICCALL, - vm.DELEGATECALL, - vm.CALLCODE, - vm.CREATE, - vm.CREATE2, - } - - for _, op := range opcodes { - str := op.String() - require.NotEmpty(t, str, "OpCode should have string representation") - } -} - -// TestEvmcCallKindValues tests that call kinds have distinct values -func TestEvmcCallKindValues(t *testing.T) { - kinds := []evmc.CallKind{ - evmc.Call, - evmc.DelegateCall, - evmc.CallCode, - evmc.Create, - evmc.Create2, - } - - // Verify all kinds are distinct - seen := make(map[evmc.CallKind]bool) - for _, kind := range kinds { - require.False(t, seen[kind], "Duplicate call kind value") - seen[kind] = true - // Use our helper to get a name - name := callKindName(kind) - require.NotEmpty(t, name, "CallKind should have a name") - require.NotEqual(t, "Unknown", name, "CallKind should be recognized") - } -} - -// TestSstoreGasAdjustmentApplication tests the gas adjustment logic in Run() -func TestSstoreGasAdjustmentApplication(t *testing.T) { - tests := []struct { - name string - gasLeftFromEvmone int64 - sstoreAdjustment int64 - expectedGasLeft int64 - expectOutOfGas bool - }{ - { - name: "No adjustment needed", - gasLeftFromEvmone: 100000, - sstoreAdjustment: 0, - expectedGasLeft: 100000, - expectOutOfGas: false, - }, - { - name: "Single SSTORE adjustment (52k delta)", - gasLeftFromEvmone: 100000, - sstoreAdjustment: 52000, - expectedGasLeft: 48000, - expectOutOfGas: false, - }, - { - name: "Multiple SSTORE adjustments", - gasLeftFromEvmone: 200000, - sstoreAdjustment: 156000, // 3 x 52000 - expectedGasLeft: 44000, - expectOutOfGas: false, - }, - { - name: "Adjustment exactly equals gas left", - gasLeftFromEvmone: 52000, - sstoreAdjustment: 52000, - expectedGasLeft: 0, - expectOutOfGas: false, - }, - { - name: "Adjustment exceeds gas left - out of gas", - gasLeftFromEvmone: 50000, - sstoreAdjustment: 52000, - expectedGasLeft: -2000, // Would be negative - expectOutOfGas: true, - }, - { - name: "Large adjustment exceeds gas left", - gasLeftFromEvmone: 100000, - sstoreAdjustment: 156000, // 3 SSTOREs worth - expectedGasLeft: -56000, // Would be negative - expectOutOfGas: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Replicate the logic from Run() - gasLeft := tt.gasLeftFromEvmone - - if tt.sstoreAdjustment > 0 { - gasLeft -= tt.sstoreAdjustment - if gasLeft < 0 { - require.True(t, tt.expectOutOfGas, "Should have detected out of gas") - return - } - } - - require.False(t, tt.expectOutOfGas, "Should not have out of gas error") - require.Equal(t, tt.expectedGasLeft, gasLeft) - }) - } -} - -// TestSstoreGasAdjustmentBoundaryConditions tests edge cases -func TestSstoreGasAdjustmentBoundaryConditions(t *testing.T) { - tests := []struct { - name string - gasLeft int64 - adjustment int64 - expectedResult int64 - expectError bool - }{ - { - name: "Zero gas left, zero adjustment", - gasLeft: 0, - adjustment: 0, - expectedResult: 0, - expectError: false, - }, - { - name: "Zero gas left, positive adjustment", - gasLeft: 0, - adjustment: 1, - expectedResult: -1, - expectError: true, - }, - { - name: "Minimum positive adjustment", - gasLeft: 1, - adjustment: 1, - expectedResult: 0, - expectError: false, - }, - { - name: "Large gas values", - gasLeft: 1000000000, // 1B gas - adjustment: 500000000, // 500M adjustment - expectedResult: 500000000, - expectError: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := tt.gasLeft - if tt.adjustment > 0 { - result -= tt.adjustment - } - - if tt.expectError { - require.True(t, result < 0, "Should result in negative gas") - } else { - require.Equal(t, tt.expectedResult, result) - require.True(t, result >= 0, "Result should be non-negative") - } - }) - } -} diff --git a/giga/executor/lib/evmlib.go b/giga/executor/lib/evmlib.go deleted file mode 100644 index 28f91aeb5f..0000000000 --- a/giga/executor/lib/evmlib.go +++ /dev/null @@ -1,105 +0,0 @@ -package lib - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - - "github.com/ethereum/evmc/v12/bindings/go/evmc" -) - -//go:generate go run ./gen/main.go . - -// libDirEnv lets operators point at the directory that holds the trusted, -// integrity-verified evmone shared library. It must be an absolute path to a -// root-owned, non-writable directory. -const libDirEnv = "SEI_EVMONE_LIB_DIR" - -// installLibDir is the canonical location the evmone library is installed to -// in release images (see Dockerfile, which copies it alongside the other -// native libraries that are loaded from /usr/lib). -const installLibDir = "/usr/lib" - -// InitEvmoneVM initializes the EVMC VM by loading the platform-specific evmone -// library from a trusted, absolute path. -// -// Resolution order (first existing file wins): -// 1. $SEI_EVMONE_LIB_DIR (operator override) -// 2. /usr/lib (release install location) -// 3. the source-tree directory (local development and tests) -// -// The file's SHA-256 digest is verified against the digest pinned for this -// platform before it is handed to the dynamic linker. Passing an absolute -// path to evmc.Load avoids the dynamic linker's search path -// (LD_LIBRARY_PATH, ld.so.cache, default dirs), so the library cannot be -// substituted by planting a file earlier in the loader's search order. -// -// It does not verify that the loaded version is compatible with evmc version. -func InitEvmoneVM() (*evmc.VM, error) { - libPath, err := resolveLibPath() - if err != nil { - return nil, err - } - - if err := verifyLibDigest(libPath); err != nil { - return nil, err - } - - vm, err := evmc.Load(libPath) - if err != nil { - return nil, fmt.Errorf("evmc.Load(%q): %w", libPath, err) - } - - return vm, nil -} - -// resolveLibPath returns the absolute path of the platform library, choosing -// the first candidate directory that actually contains it. -func resolveLibPath() (string, error) { - dirs := make([]string, 0, 3) - if dir := os.Getenv(libDirEnv); dir != "" { - if !filepath.IsAbs(dir) { - return "", fmt.Errorf("%s must be an absolute path, got %q", libDirEnv, dir) - } - dirs = append(dirs, dir) - } - dirs = append(dirs, installLibDir) - if _, srcFile, _, ok := runtime.Caller(0); ok { - dirs = append(dirs, filepath.Dir(srcFile)) - } - - for _, dir := range dirs { - candidate := filepath.Join(dir, libName) - if info, err := os.Stat(candidate); err == nil && !info.IsDir() { - return candidate, nil - } - } - - return "", fmt.Errorf("evmone library %q not found in any of %v", libName, dirs) -} - -// verifyLibDigest computes the SHA-256 of the file at path and compares it -// against the digest pinned for this platform, mirroring the integrity check -// the generator performs when downloading the library (see gen/main.go). -func verifyLibDigest(path string) error { - f, err := os.Open(filepath.Clean(path)) //nolint:gosec // path resolved from trusted, fixed locations - if err != nil { - return fmt.Errorf("open evmone library %q: %w", path, err) - } - defer func() { _ = f.Close() }() - - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - return fmt.Errorf("hash evmone library %q: %w", path, err) - } - - if actual := hex.EncodeToString(h.Sum(nil)); actual != libSHA256 { - return fmt.Errorf("evmone library %q digest mismatch: expected %s, got %s", path, libSHA256, actual) - } - - return nil -} diff --git a/giga/executor/lib/evmlib_test.go b/giga/executor/lib/evmlib_test.go deleted file mode 100644 index ca8d6037dd..0000000000 --- a/giga/executor/lib/evmlib_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package lib - -import ( - "crypto/sha256" - "encoding/hex" - "io" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestLibSHA256MatchesCheckedInLibrary(t *testing.T) { - _, srcFile, _, ok := runtime.Caller(0) - require.True(t, ok, "failed to determine source path") - - libPath := filepath.Join(filepath.Dir(srcFile), libName) - f, err := os.Open(libPath) //nolint:gosec // test reads a fixed, in-tree path - require.NoError(t, err, "open %q: %v", libPath, err) - defer func() { require.NoError(t, f.Close()) }() - - h := sha256.New() - _, err = io.Copy(h, f) - require.NoError(t, err, "hash %q: %v", libPath, err) - - got := hex.EncodeToString(h.Sum(nil)) - require.Equal(t, libSHA256, got, "checked-in %s digest = %s, want %s", libName, got, libSHA256) -} - -func TestResolveLibPathRejectsRelativeOverrideDir(t *testing.T) { - t.Setenv(libDirEnv, ".") - - _, err := resolveLibPath() - require.ErrorContains(t, err, libDirEnv+" must be an absolute path") -} - -func TestResolveLibPathUsesAbsoluteOverrideDir(t *testing.T) { - dir := t.TempDir() - libPath := filepath.Join(dir, libName) - require.NoError(t, os.WriteFile(libPath, []byte("test evmone library"), 0o600)) - t.Setenv(libDirEnv, dir) - - got, err := resolveLibPath() - require.NoError(t, err) - require.Equal(t, libPath, got) - require.True(t, filepath.IsAbs(got)) -} diff --git a/giga/executor/lib/gen/main.go b/giga/executor/lib/gen/main.go deleted file mode 100644 index db86b4dddc..0000000000 --- a/giga/executor/lib/gen/main.go +++ /dev/null @@ -1,202 +0,0 @@ -package main - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "log" - "net/http" - "os" - "os/signal" - "path/filepath" - "syscall" -) - -const evmoneVersion = "0.12.0" - -type Platform struct { - Archive string - Hash string // SHA256 hash of the archive - LibHash string // SHA256 hash of the extracted library file - OS string - Arch string - LibPath string - Ext string -} - -var platforms = []Platform{ - { - Archive: "evmone-0.12.0-linux-x86_64.tar.gz", - Hash: "1c7b5eba0c8c3b3b2a7a05101e2d01a13a2f84b323989a29be66285dba4136ce", - LibHash: "0fec5d79f4c9a466bb680e8b0b9c770aea38f3dd6d2e4af23535c893d0d18d40", - OS: "linux", - Arch: "amd64", - LibPath: "lib/libevmone.so.0.12.0", - Ext: "so", - }, - { - Archive: "evmone-0.12.0-darwin-arm64.tar.gz", - Hash: "e164e0d2b985cc1cca07b501538b2e804bf872d1d8d531f9241d518a886234a6", - LibHash: "cb1c555b3849a0a6a9402bc907a9a7bfe14e1c08c483c488ec6ba5f19e986847", - OS: "darwin", - Arch: "arm64", - LibPath: "lib/libevmone.0.12.0.dylib", - Ext: "dylib", - }, -} - -// This program downloads evmone shared libraries for supported platforms. -// -// To upgrade evmone: -// 1. Visit https://github.com/ethereum/evmone/releases -// 2. Update evmoneVersion constant below -// 3. Update the Archive filenames and SHA256 hashes in the platforms slice -// 4. Update the LibHash values for extracted library files -// 5. Run: go run download_evmone.go -func main() { - if len(os.Args) != 2 { - log.Fatalf("Usage: %s \n", os.Args[0]) - } - outDir := filepath.Clean(os.Args[1]) - - // Create context that cancels on SIGINT or SIGTERM - ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer cancel() - - for _, p := range platforms { - if err := downloadAndExtract(ctx, p, outDir); err != nil { - if ctx.Err() != nil { - log.Fatal("Interrupted") - } - log.Fatalf("Failed %s-%s: %v\n", p.OS, p.Arch, err) - } - } - log.Println("All platforms downloaded successfully!") -} - -// libFileName returns the output filename for a platform's library -func (p Platform) libFileName() string { - return fmt.Sprintf("libevmone.%s_%s_%s.%s", evmoneVersion, p.OS, p.Arch, p.Ext) -} - -// checkExistingLib checks if the library file already exists with the correct hash. -// Returns true if the file exists and has the correct hash, false otherwise. -func checkExistingLib(p Platform, outDir string) (bool, error) { - outPath := filepath.Clean(filepath.Join(outDir, p.libFileName())) - - f, err := os.Open(outPath) - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, fmt.Errorf("open existing file: %w", err) - } - defer func() { _ = f.Close() }() - - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - return false, fmt.Errorf("hash existing file: %w", err) - } - - actual := hex.EncodeToString(h.Sum(nil)) - return actual == p.LibHash, nil -} - -func downloadAndExtract(ctx context.Context, p Platform, outDir string) error { - outName := p.libFileName() - outPath := filepath.Join(outDir, outName) - - // Check if file already exists with correct hash - exists, err := checkExistingLib(p, outDir) - if err != nil { - return fmt.Errorf("check existing: %w", err) - } - if exists { - log.Printf("Skipping %s (already exists with correct hash)\n", outName) - return nil - } - - url := fmt.Sprintf("https://github.com/ethereum/evmone/releases/download/v%s/%s", evmoneVersion, p.Archive) - log.Printf("Downloading %s...\n", p.Archive) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return fmt.Errorf("create request: %w", err) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("download: %w", err) - } - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("download: %s", resp.Status) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("read: %w", err) - } - - // Check for cancellation after download - if ctx.Err() != nil { - return ctx.Err() - } - - // Verify SHA-256 hash of the downloaded archive - sum := sha256.Sum256(body) - actual := hex.EncodeToString(sum[:]) - if actual != p.Hash { - return fmt.Errorf("hash mismatch: expected %s, got %s", p.Hash, actual) - } - log.Printf(" Archive hash verified: %s-%s\n", p.OS, p.Arch) - - gzr, err := gzip.NewReader(bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("gzip: %w", err) - } - defer func() { _ = gzr.Close() }() - - tr := tar.NewReader(gzr) - for ctx.Err() == nil { - header, err := tr.Next() - if err == io.EOF { - return fmt.Errorf("library not found in archive (looking for %s)", p.LibPath) - } - if err != nil { - return fmt.Errorf("tar: %w", err) - } - if header.Typeflag == tar.TypeReg { - fmt.Println(header.Name) - } - - if header.Typeflag != tar.TypeReg || header.Name != p.LibPath { - continue - } - - f, err := os.OpenFile(outPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) //nolint:gosec - if err != nil { - return fmt.Errorf("create: %w", err) - } - - const maxSize = 100 << 20 // 100MiB maximum copy - if _, err := io.CopyN(f, tr, maxSize); err != nil && err != io.EOF { - _ = f.Close() - return fmt.Errorf("extract: %w", err) - } - - if err := f.Close(); err != nil { - return fmt.Errorf("close: %w", err) - } - - log.Printf(" Extracted: %s\n", outPath) - return nil - } - return ctx.Err() -} diff --git a/giga/executor/lib/lib_darwin_arm64.go b/giga/executor/lib/lib_darwin_arm64.go deleted file mode 100644 index c76764beda..0000000000 --- a/giga/executor/lib/lib_darwin_arm64.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build darwin && arm64 - -package lib - -const libName = "libevmone.0.12.0_darwin_arm64.dylib" - -const libSHA256 = "cb1c555b3849a0a6a9402bc907a9a7bfe14e1c08c483c488ec6ba5f19e986847" diff --git a/giga/executor/lib/lib_linux_amd64.go b/giga/executor/lib/lib_linux_amd64.go deleted file mode 100644 index dd48786f66..0000000000 --- a/giga/executor/lib/lib_linux_amd64.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build linux && amd64 - -package lib - -const libName = "libevmone.0.12.0_linux_amd64.so" - -const libSHA256 = "0fec5d79f4c9a466bb680e8b0b9c770aea38f3dd6d2e4af23535c893d0d18d40" diff --git a/giga/executor/lib/lib_linux_arm64.go b/giga/executor/lib/lib_linux_arm64.go deleted file mode 100644 index 96c17b38fc..0000000000 --- a/giga/executor/lib/lib_linux_arm64.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build linux && arm64 - -package lib - -const libName = "libevmone.0.12.0_linux_arm64.so" - -const libSHA256 = "27bf7d3632eb51c3c87234fa29ac70c471af2357898420ddef0da2c5549bac36" diff --git a/giga/executor/lib/lib_unsupported.go b/giga/executor/lib/lib_unsupported.go deleted file mode 100644 index 05d5f27591..0000000000 --- a/giga/executor/lib/lib_unsupported.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !((linux && amd64) || (linux && arm64) || (darwin && arm64)) - -package lib - -// evmone is not available for this platform. -// Supported platforms: linux/amd64, linux/arm64, darwin/arm64 -// -// If you see a compile error referencing this file, you are building -// for an unsupported OS/architecture combination. -const libName = evmone_unsupported_platform__only_linux_amd64_linux_arm64_and_darwin_arm64_are_supported - -const libSHA256 = "" diff --git a/giga/executor/lib/libevmone.0.12.0_darwin_arm64.dylib b/giga/executor/lib/libevmone.0.12.0_darwin_arm64.dylib deleted file mode 100644 index a3b462f5be..0000000000 Binary files a/giga/executor/lib/libevmone.0.12.0_darwin_arm64.dylib and /dev/null differ diff --git a/giga/executor/lib/libevmone.0.12.0_linux_amd64.so b/giga/executor/lib/libevmone.0.12.0_linux_amd64.so deleted file mode 100644 index 9c6f75feb0..0000000000 Binary files a/giga/executor/lib/libevmone.0.12.0_linux_amd64.so and /dev/null differ diff --git a/giga/executor/lib/libevmone.0.12.0_linux_arm64.so b/giga/executor/lib/libevmone.0.12.0_linux_arm64.so deleted file mode 100755 index 6a57009a5b..0000000000 Binary files a/giga/executor/lib/libevmone.0.12.0_linux_arm64.so and /dev/null differ diff --git a/giga/tests/giga_test.go b/giga/tests/giga_test.go index 8d2e80f47e..c15def26ea 100644 --- a/giga/tests/giga_test.go +++ b/giga/tests/giga_test.go @@ -1171,14 +1171,14 @@ func TestAllModes_ContractExecution(t *testing.T) { t.Logf("Contract deployment and calls produced identical results across all three executor modes") } -// TestGigaVsGeth_GasComparison compares gas usage between Geth and Giga executors. +// TestGigaVsGeth_GasComparison compares gas usage between the V2 (Geth) and Giga executors. // -// Both Geth and Giga use Sei's configurable SSTORE gas cost (SeiSstoreSetGasEIP2200). -// The GIGA path applies a gas adjustment after evmone execution to match Sei's custom SSTORE cost. +// Both paths run the same go-ethereum interpreter against the same chain config, so both +// apply Sei's configurable SSTORE gas cost (SeiSstoreSetGasEIP2200) identically. // // This test verifies: // 1. Deploy gas is exactly the same (no SSTORE involved) -// 2. Call gas is exactly the same (SSTORE gas adjustment applied in GIGA) +// 2. Call gas is exactly the same (both paths use the identical SSTORE gas cost) func TestGigaVsGeth_GasComparison(t *testing.T) { blockTime := time.Now() accts := utils.NewTestAccounts(5) @@ -1215,7 +1215,7 @@ func TestGigaVsGeth_GasComparison(t *testing.T) { deployDiff := int64(gethResults[0].GasUsed) - int64(gigaResults[0].GasUsed) callDiff := int64(gethResults[1].GasUsed) - int64(gigaResults[1].GasUsed) - t.Logf("Gas Comparison Report (Geth vs Giga/evmone):") + t.Logf("Gas Comparison Report (Geth vs Giga):") t.Logf(" Contract Deploy: Geth=%d, Giga=%d, Diff=%d", gethResults[0].GasUsed, gigaResults[0].GasUsed, deployDiff) t.Logf(" Contract Call: Geth=%d, Giga=%d, Diff=%d", @@ -1225,9 +1225,9 @@ func TestGigaVsGeth_GasComparison(t *testing.T) { require.Equal(t, int64(0), deployDiff, "Deploy gas should be identical between Geth and Giga (no SSTORE)") - // Call gas should now be IDENTICAL since GIGA applies the Sei custom SSTORE gas adjustment + // Call gas should be IDENTICAL since both paths use the same Sei custom SSTORE gas cost require.Equal(t, int64(0), callDiff, - "Call gas should be identical between Geth and Giga (SSTORE gas adjustment applied)") + "Call gas should be identical between Geth and Giga (same SSTORE gas cost)") t.Logf("Gas comparison verified: Both deploy and call gas are identical") } @@ -1253,7 +1253,7 @@ func TestGiga_CREATE_CodePath(t *testing.T) { require.Len(t, results, 1) // The key assertion: deployment should succeed (code != 0) - // This verifies that the interpreter correctly passed initcode to evmone + // This verifies that the interpreter correctly passed initcode to the EVM require.Equal(t, uint32(0), results[0].Code, "Contract deployment should succeed") require.NotEmpty(t, results[0].Data, "Deployment should return created contract address") @@ -1336,7 +1336,7 @@ func TestGiga_STATICCALL_ReadOnly(t *testing.T) { t.Logf("STATICCALL/read path verified with Giga executor") } -// TestGiga_GasAccounting verifies gas is properly tracked after evmone execution +// TestGiga_GasAccounting verifies gas is properly tracked after Giga executor execution func TestGiga_GasAccounting(t *testing.T) { blockTime := time.Now() accts := utils.NewTestAccounts(3) @@ -1371,63 +1371,6 @@ func TestGiga_GasAccounting(t *testing.T) { t.Logf("Gas accounting verified: Call used %d gas", callResults[0].GasUsed) } -// TestGiga_SstoreGasDeltaCalculation verifies that the SSTORE gas delta is correctly calculated -// based on different Sei SSTORE gas parameter values. -// This is a unit test for the HostContext gas adjustment logic. -func TestGiga_SstoreGasDeltaCalculation(t *testing.T) { - // Test the delta calculation directly - // StandardSstoreSetGasEIP2200 = 20000 - - tests := []struct { - name string - seiSstoreGas uint64 - expectedDelta uint64 - }{ - { - name: "Standard (20k) - no adjustment needed", - seiSstoreGas: 20000, - expectedDelta: 0, - }, - { - name: "Higher value (72k) - 52k delta", - seiSstoreGas: 72000, - expectedDelta: 52000, - }, - { - name: "Higher (100k) - 80k delta", - seiSstoreGas: 100000, - expectedDelta: 80000, - }, - { - name: "Lower than standard (10k) - no adjustment", - seiSstoreGas: 10000, - expectedDelta: 0, // No negative adjustments - }, - { - name: "Zero - no adjustment", - seiSstoreGas: 0, - expectedDelta: 0, - }, - } - - const standardSstoreGas = uint64(20000) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Calculate delta the same way NewHostContext does - var delta uint64 - if tt.seiSstoreGas > standardSstoreGas { - delta = tt.seiSstoreGas - standardSstoreGas - } - - require.Equal(t, tt.expectedDelta, delta, - "Delta calculation for seiSstoreGas=%d", tt.seiSstoreGas) - }) - } - - t.Logf("SSTORE gas delta calculation verified for all test cases") -} - // TestGiga_SstoreGasHonoredByChainConfig verifies that the SSTORE gas parameter // is correctly read from the chain config and would be passed to the executor. // This tests the parameter flow, not full execution. diff --git a/go.mod b/go.mod index 736705c6c1..02f624c92f 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,6 @@ require ( github.com/creachadair/taskgroup v0.3.2 github.com/creachadair/tomledit v0.0.29 github.com/dvsekhvalnov/jose2go v1.7.0 - github.com/ethereum/evmc/v12 v12.1.0 github.com/ethereum/go-ethereum v1.16.8 github.com/fortytw2/leaktest v1.3.0 github.com/go-git/go-git/v5 v5.17.2 diff --git a/go.sum b/go.sum index 82d899b454..f2af37019d 100644 --- a/go.sum +++ b/go.sum @@ -973,8 +973,6 @@ github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0+ github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/evmc/v12 v12.1.0 h1:fUIzJNnXa9VPYx253lDS7L9iBZtP+tlpTdZst5e6Pks= -github.com/ethereum/evmc/v12 v12.1.0/go.mod h1:80jmft01io35nSmrX70bKFR/lncwFuqE90iLLSMyMAE= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0=