diff --git a/libevm/legacy/legacy.go b/libevm/legacy/legacy.go index f9ff73090f0a..c78505a823ae 100644 --- a/libevm/legacy/legacy.go +++ b/libevm/legacy/legacy.go @@ -1,4 +1,4 @@ -// Copyright 2024 the libevm authors. +// Copyright 2024-2025 the libevm authors. // // The libevm additions to go-ethereum are free software: you can redistribute // them and/or modify them under the terms of the GNU Lesser General Public License @@ -18,7 +18,16 @@ // equivalents. package legacy -import "github.com/ava-labs/libevm/core/vm" +import ( + "errors" + "fmt" + + "github.com/ava-labs/libevm/core/vm" +) + +var ( + errRemainingGasExceedsSuppliedGas = errors.New("remaining gas exceeds supplied gas") +) // PrecompiledStatefulContract is the legacy signature of // [vm.PrecompiledStatefulContract], which explicitly accepts and returns gas @@ -31,8 +40,11 @@ func (c PrecompiledStatefulContract) Upgrade() vm.PrecompiledStatefulContract { return func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) { gas := env.Gas() ret, remainingGas, err := c(env, input, gas) - if used := gas - remainingGas; used < gas { - env.UseGas(used) + if remainingGas > gas { + return ret, fmt.Errorf("%w: %d > %d", errRemainingGasExceedsSuppliedGas, remainingGas, gas) + } + if !env.UseGas(gas - remainingGas) { + return ret, vm.ErrOutOfGas } return ret, err } diff --git a/libevm/legacy/legacy_test.go b/libevm/legacy/legacy_test.go new file mode 100644 index 000000000000..afd3df03da04 --- /dev/null +++ b/libevm/legacy/legacy_test.go @@ -0,0 +1,111 @@ +// Copyright 2025 the libevm authors. +// +// The libevm additions to go-ethereum are free software: you can redistribute +// them and/or modify them under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The libevm additions are distributed in the hope that they will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see +// . + +package legacy + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/libevm/core/vm" +) + +// stubPrecompileEnvironment implements [vm.PrecompileEnvironment] for testing. +type stubPrecompileEnvironment struct { + vm.PrecompileEnvironment + gas uint64 +} + +func (s *stubPrecompileEnvironment) Gas() uint64 { + return s.gas +} + +func (s *stubPrecompileEnvironment) UseGas(gas uint64) (hasEnoughGas bool) { + if s.gas < gas { + return false + } + s.gas -= gas + return true +} + +func TestPrecompiledStatefulContract_Upgrade(t *testing.T) { + t.Parallel() + + errTest := errors.New("test error") + + tests := map[string]struct { + suppliedGas uint64 + precompileRet []byte + remainingGas uint64 + precompileErr error + wantErr error + wantGas uint64 + }{ + "call_error": { + suppliedGas: 10, + precompileRet: []byte{2}, + remainingGas: 6, + precompileErr: errTest, + wantErr: errTest, + wantGas: 6, + }, + "remaining_gas_exceeds_supplied_gas": { + suppliedGas: 10, + precompileRet: []byte{2}, + remainingGas: 11, + wantErr: errRemainingGasExceedsSuppliedGas, + wantGas: 10, + }, + "zero_remaining_gas": { + suppliedGas: 10, + precompileRet: []byte{2}, + remainingGas: 0, + wantGas: 0, + }, + "used_one_gas": { + suppliedGas: 10, + precompileRet: []byte{2}, + remainingGas: 9, + wantGas: 9, + }, + } + + for name, test := range tests { + testCase := test + t.Run(name, func(t *testing.T) { + t.Parallel() + + c := PrecompiledStatefulContract(func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + return testCase.precompileRet, testCase.remainingGas, testCase.precompileErr + }) + + upgraded := c.Upgrade() + + env := &stubPrecompileEnvironment{ + gas: testCase.suppliedGas, + } + input := []byte("unused") + + ret, err := upgraded(env, input) + require.ErrorIs(t, err, testCase.wantErr) + assert.Equal(t, testCase.precompileRet, ret, "bytes returned by upgraded contract") + assert.Equalf(t, testCase.wantGas, env.gas, "remaining gas in %T", env) + }) + } +}