Skip to content

Commit 30dc8c0

Browse files
committed
feat(evm/precompile); DynamicPrecompile gas handling and OOG panic recovery
1 parent 9d08af5 commit 30dc8c0

File tree

9 files changed

+181
-54
lines changed

9 files changed

+181
-54
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@ replace (
271271

272272
github.com/cosmos/iavl => github.com/cosmos/iavl v0.20.0
273273

274-
github.com/ethereum/go-ethereum => github.com/NibiruChain/go-ethereum v1.14.13-nibiru.3
274+
github.com/ethereum/go-ethereum => github.com/NibiruChain/go-ethereum v1.14.13-nibiru.4
275+
// github.com/ethereum/go-ethereum => ../nibi-geth
275276

276277
github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
277278

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,8 +669,8 @@ github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZ
669669
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
670670
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
671671
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
672-
github.com/NibiruChain/go-ethereum v1.14.13-nibiru.3 h1:vWBieiwll6sjQ4OTagJUXwtjw85Crfk3f7kAPik6BD8=
673-
github.com/NibiruChain/go-ethereum v1.14.13-nibiru.3/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY=
672+
github.com/NibiruChain/go-ethereum v1.14.13-nibiru.4 h1:/ccFY4oF1LFUtBUomw0tmLNNc6qwEhNnjPKGPelV2Jg=
673+
github.com/NibiruChain/go-ethereum v1.14.13-nibiru.4/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY=
674674
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
675675
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
676676
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=

x/evm/errors.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ package evm
55
import (
66
"encoding/hex"
77
"fmt"
8+
"strings"
89

910
sdkioerrors "cosmossdk.io/errors"
1011

12+
sdk "github.com/cosmos/cosmos-sdk/types"
1113
"github.com/ethereum/go-ethereum/accounts/abi"
14+
"github.com/ethereum/go-ethereum/core/vm"
1215
)
1316

1417
const (
@@ -55,6 +58,25 @@ var (
5558
ErrCanonicalWnibi = "canonical WNIBI address in state is a not a smart contract"
5659
)
5760

61+
// ParseOOGPanic interprets the result of recover() and returns (oog
62+
// bool, perr error). If panicInfo is nil, returns (false, nil). OOG panics
63+
// (sdk.ErrorOutOfGas or message "out of gas") return (true, vm.ErrOutOfGas).
64+
// Other panics return (false, err) with err from formatUnexpected(panicInfo).
65+
// Callers should keep the line "panicInfo := recover()" explicit and pass
66+
// panicInfo here.
67+
func ParseOOGPanic(panicInfo any, formatUnexpected func(any) string) (oog bool, perr error) {
68+
if panicInfo == nil {
69+
return false, nil
70+
}
71+
if _, isOOG := panicInfo.(sdk.ErrorOutOfGas); isOOG {
72+
return true, vm.ErrOutOfGas
73+
}
74+
if strings.Contains(fmt.Sprint(panicInfo), "out of gas") {
75+
return true, vm.ErrOutOfGas
76+
}
77+
return false, fmt.Errorf("%s", formatUnexpected(panicInfo))
78+
}
79+
5880
// NewRevertError unpacks the revert return bytes and returns a wrapped error
5981
// with the return reason.
6082
func NewRevertError(revertReason []byte) error {

x/evm/evmstate/grpc_query.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -386,22 +386,15 @@ func (k Keeper) EstimateGas(
386386
// increase gas. Any non-OOG panic aborts the search with a
387387
// contextual error for diagnostics.
388388
var (
389-
oog bool
390-
perr error
389+
oog bool // true if panic was out-of-gas
390+
perr error // ErrOutOfGas for OOG, or formatted error for unexpected panic
391391
)
392-
393-
if panicInfo := recover(); panicInfo != nil {
394-
if _, isOutOfGasPanic := panicInfo.(sdk.ErrorOutOfGas); isOutOfGasPanic {
395-
oog, perr = true, vm.ErrOutOfGas
396-
} else if strings.Contains(fmt.Sprint(panicInfo), "out of gas") {
397-
oog, perr = true, vm.ErrOutOfGas
398-
} else {
399-
// Non-OOG panics are not handled here
400-
oog, perr = false, fmt.Errorf(
401-
`unexpected panic in eth_estimateGas { gas: %d }: %v`, gas, panicInfo)
402-
}
392+
panicInfo := recover()
393+
if panicInfo != nil {
394+
oog, perr = evm.ParseOOGPanic(panicInfo, func(p any) string {
395+
return fmt.Sprintf("unexpected panic in eth_estimateGas { gas: %d }: %v", gas, p)
396+
})
403397
}
404-
405398
if oog {
406399
vmError, rsp, err = true, nil, nil
407400
return

x/evm/precompile/funtoken.go

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
bank "github.com/cosmos/cosmos-sdk/x/bank/types"
1010
gethabi "github.com/ethereum/go-ethereum/accounts/abi"
1111
gethcommon "github.com/ethereum/go-ethereum/common"
12-
"github.com/ethereum/go-ethereum/core/tracing"
1312
"github.com/ethereum/go-ethereum/core/vm"
1413

1514
bankkeeper "github.com/NibiruChain/nibiru/v2/x/bank/keeper"
@@ -22,7 +21,10 @@ import (
2221
tftypes "github.com/NibiruChain/nibiru/v2/x/tokenfactory/types"
2322
)
2423

25-
var _ vm.PrecompiledContract = (*precompileFunToken)(nil)
24+
var (
25+
_ vm.PrecompiledContract = (*precompileFunToken)(nil)
26+
_ vm.DynamicPrecompile = (*precompileFunToken)(nil)
27+
)
2628

2729
// Precompile address for "IFunToken.sol", the contract that
2830
// enables transfers of ERC20 tokens to "nibi" addresses as bank coins
@@ -52,7 +54,6 @@ const (
5254
FunTokenMethod_getErc20Address PrecompileMethod = "getErc20Address"
5355
)
5456

55-
// Run runs the precompiled contract
5657
func (p precompileFunToken) Run(
5758
evmObj *vm.EVM,
5859
trueCaller gethcommon.Address,
@@ -64,13 +65,50 @@ func (p precompileFunToken) Run(
6465
// isDelegatedCall: Flag to add conditional logic specific to delegate calls
6566
isDelegatedCall bool,
6667
) (bz []byte, err error) {
68+
bz, _, err = p.DynamicRun(evmObj, trueCaller, contract, readonly, isDelegatedCall)
69+
return bz, err
70+
}
71+
72+
// DynamicRun runs the precompiled contract and returns the gas cost.
73+
func (p precompileFunToken) DynamicRun(
74+
evmObj *vm.EVM,
75+
trueCaller gethcommon.Address,
76+
// Note that we use "trueCaller" here to differentiate between a delegate
77+
// caller ("parent.CallerAddress" in geth) and "contract.CallerAddress"
78+
// because these two addresses may differ.
79+
contract *vm.Contract,
80+
readonly bool,
81+
// isDelegatedCall: Flag to add conditional logic specific to delegate calls
82+
isDelegatedCall bool,
83+
) (bz []byte, gasCost uint64, err error) {
6784
defer func() {
6885
err = ErrPrecompileRun(err, p)
6986
}()
7087
startResult, err := OnRunStart(evmObj, contract.Input, p.ABI(), contract.Gas)
7188
if err != nil {
72-
return nil, err
89+
return nil, 0, err
7390
}
91+
defer func() {
92+
// Recover OOG panics as ErrOutOfGas; other panics become an error.
93+
var (
94+
oog bool // true if panic was out-of-gas
95+
perr error // ErrOutOfGas for OOG, or formatted error for unexpected panic
96+
)
97+
panicInfo := recover()
98+
if panicInfo != nil {
99+
oog, perr = evm.ParseOOGPanic(panicInfo, func(p interface{}) string {
100+
return fmt.Sprintf("unexpected panic in precompile: %v", p)
101+
})
102+
}
103+
if oog {
104+
gasCost = startResult.Ctx.GasMeter().GasConsumed()
105+
err = perr
106+
return
107+
} else if perr != nil {
108+
err = perr
109+
return
110+
}
111+
}()
74112

75113
abciEventsStartIdx := len(startResult.Ctx.EventManager().Events())
76114

@@ -96,14 +134,13 @@ func (p precompileFunToken) Run(
96134
err = fmt.Errorf("invalid method called with name \"%s\"", method.Name)
97135
return
98136
}
99-
// Gas consumed by a local gas meter
100-
contract.UseGas(
101-
startResult.Ctx.GasMeter().GasConsumed(),
102-
evmObj.Config.Tracer,
103-
tracing.GasChangeCallPrecompiledContract,
104-
)
137+
// Gas consumed by a local gas meter (the one in startResult.Ctx from OnRunStart).
138+
// Gas consumed is guaranteed to be less than the gas limit (contract.Gas) because
139+
// the gas meter was initialized inside of the setup function, OnRunStart. The EVM
140+
// applies it via the returned gasCost from DynamicRun.
141+
gasCost = startResult.Ctx.GasMeter().GasConsumed()
105142
if err != nil {
106-
return nil, err
143+
return nil, gasCost, err
107144
}
108145

109146
// Emit extra events for the EVM if this is a transaction
@@ -117,7 +154,7 @@ func (p precompileFunToken) Run(
117154
)
118155
}
119156

120-
return bz, err
157+
return bz, gasCost, err
121158
}
122159

123160
func PrecompileFunToken(keepers keepers.PublicKeepers) NibiruCustomPrecompile {

x/evm/precompile/oracle.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import (
77
sdk "github.com/cosmos/cosmos-sdk/types"
88
gethabi "github.com/ethereum/go-ethereum/accounts/abi"
99
gethcommon "github.com/ethereum/go-ethereum/common"
10-
"github.com/ethereum/go-ethereum/core/tracing"
1110
"github.com/ethereum/go-ethereum/core/vm"
1211

1312
"github.com/NibiruChain/nibiru/v2/app/keepers"
13+
"github.com/NibiruChain/nibiru/v2/x/evm"
1414
"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
1515
"github.com/NibiruChain/nibiru/v2/x/nutil/asset"
1616
oraclekeeper "github.com/NibiruChain/nibiru/v2/x/oracle/keeper"
@@ -40,7 +40,7 @@ const (
4040

4141
// Run runs the precompiled contract
4242
func (p precompileOracle) Run(
43-
evm *vm.EVM,
43+
evmObj *vm.EVM,
4444
trueCaller gethcommon.Address,
4545
// Note that we use "trueCaller" here to differentiate between a delegate
4646
// caller ("parent.CallerAddress" in geth) and "contract.CallerAddress"
@@ -53,10 +53,31 @@ func (p precompileOracle) Run(
5353
defer func() {
5454
err = ErrPrecompileRun(err, p)
5555
}()
56-
startResult, err := OnRunStart(evm, contract.Input, p.ABI(), contract.Gas)
56+
startResult, err := OnRunStart(evmObj, contract.Input, p.ABI(), contract.Gas)
5757
if err != nil {
5858
return nil, err
5959
}
60+
defer func() {
61+
// Recover OOG panics as ErrOutOfGas; other panics become an error.
62+
var (
63+
oog bool // true if panic was out-of-gas
64+
perr error // ErrOutOfGas for OOG, or formatted error for unexpected panic
65+
)
66+
panicInfo := recover()
67+
if panicInfo != nil {
68+
oog, perr = evm.ParseOOGPanic(panicInfo, func(p any) string {
69+
return fmt.Sprintf("unexpected panic in precompile: %v", p)
70+
})
71+
}
72+
if oog {
73+
err = perr
74+
return
75+
} else if perr != nil {
76+
err = perr
77+
return
78+
}
79+
}()
80+
6081
method, args, ctx := startResult.Method, startResult.Args, startResult.Ctx
6182

6283
switch PrecompileMethod(method.Name) {
@@ -72,11 +93,6 @@ func (p precompileOracle) Run(
7293
err = fmt.Errorf("invalid method called with name \"%s\"", method.Name)
7394
return
7495
}
75-
contract.UseGas(
76-
startResult.Ctx.GasMeter().GasConsumed(),
77-
evm.Config.Tracer,
78-
tracing.GasChangeCallPrecompiledContract,
79-
)
8096
if err != nil {
8197
return nil, err
8298
}

x/evm/precompile/p256.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"math/big"
88

99
"github.com/NibiruChain/nibiru/v2/app/keepers"
10+
"github.com/NibiruChain/nibiru/v2/x/evm"
1011

1112
gethcommon "github.com/ethereum/go-ethereum/common"
1213
"github.com/ethereum/go-ethereum/core/vm"
@@ -51,6 +52,26 @@ func (p precompileP256) Run(
5152
defer func() {
5253
err = ErrPrecompileRun(err, p)
5354
}()
55+
defer func() {
56+
// Recover OOG panics as ErrOutOfGas; other panics become an error.
57+
var (
58+
oog bool // true if panic was out-of-gas
59+
perr error // ErrOutOfGas for OOG, or formatted error for unexpected panic
60+
)
61+
panicInfo := recover()
62+
if panicInfo != nil {
63+
oog, perr = evm.ParseOOGPanic(panicInfo, func(p any) string {
64+
return fmt.Sprintf("unexpected panic in precompile: %v", p)
65+
})
66+
}
67+
if oog {
68+
err = perr
69+
return
70+
} else if perr != nil {
71+
err = perr
72+
return
73+
}
74+
}()
5475

5576
if len(contract.Input) != p256InputLen {
5677
return nil, fmt.Errorf("invalid input length: want %d, got %d", p256InputLen, len(contract.Input))

x/evm/precompile/precompile.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,14 @@ type OnRunStartResult struct {
184184
// }
185185
// ```
186186
func OnRunStart(
187-
evm *vm.EVM, contractInput []byte, abi *gethabi.ABI, gasLimit uint64,
187+
evmObj *vm.EVM, contractInput []byte, abi *gethabi.ABI, gasLimit uint64,
188188
) (res OnRunStartResult, err error) {
189189
method, args, err := decomposeInput(abi, contractInput)
190190
if err != nil {
191191
return res, err
192192
}
193193

194-
sdb, ok := evm.StateDB.(*evmstate.SDB)
194+
sdb, ok := evmObj.StateDB.(*evmstate.SDB)
195195
if !ok {
196196
err = fmt.Errorf("failed to load the sdk.Context from the EVM StateDB")
197197
return

0 commit comments

Comments
 (0)