Skip to content

Commit f906679

Browse files
authored
fix(libevm/legacy): PrecompiledStatefulContract gas and remaining gas handling (#114)
- remaining gas higher than the input gas is not allowed (otherwise it would overflow as well) - remaining gas can be equal to the input gas - add new test and `vm.PrecompileEnvironment` stub
1 parent a8df623 commit f906679

File tree

2 files changed

+127
-4
lines changed

2 files changed

+127
-4
lines changed

libevm/legacy/legacy.go

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 the libevm authors.
1+
// Copyright 2024-2025 the libevm authors.
22
//
33
// The libevm additions to go-ethereum are free software: you can redistribute
44
// them and/or modify them under the terms of the GNU Lesser General Public License
@@ -18,7 +18,16 @@
1818
// equivalents.
1919
package legacy
2020

21-
import "github.com/ava-labs/libevm/core/vm"
21+
import (
22+
"errors"
23+
"fmt"
24+
25+
"github.com/ava-labs/libevm/core/vm"
26+
)
27+
28+
var (
29+
errRemainingGasExceedsSuppliedGas = errors.New("remaining gas exceeds supplied gas")
30+
)
2231

2332
// PrecompiledStatefulContract is the legacy signature of
2433
// [vm.PrecompiledStatefulContract], which explicitly accepts and returns gas
@@ -31,8 +40,11 @@ func (c PrecompiledStatefulContract) Upgrade() vm.PrecompiledStatefulContract {
3140
return func(env vm.PrecompileEnvironment, input []byte) ([]byte, error) {
3241
gas := env.Gas()
3342
ret, remainingGas, err := c(env, input, gas)
34-
if used := gas - remainingGas; used < gas {
35-
env.UseGas(used)
43+
if remainingGas > gas {
44+
return ret, fmt.Errorf("%w: %d > %d", errRemainingGasExceedsSuppliedGas, remainingGas, gas)
45+
}
46+
if !env.UseGas(gas - remainingGas) {
47+
return ret, vm.ErrOutOfGas
3648
}
3749
return ret, err
3850
}

libevm/legacy/legacy_test.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package legacy
18+
19+
import (
20+
"errors"
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
26+
"github.com/ava-labs/libevm/core/vm"
27+
)
28+
29+
// stubPrecompileEnvironment implements [vm.PrecompileEnvironment] for testing.
30+
type stubPrecompileEnvironment struct {
31+
vm.PrecompileEnvironment
32+
gas uint64
33+
}
34+
35+
func (s *stubPrecompileEnvironment) Gas() uint64 {
36+
return s.gas
37+
}
38+
39+
func (s *stubPrecompileEnvironment) UseGas(gas uint64) (hasEnoughGas bool) {
40+
if s.gas < gas {
41+
return false
42+
}
43+
s.gas -= gas
44+
return true
45+
}
46+
47+
func TestPrecompiledStatefulContract_Upgrade(t *testing.T) {
48+
t.Parallel()
49+
50+
errTest := errors.New("test error")
51+
52+
tests := map[string]struct {
53+
suppliedGas uint64
54+
precompileRet []byte
55+
remainingGas uint64
56+
precompileErr error
57+
wantErr error
58+
wantGas uint64
59+
}{
60+
"call_error": {
61+
suppliedGas: 10,
62+
precompileRet: []byte{2},
63+
remainingGas: 6,
64+
precompileErr: errTest,
65+
wantErr: errTest,
66+
wantGas: 6,
67+
},
68+
"remaining_gas_exceeds_supplied_gas": {
69+
suppliedGas: 10,
70+
precompileRet: []byte{2},
71+
remainingGas: 11,
72+
wantErr: errRemainingGasExceedsSuppliedGas,
73+
wantGas: 10,
74+
},
75+
"zero_remaining_gas": {
76+
suppliedGas: 10,
77+
precompileRet: []byte{2},
78+
remainingGas: 0,
79+
wantGas: 0,
80+
},
81+
"used_one_gas": {
82+
suppliedGas: 10,
83+
precompileRet: []byte{2},
84+
remainingGas: 9,
85+
wantGas: 9,
86+
},
87+
}
88+
89+
for name, test := range tests {
90+
testCase := test
91+
t.Run(name, func(t *testing.T) {
92+
t.Parallel()
93+
94+
c := PrecompiledStatefulContract(func(env vm.PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
95+
return testCase.precompileRet, testCase.remainingGas, testCase.precompileErr
96+
})
97+
98+
upgraded := c.Upgrade()
99+
100+
env := &stubPrecompileEnvironment{
101+
gas: testCase.suppliedGas,
102+
}
103+
input := []byte("unused")
104+
105+
ret, err := upgraded(env, input)
106+
require.ErrorIs(t, err, testCase.wantErr)
107+
assert.Equal(t, testCase.precompileRet, ret, "bytes returned by upgraded contract")
108+
assert.Equalf(t, testCase.wantGas, env.gas, "remaining gas in %T", env)
109+
})
110+
}
111+
}

0 commit comments

Comments
 (0)