Skip to content

Commit 94f65ea

Browse files
Merge pull request #8181 from onflow/janez/public-port-7125-master
Fix EVM gas overflow in FVM - port from internal
2 parents 280df4e + c844a6d commit 94f65ea

File tree

11 files changed

+193
-13
lines changed

11 files changed

+193
-13
lines changed

cmd/util/ledger/util/nop_meter.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package util
22

33
import (
4+
"math"
5+
46
"github.com/onflow/cadence/common"
57

68
"github.com/onflow/flow-go/fvm/environment"
@@ -28,6 +30,10 @@ func (n NopMeter) ComputationIntensities() meter.MeteredComputationIntensities {
2830
return meter.MeteredComputationIntensities{}
2931
}
3032

33+
func (n NopMeter) ComputationRemaining(_ common.ComputationKind) uint64 {
34+
return math.MaxUint64
35+
}
36+
3137
func (n NopMeter) MeterMemory(_ common.MemoryUsage) error {
3238
return nil
3339
}

fvm/environment/meter.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ type Meter interface {
112112

113113
ComputationIntensities() meter.MeteredComputationIntensities
114114
ComputationAvailable(common.ComputationUsage) bool
115+
ComputationRemaining(kind common.ComputationKind) uint64
115116

116117
MeterEmittedEvent(byteSize uint64) error
117118
TotalEmittedEventBytes() uint64
@@ -141,6 +142,10 @@ func (meter *meterImpl) ComputationAvailable(usage common.ComputationUsage) bool
141142
return meter.txnState.ComputationAvailable(usage)
142143
}
143144

145+
func (meter *meterImpl) ComputationRemaining(kind common.ComputationKind) uint64 {
146+
return meter.txnState.ComputationRemaining(kind)
147+
}
148+
144149
func (meter *meterImpl) ComputationUsed() (uint64, error) {
145150
return meter.txnState.TotalComputationUsed(), nil
146151
}

fvm/environment/mock/environment.go

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fvm/environment/mock/meter.go

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fvm/evm/backends/wrappedEnv.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ func (we *WrappedEnvironment) ComputationAvailable(usage common.ComputationUsage
8282
return we.env.ComputationAvailable(usage)
8383
}
8484

85-
// MeterMemory meters the memory usage of a new operation.
85+
// ComputationRemaining returns the remaining computation for the given kind.
86+
func (we *WrappedEnvironment) ComputationRemaining(kind common.ComputationKind) uint64 {
87+
return we.env.ComputationRemaining(kind)
88+
}
89+
8690
func (we *WrappedEnvironment) MeterMemory(usage common.MemoryUsage) error {
8791
err := we.env.MeterMemory(usage)
8892
return handleEnvironmentError(err)

fvm/evm/evm_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/binary"
66
"fmt"
7+
"math"
78
"math/big"
89
"testing"
910

@@ -1244,6 +1245,89 @@ func TestEVMBatchRun(t *testing.T) {
12441245
require.Equal(t, num, new(big.Int).SetBytes(res.ReturnedData).Int64())
12451246
})
12461247
})
1248+
1249+
// run a batch of two transactions. The sum of their gas usage would overflow an uint46
1250+
// so the batch run should fail with an overflow error.
1251+
t.Run("Batch run evm gas overflow", func(t *testing.T) {
1252+
t.Parallel()
1253+
RunWithNewEnvironment(t,
1254+
chain, func(
1255+
ctx fvm.Context,
1256+
vm fvm.VM,
1257+
snapshot snapshot.SnapshotTree,
1258+
testContract *TestContract,
1259+
testAccount *EOATestAccount,
1260+
) {
1261+
sc := systemcontracts.SystemContractsForChain(chain.ChainID())
1262+
batchRunCode := []byte(fmt.Sprintf(
1263+
`
1264+
import EVM from %s
1265+
1266+
transaction(txs: [[UInt8]], coinbaseBytes: [UInt8; 20]) {
1267+
execute {
1268+
let coinbase = EVM.EVMAddress(bytes: coinbaseBytes)
1269+
let batchResults = EVM.batchRun(txs: txs, coinbase: coinbase)
1270+
}
1271+
}
1272+
`,
1273+
sc.EVMContract.Address.HexWithPrefix(),
1274+
))
1275+
1276+
coinbaseAddr := types.Address{1, 2, 3}
1277+
coinbaseBalance := getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr)
1278+
require.Zero(t, types.BalanceToBigInt(coinbaseBalance).Uint64())
1279+
1280+
batchCount := 2
1281+
txBytes := make([]cadence.Value, batchCount)
1282+
1283+
tx := testAccount.PrepareSignAndEncodeTx(t,
1284+
testContract.DeployedAt.ToCommon(),
1285+
testContract.MakeCallData(t, "storeWithLog", big.NewInt(0)),
1286+
big.NewInt(0),
1287+
uint64(200_000),
1288+
big.NewInt(1),
1289+
)
1290+
1291+
txBytes[0] = cadence.NewArray(
1292+
unittest.BytesToCdcUInt8(tx),
1293+
).WithType(stdlib.EVMTransactionBytesCadenceType)
1294+
1295+
tx = testAccount.PrepareSignAndEncodeTx(t,
1296+
testContract.DeployedAt.ToCommon(),
1297+
testContract.MakeCallData(t, "storeWithLog", big.NewInt(1)),
1298+
big.NewInt(0),
1299+
math.MaxUint64-uint64(100_000),
1300+
big.NewInt(1),
1301+
)
1302+
1303+
txBytes[1] = cadence.NewArray(
1304+
unittest.BytesToCdcUInt8(tx),
1305+
).WithType(stdlib.EVMTransactionBytesCadenceType)
1306+
1307+
coinbase := cadence.NewArray(
1308+
unittest.BytesToCdcUInt8(coinbaseAddr.Bytes()),
1309+
).WithType(stdlib.EVMAddressBytesCadenceType)
1310+
1311+
txs := cadence.NewArray(txBytes).
1312+
WithType(cadence.NewVariableSizedArrayType(
1313+
stdlib.EVMTransactionBytesCadenceType,
1314+
))
1315+
1316+
txBody, err := flow.NewTransactionBodyBuilder().
1317+
SetScript(batchRunCode).
1318+
SetPayer(sc.FlowServiceAccount.Address).
1319+
AddArgument(json.MustEncode(txs)).
1320+
AddArgument(json.MustEncode(coinbase)).
1321+
Build()
1322+
require.NoError(t, err)
1323+
1324+
state, output, err := vm.Run(ctx, fvm.Transaction(txBody, 0), snapshot)
1325+
require.NoError(t, err)
1326+
require.Error(t, output.Err)
1327+
require.ErrorContains(t, output.Err, "insufficient computation")
1328+
require.Empty(t, state.WriteSet)
1329+
})
1330+
})
12471331
}
12481332

