-
Notifications
You must be signed in to change notification settings - Fork 142
/
evmestimategas.go
146 lines (129 loc) · 4.24 KB
/
evmestimategas.go
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package chainutil
import (
"fmt"
"regexp"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/params"
"github.com/iotaledger/wasp/packages/chain"
"github.com/iotaledger/wasp/packages/isc"
"github.com/iotaledger/wasp/packages/parameters"
"github.com/iotaledger/wasp/packages/vm"
"github.com/iotaledger/wasp/packages/vm/core/governance"
"github.com/iotaledger/wasp/packages/vm/gas"
)
var evmErrOutOfGasRegex = regexp.MustCompile("out of gas|intrinsic gas too low")
// EVMEstimateGas executes the given request and discards the resulting chain state. It is useful
// for estimating gas.
func EVMEstimateGas(ch chain.ChainCore, aliasOutput *isc.AliasOutputWithID, call ethereum.CallMsg) (uint64, error) { //nolint:gocyclo,funlen
// Determine the lowest and highest possible gas limits to binary search in between
intrinsicGas, err := core.IntrinsicGas(call.Data, nil, call.To == nil, true, true, true)
if err != nil {
return 0, err
}
var (
lo uint64 = intrinsicGas - 1
hi uint64
gasCap uint64
)
info := getChainInfo(ch)
maximumPossibleGas := gas.EVMCallGasLimit(info.GasLimits, &info.GasFeePolicy.EVMGasRatio)
if call.Gas >= params.TxGas {
hi = call.Gas
} else {
hi = maximumPossibleGas
}
if call.GasPrice == nil {
call.GasPrice = info.GasFeePolicy.DefaultGasPriceFullDecimals(parameters.L1().BaseToken.Decimals)
}
gasCap = hi
// Create a helper to check if a gas allowance results in an executable transaction
blockTime := time.Now()
executable := func(gas uint64) (failed bool, result *vm.RequestResult, err error) {
call.Gas = gas
iscReq := isc.NewEVMOffLedgerCallRequest(ch.ID(), call)
res, err := runISCRequest(ch, aliasOutput, blockTime, iscReq, true)
if err != nil {
return true, nil, err
}
return res.Receipt.Error != nil, res, nil
}
// Execute the binary search and hone in on an executable gas limit
var lastUsed uint64
const maxLastUsedAttempts = 2
lastUsedAttempts := 0
for lo+1 < hi {
mid := (hi + lo) / 2
if lastUsed > lo && lastUsed != mid && lastUsed < hi && lastUsedAttempts < maxLastUsedAttempts {
// use the last used gas as a better estimation to home in faster
mid = lastUsed
// this may turn the binary search into a linear search for some
// edge cases. We put a limit and after that we default to the
// binary search.
lastUsedAttempts++
}
var failed bool
var err error
failed, res, err := executable(mid)
if err != nil {
return 0, err
}
if failed {
lastUsed = 0
lo = mid
} else {
lastUsed = res.Receipt.GasBurned
hi = mid
if lastUsed == mid {
// if used gas == gas limit, then use this as the estimation.
// It may not be the most precise estimation (e.g. lowering the gas
// limit may end up using less gas), but it's "good enough" and
// saves a lot of iterations in the binary search.
break
}
}
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == gasCap {
failed, res, err := executable(hi)
if err != nil {
return 0, err
}
if failed {
if res.Receipt.Error != nil {
isOutOfGas, resolvedErr, err := resolveError(ch, res.Receipt.Error)
if err != nil {
return 0, err
}
if resolvedErr != nil && !isOutOfGas {
return 0, resolvedErr
}
}
if hi == maximumPossibleGas {
return 0, fmt.Errorf("request might require more gas than it is allowed by the VM (%d), or will never succeed", gasCap)
}
// the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds budget (%d)", gasCap)
}
}
return hi, nil
}
func getChainInfo(ch chain.ChainCore) *isc.ChainInfo {
return governance.NewStateAccess(mustLatestState(ch)).ChainInfo(ch.ID())
}
func resolveError(ch chain.ChainCore, receiptError *isc.UnresolvedVMError) (isOutOfGas bool, resolved *isc.VMError, err error) {
if receiptError.ErrorCode == vm.ErrGasBudgetExceeded.Code() {
// out of gas when charging ISC gas
return true, nil, nil
}
vmerr, resolvingErr := ResolveError(ch, receiptError)
if resolvingErr != nil {
return true, nil, fmt.Errorf("error resolving vmerror: %w", resolvingErr)
}
if evmErrOutOfGasRegex.Match([]byte(vmerr.Error())) {
// increase gas
return true, vmerr, nil
}
return false, vmerr, nil
}