Skip to content

Commit d85bc62

Browse files
committed
Validate gas limit of COA interactions according to Osaka's EIP-7825
1 parent 736053b commit d85bc62

File tree

9 files changed

+245
-33
lines changed

9 files changed

+245
-33
lines changed

fvm/evm/emulator/emulator.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package emulator
22

33
import (
44
"errors"
5+
"fmt"
56
"math/big"
67

78
gethCommon "github.com/ethereum/go-ethereum/common"
89
gethCore "github.com/ethereum/go-ethereum/core"
9-
"github.com/ethereum/go-ethereum/core/tracing"
1010
gethTracing "github.com/ethereum/go-ethereum/core/tracing"
1111
gethTypes "github.com/ethereum/go-ethereum/core/types"
1212
gethVM "github.com/ethereum/go-ethereum/core/vm"
@@ -120,6 +120,22 @@ func (bl *BlockView) DirectCall(call *types.DirectCall) (res *types.Result, err
120120
// Set the nonce for the call (needed for some operations like deployment)
121121
call.Nonce = proc.state.GetNonce(call.From.ToCommon())
122122

123+
if !call.ValidEIP7825GasLimit(proc.config.ChainRules()) {
124+
res := &types.Result{
125+
TxType: call.Type,
126+
TxHash: call.Hash(),
127+
}
128+
res.SetValidationError(
129+
fmt.Errorf(
130+
"%w (cap: %d, tx: %d)",
131+
gethCore.ErrGasLimitTooHigh,
132+
gethParams.MaxTxGas,
133+
call.GasLimit,
134+
),
135+
)
136+
return res, nil
137+
}
138+
123139
// Call tx tracer
124140
if proc.evm.Config.Tracer != nil && proc.evm.Config.Tracer.OnTxStart != nil {
125141
proc.evm.Config.Tracer.OnTxStart(proc.evm.GetVMContext(), call.Transaction(), call.From.ToCommon())
@@ -596,7 +612,7 @@ func (proc *procedure) deployAt(
596612
res.DeployedContractAddress = &call.To
597613
res.CumulativeGasUsed = proc.config.BlockTotalGasUsedSoFar + res.GasConsumed
598614

599-
proc.state.SetCode(addr, ret, tracing.CodeChangeContractCreation)
615+
proc.state.SetCode(addr, ret, gethTracing.CodeChangeContractCreation)
600616
res.StateChangeCommitment, err = proc.commit(true)
601617
return res, err
602618
}

fvm/evm/emulator/emulator_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package emulator_test
33
import (
44
"encoding/hex"
55
"fmt"
6-
"math"
76
"math/big"
87
"strings"
98
"testing"
@@ -211,7 +210,7 @@ func TestContractInteraction(t *testing.T) {
211210
call := types.NewDeployCall(
212211
testAccount,
213212
testContract.ByteCode,
214-
math.MaxUint64,
213+
gethParams.MaxTxGas,
215214
amountToBeTransfered,
216215
testAccountNonce)
217216
res, err := blk.DirectCall(call)
@@ -599,7 +598,7 @@ func TestDeployAtFunctionality(t *testing.T) {
599598
testAccount,
600599
target,
601600
testContract.ByteCode,
602-
math.MaxUint64,
601+
gethParams.MaxTxGas,
603602
amountToBeTransfered,
604603
0,
605604
),
@@ -629,7 +628,7 @@ func TestDeployAtFunctionality(t *testing.T) {
629628
testAccount,
630629
target,
631630
testContract.ByteCode,
632-
math.MaxUint64,
631+
gethParams.MaxTxGas,
633632
amountToBeTransfered,
634633
0),
635634
)
@@ -687,7 +686,7 @@ func TestSelfdestruct(t *testing.T) {
687686
types.NewDeployCall(
688687
testAddress,
689688
testContract.ByteCode,
690-
math.MaxUint64,
689+
gethParams.MaxTxGas,
691690
deployBalance,
692691
0),
693692
)
@@ -768,7 +767,7 @@ func TestFactoryPatterns(t *testing.T) {
768767
types.NewDeployCall(
769768
factoryDeployer,
770769
factoryContract.ByteCode,
771-
math.MaxUint64,
770+
gethParams.MaxTxGas,
772771
factoryBalance,
773772
0),
774773
)

fvm/evm/evm_test.go

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"encoding/binary"
66
"fmt"
7-
"math"
87
"math/big"
98
"testing"
109

@@ -1915,6 +1914,134 @@ func TestCadenceOwnedAccountFunctionalities(t *testing.T) {
19151914
assert.Len(t, state.UpdatedRegisterIDs(), 0)
19161915
})
19171916
})
1917+
1918+
t.Run("test coa deploy with max gas limit cap", func(t *testing.T) {
1919+
RunWithNewEnvironment(t,
1920+
chain, func(
1921+
ctx fvm.Context,
1922+
vm fvm.VM,
1923+
snapshot snapshot.SnapshotTree,
1924+
testContract *TestContract,
1925+
testAccount *EOATestAccount,
1926+
) {
1927+
code := []byte(fmt.Sprintf(
1928+
`
1929+
import EVM from %s
1930+
import FlowToken from %s
1931+
access(all)
1932+
fun main(code: [UInt8]): EVM.Result {
1933+
let admin = getAuthAccount<auth(Storage) &Account>(%s)
1934+
.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin)!
1935+
let minter <- admin.createNewMinter(allowedAmount: 2.34)
1936+
let vault <- minter.mintTokens(amount: 2.34)
1937+
destroy minter
1938+
let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount()
1939+
cadenceOwnedAccount.deposit(from: <-vault)
1940+
let res = cadenceOwnedAccount.deploy(
1941+
code: code,
1942+
gasLimit: 16_777_216,
1943+
value: EVM.Balance(attoflow: 1230000000000000000)
1944+
)
1945+
destroy cadenceOwnedAccount
1946+
return res
1947+
}
1948+
`,
1949+
sc.EVMContract.Address.HexWithPrefix(),
1950+
sc.FlowToken.Address.HexWithPrefix(),
1951+
sc.FlowServiceAccount.Address.HexWithPrefix(),
1952+
))
1953+
1954+
script := fvm.Script(code).
1955+
WithArguments(json.MustEncode(
1956+
cadence.NewArray(
1957+
unittest.BytesToCdcUInt8(testContract.ByteCode),
1958+
).WithType(cadence.NewVariableSizedArrayType(cadence.UInt8Type)),
1959+
))
1960+
1961+
_, output, err := vm.Run(
1962+
ctx,
1963+
script,
1964+
snapshot,
1965+
)
1966+
require.NoError(t, err)
1967+
require.NoError(t, output.Err)
1968+
1969+
res, err := impl.ResultSummaryFromEVMResultValue(output.Value)
1970+
require.NoError(t, err)
1971+
require.Equal(t, types.StatusSuccessful, res.Status)
1972+
require.Equal(t, types.ErrCodeNoError, res.ErrorCode)
1973+
require.Empty(t, res.ErrorMessage)
1974+
require.NotNil(t, res.DeployedContractAddress)
1975+
// we strip away first few bytes because they contain deploy code
1976+
require.Equal(t, testContract.ByteCode[17:], []byte(res.ReturnedData))
1977+
})
1978+
})
1979+
1980+
t.Run("test coa deploy with bigger than max gas limit cap", func(t *testing.T) {
1981+
RunWithNewEnvironment(t,
1982+
chain, func(
1983+
ctx fvm.Context,
1984+
vm fvm.VM,
1985+
snapshot snapshot.SnapshotTree,
1986+
testContract *TestContract,
1987+
testAccount *EOATestAccount,
1988+
) {
1989+
code := []byte(fmt.Sprintf(
1990+
`
1991+
import EVM from %s
1992+
import FlowToken from %s
1993+
access(all)
1994+
fun main(code: [UInt8]): EVM.Result {
1995+
let admin = getAuthAccount<auth(Storage) &Account>(%s)
1996+
.storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin)!
1997+
let minter <- admin.createNewMinter(allowedAmount: 2.34)
1998+
let vault <- minter.mintTokens(amount: 2.34)
1999+
destroy minter
2000+
let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount()
2001+
cadenceOwnedAccount.deposit(from: <-vault)
2002+
let res = cadenceOwnedAccount.deploy(
2003+
code: code,
2004+
gasLimit: 16_777_226,
2005+
value: EVM.Balance(attoflow: 1230000000000000000)
2006+
)
2007+
destroy cadenceOwnedAccount
2008+
return res
2009+
}
2010+
`,
2011+
sc.EVMContract.Address.HexWithPrefix(),
2012+
sc.FlowToken.Address.HexWithPrefix(),
2013+
sc.FlowServiceAccount.Address.HexWithPrefix(),
2014+
))
2015+
2016+
script := fvm.Script(code).
2017+
WithArguments(json.MustEncode(
2018+
cadence.NewArray(
2019+
unittest.BytesToCdcUInt8(testContract.ByteCode),
2020+
).WithType(cadence.NewVariableSizedArrayType(cadence.UInt8Type)),
2021+
))
2022+
2023+
_, output, err := vm.Run(
2024+
ctx,
2025+
script,
2026+
snapshot,
2027+
)
2028+
require.NoError(t, err)
2029+
require.NoError(t, output.Err)
2030+
2031+
res, err := impl.ResultSummaryFromEVMResultValue(output.Value)
2032+
require.NoError(t, err)
2033+
require.Equal(t, types.StatusInvalid, res.Status)
2034+
require.Equal(t, types.ValidationErrCodeMisc, res.ErrorCode)
2035+
require.Equal(
2036+
t,
2037+
"transaction gas limit too high (cap: 16777216, tx: 16777226)",
2038+
res.ErrorMessage,
2039+
)
2040+
require.Nil(t, res.DeployedContractAddress)
2041+
// we strip away first few bytes because they contain deploy code
2042+
require.Empty(t, []byte(res.ReturnedData))
2043+
})
2044+
})
19182045
}
19192046