12491333
func TestEVMBlockData(t *testing.T) {

fvm/evm/handler/handler.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,12 @@ func (h *ContractHandler) BatchRun(rlpEncodedTxs [][]byte, gasFeeCollector types
204204
}
205205

206206
func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte) ([]*types.Result, error) {
207-
// step 1 - transaction decoding and compute total gas needed
208-
// This is safe to be done before checking the gas
209-
// as it has its own metering
210-
var totalGasLimit types.GasLimit
207+
// step 1 - transaction decoding and check that enough evm gas is available in the FVM transaction
208+
209+
// remainingGasLimit is the remaining EVM gas available in hte FVM transaction
210+
remainingGasLimit := h.backend.ComputationRemaining(environment.ComputationKindEVMGasUsage)
211211
batchLen := len(rlpEncodedTxs)
212212
txs := make([]*gethTypes.Transaction, batchLen)
213-
214213
for i, rlpEncodedTx := range rlpEncodedTxs {
215214
tx, err := h.decodeTransaction(rlpEncodedTx)
216215
// if any tx fails decoding revert the batch
@@ -219,14 +218,13 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte) ([]*types.Result, err
219218
}
220219

221220
txs[i] = tx
222-
totalGasLimit += types.GasLimit(tx.Gas())
223-
}
224221