19202047
func TestDryRun(t *testing.T) {
@@ -1977,7 +2104,8 @@ func TestDryRun(t *testing.T) {
19772104
) {
19782105
data := testContract.MakeCallData(t, "store", big.NewInt(1337))
19792106

1980-
limit := uint64(math.MaxUint64 - 1)
2107+
// EVM.dryRun must not be limited by the `gethParams.MaxTxGas`
2108+
limit := gethParams.MaxTxGas + 1_000
19812109
tx := gethTypes.NewTransaction(
19822110
0,
19832111
testContract.DeployedAt.ToCommon(),
@@ -2572,6 +2700,22 @@ func TestDryCall(t *testing.T) {
25722700
require.Equal(t, types.ExecutionErrCodeOutOfGas, result.ErrorCode)
25732701
require.Equal(t, types.StatusFailed, result.Status)
25742702
require.Equal(t, result.GasConsumed, limit)
2703+
2704+
// EVM.dryCall must not be limited to `gethParams.MaxTxGas`
2705+
limit = gethParams.MaxTxGas + 1_000
2706+
tx = gethTypes.NewTransaction(
2707+
0,
2708+
testContract.DeployedAt.ToCommon(),
2709+
big.NewInt(0),
2710+
limit,
2711+
big.NewInt(0),
2712+
data,
2713+
)
2714+
result, _ = dryCall(t, tx, ctx, vm, snapshot)
2715+
require.Equal(t, types.ErrCodeNoError, result.ErrorCode)
2716+
require.Equal(t, types.StatusSuccessful, result.Status)
2717+
require.Greater(t, result.GasConsumed, uint64(0))
2718+
require.Less(t, result.GasConsumed, limit)
25752719
})
25762720
})
25772721

fvm/evm/handler/handler_test.go

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

33
import (
44
"fmt"
5-
"math"
65
"math/big"
76
"testing"
87

@@ -599,7 +598,7 @@ func TestHandler_COA(t *testing.T) {
599598
require.Equal(t, bal, foa.Balance())
600599

601600
testContract := testutils.GetStorageTestContract(t)
602-
result := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.NewBalanceFromUFix64(0))
601+
result := foa.Deploy(testContract.ByteCode, types.GasLimit(gethParams.MaxTxGas), types.NewBalanceFromUFix64(0))
603602
require.NotNil(t, result.DeployedContractAddress)
604603
addr := *result.DeployedContractAddress
605604
// skip first few bytes as they are deploy codes
@@ -610,13 +609,13 @@ func TestHandler_COA(t *testing.T) {
610609
_ = foa.Call(
611610
addr,
612611
testContract.MakeCallData(t, "store", num),
613-
math.MaxUint64,
612+
types.GasLimit(gethParams.MaxTxGas),
614613
types.NewBalanceFromUFix64(0))
615614

616615
res := foa.Call(
617616
addr,
618617
testContract.MakeCallData(t, "retrieve"),
619-
math.MaxUint64,
618+
types.GasLimit(gethParams.MaxTxGas),
620619
types.NewBalanceFromUFix64(0))
621620

622621
require.Equal(t, num, res.ReturnedData.AsBigInt())
@@ -645,7 +644,7 @@ func TestHandler_COA(t *testing.T) {
645644

646645
arch := handler.MakePrecompileAddress(1)
647646

648-
ret := foa.Call(arch, precompiles.FlowBlockHeightFuncSig[:], math.MaxUint64, types.NewBalanceFromUFix64(0))
647+
ret := foa.Call(arch, precompiles.FlowBlockHeightFuncSig[:], types.GasLimit(gethParams.MaxTxGas), types.NewBalanceFromUFix64(0))
649648
require.Equal(t, big.NewInt(int64(blockHeight)), new(big.Int).SetBytes(ret.ReturnedData))
650649

651650
events := backend.Events()
@@ -697,7 +696,7 @@ func TestHandler_COA(t *testing.T) {
697696
foa.Deposit(vault)
698697

699698
testContract := testutils.GetStorageTestContract(t)
700-
result := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.EmptyBalance)
699+
result := foa.Deploy(testContract.ByteCode, types.GasLimit(gethParams.MaxTxGas), types.EmptyBalance)
701700
require.NotNil(t, result.DeployedContractAddress)
702701
addr := *result.DeployedContractAddress
703702
require.Equal(t, types.StatusSuccessful, result.Status)
@@ -706,7 +705,7 @@ func TestHandler_COA(t *testing.T) {
706705
ret := foa.Call(
707706
addr,
708707
testContract.MakeCallData(t, "random"),
709-
math.MaxUint64,
708+
types.GasLimit(gethParams.MaxTxGas),
710709
types.EmptyBalance)
711710

712711
require.Equal(t, random.Bytes(), []byte(ret.ReturnedData))

fvm/evm/offchain/query/view.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,15 +147,20 @@ func (v *View) DryCall(
147147
return nil, err
148148
}
149149

150-
res, err := bv.DirectCall(
151-
&types.DirectCall{
152-
From: types.NewAddress(from),
153-
To: types.NewAddress(to),
154-
Data: data,
155-
Value: value,
156-
GasLimit: gasLimit,
157-
},
158-
)
150+
call := &types.DirectCall{
151+
From: types.NewAddress(from),
152+
To: types.NewAddress(to),
153+
Data: data,
154+
Value: value,
155+
GasLimit: gasLimit,
156+
}
157+
// We deliberately skip the EIP-7825 tx gas limit cap, because
158+
// `DryCall` is used only for `eth_estimateGas` & `eth_call`
159+
// JSON-RPC endpoints, for which the tx gas limit cap does
160+
// not apply. These endpoints don't mutate the state, they
161+
// simply read the state.
162+
call.SkipTxGasLimitCheck()
163+
res, err := bv.DirectCall(call)
159164
if err != nil {
160165
return nil, err
161166
}

0 commit comments

Comments
 (0)