225-
// step 2 - check if enough computation is available
226-
// for the whole batch
227-
err := h.checkGasLimit(totalGasLimit)
228-
if err != nil {
229-
return nil, err
222+
// step 2 - check if enough computation is available
223+
txGasLimit := tx.Gas()
224+
if remainingGasLimit < txGasLimit {
225+
return nil, types.ErrInsufficientComputation
226+
}
227+
remainingGasLimit -= txGasLimit
230228
}
231229

232230
// step 3 - prepare block context

fvm/evm/testutils/backend.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ func getSimpleMeter() *testMeter {
203203
computationUsed: func() (uint64, error) {
204204
return compUsed, nil
205205
},
206+
computationRemaining: func(kind common.ComputationKind) uint64 {
207+
return TestComputationLimit - compUsed
208+
},
206209
}
207210
}
208211

@@ -366,6 +369,7 @@ type testMeter struct {
366369
hasComputationCapacity func(common.ComputationUsage) bool
367370
computationUsed func() (uint64, error)
368371
computationIntensities func() meter.MeteredComputationIntensities
372+
computationRemaining func(kind common.ComputationKind) uint64
369373

370374
meterMemory func(usage common.MemoryUsage) error
371375
memoryUsed func() (uint64, error)
@@ -407,6 +411,14 @@ func (m *testMeter) ComputationIntensities() meter.MeteredComputationIntensities
407411
return computationIntensities()
408412
}
409413

414+
func (m *testMeter) ComputationRemaining(kind common.ComputationKind) uint64 {
415+
computationRemaining := m.computationRemaining
416+
if computationRemaining == nil {
417+
panic("method not set")
418+
}
419+
return computationRemaining(kind)
420+
}
421+
410422
func (m *testMeter) ComputationUsed() (uint64, error) {
411423
computationUsed := m.computationUsed
412424
if computationUsed == nil {

fvm/meter/computation_meter.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,22 @@ func (m *ComputationMeter) ComputationAvailable(usage common.ComputationUsage) b
118118
return potentialComputationUsage <= m.params.computationLimit
119119
}
120120

121+
// ComputationRemaining returns the remaining computation (intensity) left in the transaction for the given type
122+
func (m *ComputationMeter) ComputationRemaining(kind common.ComputationKind) uint64 {
123+
w, ok := m.params.computationWeights[kind]
124+
// if the weight is 0 or not set return max uint64
125+
if !ok || w == 0 {
126+
return math.MaxUint64
127+
}
128+
129+
remainingComputationUsage := m.params.computationLimit - m.computationUsed
130+
if remainingComputationUsage <= 0 {
131+
return 0
132+
}
133+
134+
return remainingComputationUsage / w
135+
}
136+
121137
// ComputationIntensities returns all the measured computational intensities
122138
func (m *ComputationMeter) ComputationIntensities() MeteredComputationIntensities {
123139
return m.computationIntensities

fvm/storage/state/execution_state.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package state
22

33
import (
44
"fmt"
5+
"math"
56

67
"github.com/onflow/cadence/common"
78
"github.com/onflow/crypto/hash"
@@ -239,6 +240,19 @@ func (state *ExecutionState) ComputationAvailable(usage common.ComputationUsage)
239240
return true
240241
}
241242

243+
// ComputationRemaining returns the remaining computation for the given kind.
244+
func (state *ExecutionState) ComputationRemaining(kind common.ComputationKind) uint64 {
245+
if state.finalized {
246+
// if state is finalized return false
247+
return 0
248+
}
249+
250+
if state.meteringEnabled {
251+
return state.meter.ComputationRemaining(kind)
252+
}
253+
return math.MaxUint64
254+
}
255+
242256
// TotalComputationUsed returns total computation used
243257
func (state *ExecutionState) TotalComputationUsed() uint64 {
244258
return state.meter.TotalComputationUsed()

0 commit comments

Comments
 (0)