From a14648b034bc52fc5aed7869ee70bff324c89db2 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 10 Oct 2025 11:40:46 +0300 Subject: [PATCH 1/5] Enable EVM Fusaka hard-fork --- fvm/evm/emulator/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index baee338825f..af7c7b09a3b 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -111,6 +111,8 @@ func MakeChainConfig(chainID *big.Int) *gethParams.ChainConfig { chainConfig.PragueTime = &MainnetPragueActivation } + chainConfig.OsakaTime = &zero + return chainConfig } From 2990ec583cd4e2db1be526b8051d1a1451597f11 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Fri, 10 Oct 2025 12:46:01 +0300 Subject: [PATCH 2/5] Validate gas limit of COA interactions according to MaxTxGas --- fvm/evm/emulator/emulator.go | 11 ++ fvm/evm/evm_test.go | 321 ++++++++++++++++++++++++++++++++++ fvm/evm/testutils/contract.go | 4 +- 3 files changed, 334 insertions(+), 2 deletions(-) diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index 5be27dd820a..1f31b7ef405 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -2,15 +2,18 @@ package emulator import ( "errors" + "fmt" "math/big" gethCommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" gethCore "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/tracing" gethTracing "github.com/ethereum/go-ethereum/core/tracing" gethTypes "github.com/ethereum/go-ethereum/core/types" gethVM "github.com/ethereum/go-ethereum/core/vm" gethCrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" gethParams "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" "github.com/onflow/atree" @@ -111,6 +114,14 @@ type BlockView struct { // DirectCall executes a direct call func (bl *BlockView) DirectCall(call *types.DirectCall) (res *types.Result, err error) { + if call.GasLimit > gethParams.MaxTxGas { + res := &types.Result{ + TxType: call.Type, + TxHash: call.Hash(), + ValidationError: fmt.Errorf("%w (cap: %d, tx: %d)", core.ErrGasLimitTooHigh, params.MaxTxGas, call.GasLimit), + } + return res, nil + } // construct a new procedure proc, err := bl.newProcedure() if err != nil { diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index 9e64b4fd17d..49a1da278d8 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -577,6 +577,193 @@ func TestEVMRun(t *testing.T) { }, ) }) + + t.Run("testing EVM.run failed with gas limit validation error", func(t *testing.T) { + t.Parallel() + RunWithNewEnvironment(t, + chain, func( + ctx fvm.Context, + vm fvm.VM, + snapshot snapshot.SnapshotTree, + testContract *TestContract, + testAccount *EOATestAccount, + ) { + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + code := []byte(fmt.Sprintf( + ` + import EVM from %s + + transaction(tx: [UInt8], coinbaseBytes: [UInt8; 20]){ + prepare(account: &Account) { + let coinbase = EVM.EVMAddress(bytes: coinbaseBytes) + let res = EVM.run(tx: tx, coinbase: coinbase) + + assert(res.status == EVM.Status.invalid, message: "unexpected status") + assert(res.errorCode == 100, message: "unexpected error code: \(res.errorCode)") + assert(res.errorMessage == "transaction gas limit too high (cap: 16777216, tx: 16777220)") + } + } + `, + sc.EVMContract.Address.HexWithPrefix(), + )) + + coinbaseAddr := types.Address{1, 2, 3} + coinbaseBalance := getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr) + require.Zero(t, types.BalanceToBigInt(coinbaseBalance).Uint64()) + + num := int64(12) + innerTxBytes := testAccount.PrepareSignAndEncodeTx(t, + testContract.DeployedAt.ToCommon(), + testContract.MakeCallData(t, "store", big.NewInt(num)), + big.NewInt(0), + uint64(16_777_220), // max is 16,777,216 + big.NewInt(1), + ) + + innerTx := cadence.NewArray( + unittest.BytesToCdcUInt8(innerTxBytes), + ).WithType(stdlib.EVMTransactionBytesCadenceType) + + coinbase := cadence.NewArray( + unittest.BytesToCdcUInt8(coinbaseAddr.Bytes()), + ).WithType(stdlib.EVMAddressBytesCadenceType) + + txBody, err := flow.NewTransactionBodyBuilder(). + SetScript(code). + SetPayer(sc.FlowServiceAccount.Address). + AddAuthorizer(sc.FlowServiceAccount.Address). + AddArgument(json.MustEncode(innerTx)). + AddArgument(json.MustEncode(coinbase)). + Build() + require.NoError(t, err) + + tx := fvm.Transaction(txBody, 0) + + state, output, err := vm.Run( + ctx, + tx, + snapshot, + ) + require.NoError(t, err) + require.NoError(t, output.Err) + require.NotEmpty(t, state.WriteSet) + snapshot = snapshot.Append(state) + + // assert no events were produced from an invalid EVM transaction + require.Len(t, output.Events, 0) + }) + }) + + t.Run("testing EVM.run with max gas limit cap", func(t *testing.T) { + + t.Parallel() + RunWithNewEnvironment(t, + chain, func( + ctx fvm.Context, + vm fvm.VM, + snapshot snapshot.SnapshotTree, + testContract *TestContract, + testAccount *EOATestAccount, + ) { + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + code := []byte(fmt.Sprintf( + ` + import EVM from %s + + transaction(tx: [UInt8], coinbaseBytes: [UInt8; 20]){ + prepare(account: &Account) { + let coinbase = EVM.EVMAddress(bytes: coinbaseBytes) + let res = EVM.run(tx: tx, coinbase: coinbase) + + assert(res.status == EVM.Status.successful, message: "unexpected status") + assert(res.errorCode == 0, message: "unexpected error code: \(res.errorCode)") + } + } + `, + sc.EVMContract.Address.HexWithPrefix(), + )) + + coinbaseAddr := types.Address{1, 2, 3} + coinbaseBalance := getEVMAccountBalance(t, ctx, vm, snapshot, coinbaseAddr) + require.Zero(t, types.BalanceToBigInt(coinbaseBalance).Uint64()) + + num := int64(12) + innerTxBytes := testAccount.PrepareSignAndEncodeTx(t, + testContract.DeployedAt.ToCommon(), + testContract.MakeCallData(t, "store", big.NewInt(num)), + big.NewInt(0), + uint64(16_777_216), + big.NewInt(1), + ) + + innerTx := cadence.NewArray( + unittest.BytesToCdcUInt8(innerTxBytes), + ).WithType(stdlib.EVMTransactionBytesCadenceType) + + coinbase := cadence.NewArray( + unittest.BytesToCdcUInt8(coinbaseAddr.Bytes()), + ).WithType(stdlib.EVMAddressBytesCadenceType) + + txBody, err := flow.NewTransactionBodyBuilder(). + SetScript(code). + SetPayer(sc.FlowServiceAccount.Address). + AddAuthorizer(sc.FlowServiceAccount.Address). + AddArgument(json.MustEncode(innerTx)). + AddArgument(json.MustEncode(coinbase)). + Build() + require.NoError(t, err) + + tx := fvm.Transaction(txBody, 0) + + state, output, err := vm.Run( + ctx, + tx, + snapshot, + ) + require.NoError(t, err) + require.NoError(t, output.Err) + require.NotEmpty(t, state.WriteSet) + snapshot = snapshot.Append(state) + + // assert event fields are correct + require.Len(t, output.Events, 2) + txEvent := output.Events[0] + txEventPayload := TxEventToPayload(t, txEvent, sc.EVMContract.Address) + require.NoError(t, err) + + // fee transfer event + feeTransferEvent := output.Events[1] + feeTranferEventPayload := TxEventToPayload(t, feeTransferEvent, sc.EVMContract.Address) + require.NoError(t, err) + require.Equal(t, uint16(types.ErrCodeNoError), feeTranferEventPayload.ErrorCode) + require.Equal(t, uint16(1), feeTranferEventPayload.Index) + require.Equal(t, uint64(21000), feeTranferEventPayload.GasConsumed) + + // commit block + blockEventPayload, snapshot := callEVMHeartBeat(t, + ctx, + vm, + snapshot) + + require.NotEmpty(t, blockEventPayload.Hash) + require.Equal(t, uint64(64785), blockEventPayload.TotalGasUsed) + require.NotEmpty(t, blockEventPayload.Hash) + + txHashes := types.TransactionHashes{txEventPayload.Hash, feeTranferEventPayload.Hash} + require.Equal(t, + txHashes.RootHash(), + blockEventPayload.TransactionHashRoot, + ) + require.NotEmpty(t, blockEventPayload.ReceiptRoot) + + require.Equal(t, innerTxBytes, txEventPayload.Payload) + require.Equal(t, uint16(types.ErrCodeNoError), txEventPayload.ErrorCode) + require.Equal(t, uint16(0), txEventPayload.Index) + require.Equal(t, blockEventPayload.Height, txEventPayload.BlockHeight) + require.Equal(t, blockEventPayload.TotalGasUsed-feeTranferEventPayload.GasConsumed, txEventPayload.GasConsumed) + require.Empty(t, txEventPayload.ContractAddress) + }) + }) } func TestEVMBatchRun(t *testing.T) { @@ -1731,6 +1918,140 @@ func TestCadenceOwnedAccountFunctionalities(t *testing.T) { assert.Len(t, state.UpdatedRegisterIDs(), 0) }) }) + + t.Run("test coa deploy with max gas limit cap", func(t *testing.T) { + RunWithNewEnvironment(t, + chain, func( + ctx fvm.Context, + vm fvm.VM, + snapshot snapshot.SnapshotTree, + testContract *TestContract, + testAccount *EOATestAccount, + ) { + code := []byte(fmt.Sprintf( + ` + import EVM from %s + import FlowToken from %s + + access(all) + fun main(code: [UInt8]): EVM.Result { + let admin = getAuthAccount(%s) + .storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin)! + let minter <- admin.createNewMinter(allowedAmount: 2.34) + let vault <- minter.mintTokens(amount: 2.34) + destroy minter + + let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount() + cadenceOwnedAccount.deposit(from: <-vault) + + let res = cadenceOwnedAccount.deploy( + code: code, + gasLimit: 16_777_216, + value: EVM.Balance(attoflow: 1230000000000000000) + ) + destroy cadenceOwnedAccount + return res + } + `, + sc.EVMContract.Address.HexWithPrefix(), + sc.FlowToken.Address.HexWithPrefix(), + sc.FlowServiceAccount.Address.HexWithPrefix(), + )) + + script := fvm.Script(code). + WithArguments(json.MustEncode( + cadence.NewArray( + unittest.BytesToCdcUInt8(testContract.ByteCode), + ).WithType(cadence.NewVariableSizedArrayType(cadence.UInt8Type)), + )) + + _, output, err := vm.Run( + ctx, + script, + snapshot, + ) + require.NoError(t, err) + require.NoError(t, output.Err) + + res, err := impl.ResultSummaryFromEVMResultValue(output.Value) + require.NoError(t, err) + require.Equal(t, types.StatusSuccessful, res.Status) + require.Equal(t, types.ErrCodeNoError, res.ErrorCode) + require.Empty(t, res.ErrorMessage) + require.NotNil(t, res.DeployedContractAddress) + // we strip away first few bytes because they contain deploy code + require.Equal(t, testContract.ByteCode[17:], []byte(res.ReturnedData)) + }) + }) + + t.Run("test coa deploy with bigger than max gas limit cap", func(t *testing.T) { + RunWithNewEnvironment(t, + chain, func( + ctx fvm.Context, + vm fvm.VM, + snapshot snapshot.SnapshotTree, + testContract *TestContract, + testAccount *EOATestAccount, + ) { + code := []byte(fmt.Sprintf( + ` + import EVM from %s + import FlowToken from %s + + access(all) + fun main(code: [UInt8]): EVM.Result { + let admin = getAuthAccount(%s) + .storage.borrow<&FlowToken.Administrator>(from: /storage/flowTokenAdmin)! + let minter <- admin.createNewMinter(allowedAmount: 2.34) + let vault <- minter.mintTokens(amount: 2.34) + destroy minter + + let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount() + cadenceOwnedAccount.deposit(from: <-vault) + + let res = cadenceOwnedAccount.deploy( + code: code, + gasLimit: 16_777_226, + value: EVM.Balance(attoflow: 1230000000000000000) + ) + destroy cadenceOwnedAccount + return res + } + `, + sc.EVMContract.Address.HexWithPrefix(), + sc.FlowToken.Address.HexWithPrefix(), + sc.FlowServiceAccount.Address.HexWithPrefix(), + )) + + script := fvm.Script(code). + WithArguments(json.MustEncode( + cadence.NewArray( + unittest.BytesToCdcUInt8(testContract.ByteCode), + ).WithType(cadence.NewVariableSizedArrayType(cadence.UInt8Type)), + )) + + _, output, err := vm.Run( + ctx, + script, + snapshot, + ) + require.NoError(t, err) + require.NoError(t, output.Err) + + res, err := impl.ResultSummaryFromEVMResultValue(output.Value) + require.NoError(t, err) + require.Equal(t, types.StatusInvalid, res.Status) + require.Equal(t, types.ValidationErrCodeMisc, res.ErrorCode) + require.Equal( + t, + "transaction gas limit too high (cap: 16777216, tx: 16777226)", + res.ErrorMessage, + ) + require.Nil(t, res.DeployedContractAddress) + // we strip away first few bytes because they contain deploy code + require.Empty(t, []byte(res.ReturnedData)) + }) + }) } func TestDryRun(t *testing.T) { diff --git a/fvm/evm/testutils/contract.go b/fvm/evm/testutils/contract.go index d9286e62688..45a9f36c7c3 100644 --- a/fvm/evm/testutils/contract.go +++ b/fvm/evm/testutils/contract.go @@ -1,12 +1,12 @@ package testutils import ( - "math" "math/big" "strings" "testing" gethABI "github.com/ethereum/go-ethereum/accounts/abi" + gethParams "github.com/ethereum/go-ethereum/params" "github.com/onflow/atree" "github.com/stretchr/testify/require" @@ -110,7 +110,7 @@ func DeployContract(t testing.TB, caller types.Address, tc *TestContract, led at types.NewDeployCall( caller, tc.ByteCode, - math.MaxUint64, + gethParams.MaxTxGas, big.NewInt(0), nonce+1, ), From 5da96b9d4fed716d5f63a2a7424734cd83eb0f32 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 13 Oct 2025 10:19:02 +0300 Subject: [PATCH 3/5] fixup! Enable EVM Fusaka hard-fork --- fvm/evm/emulator/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index af7c7b09a3b..f3fa304a65e 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -111,7 +111,9 @@ func MakeChainConfig(chainID *big.Int) *gethParams.ChainConfig { chainConfig.PragueTime = &MainnetPragueActivation } - chainConfig.OsakaTime = &zero + if chainID.Cmp(types.FlowEVMPreviewNetChainID) == 0 { + chainConfig.OsakaTime = &zero + } return chainConfig } From c8123ae779e87aad8ff8f14679d8c9418a01af4a Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Mon, 13 Oct 2025 13:04:05 +0300 Subject: [PATCH 4/5] Add methods on DirectCall to control whether to enable/disable max tx gas limit check --- fvm/evm/emulator/emulator.go | 2 +- fvm/evm/emulator/emulator_test.go | 11 +++++------ fvm/evm/handler/handler_test.go | 26 +++++++++++++++++++------- fvm/evm/offchain/query/view.go | 18 +++++++++--------- fvm/evm/types/call.go | 10 ++++++++++ 5 files changed, 44 insertions(+), 23 deletions(-) diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index 1f31b7ef405..f68089c825f 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -114,7 +114,7 @@ type BlockView struct { // DirectCall executes a direct call func (bl *BlockView) DirectCall(call *types.DirectCall) (res *types.Result, err error) { - if call.GasLimit > gethParams.MaxTxGas { + if call.TxGasLimitEnabled() && call.GasLimit > gethParams.MaxTxGas { res := &types.Result{ TxType: call.Type, TxHash: call.Hash(), diff --git a/fvm/evm/emulator/emulator_test.go b/fvm/evm/emulator/emulator_test.go index 8c4634c72ff..ce919fad03d 100644 --- a/fvm/evm/emulator/emulator_test.go +++ b/fvm/evm/emulator/emulator_test.go @@ -3,7 +3,6 @@ package emulator_test import ( "encoding/hex" "fmt" - "math" "math/big" "strings" "testing" @@ -211,7 +210,7 @@ func TestContractInteraction(t *testing.T) { call := types.NewDeployCall( testAccount, testContract.ByteCode, - math.MaxUint64, + gethParams.MaxTxGas, amountToBeTransfered, testAccountNonce) res, err := blk.DirectCall(call) @@ -599,7 +598,7 @@ func TestDeployAtFunctionality(t *testing.T) { testAccount, target, testContract.ByteCode, - math.MaxUint64, + gethParams.MaxTxGas, amountToBeTransfered, 0, ), @@ -629,7 +628,7 @@ func TestDeployAtFunctionality(t *testing.T) { testAccount, target, testContract.ByteCode, - math.MaxUint64, + gethParams.MaxTxGas, amountToBeTransfered, 0), ) @@ -687,7 +686,7 @@ func TestSelfdestruct(t *testing.T) { types.NewDeployCall( testAddress, testContract.ByteCode, - math.MaxUint64, + gethParams.MaxTxGas, deployBalance, 0), ) @@ -768,7 +767,7 @@ func TestFactoryPatterns(t *testing.T) { types.NewDeployCall( factoryDeployer, factoryContract.ByteCode, - math.MaxUint64, + gethParams.MaxTxGas, factoryBalance, 0), ) diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 8dabc3a1646..047d31d5531 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -2,7 +2,6 @@ package handler_test import ( "fmt" - "math" "math/big" "testing" @@ -599,7 +598,11 @@ func TestHandler_COA(t *testing.T) { require.Equal(t, bal, foa.Balance()) testContract := testutils.GetStorageTestContract(t) - result := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.NewBalanceFromUFix64(0)) + result := foa.Deploy( + testContract.ByteCode, + types.GasLimit(gethParams.MaxTxGas), + types.NewBalanceFromUFix64(0), + ) require.NotNil(t, result.DeployedContractAddress) addr := *result.DeployedContractAddress // skip first few bytes as they are deploy codes @@ -610,13 +613,13 @@ func TestHandler_COA(t *testing.T) { _ = foa.Call( addr, testContract.MakeCallData(t, "store", num), - math.MaxUint64, + types.GasLimit(gethParams.MaxTxGas), types.NewBalanceFromUFix64(0)) res := foa.Call( addr, testContract.MakeCallData(t, "retrieve"), - math.MaxUint64, + types.GasLimit(gethParams.MaxTxGas), types.NewBalanceFromUFix64(0)) require.Equal(t, num, res.ReturnedData.AsBigInt()) @@ -645,7 +648,12 @@ func TestHandler_COA(t *testing.T) { arch := handler.MakePrecompileAddress(1) - ret := foa.Call(arch, precompiles.FlowBlockHeightFuncSig[:], math.MaxUint64, types.NewBalanceFromUFix64(0)) + ret := foa.Call( + arch, + precompiles.FlowBlockHeightFuncSig[:], + types.GasLimit(gethParams.MaxTxGas), + types.NewBalanceFromUFix64(0), + ) require.Equal(t, big.NewInt(int64(blockHeight)), new(big.Int).SetBytes(ret.ReturnedData)) events := backend.Events() @@ -697,7 +705,11 @@ func TestHandler_COA(t *testing.T) { foa.Deposit(vault) testContract := testutils.GetStorageTestContract(t) - result := foa.Deploy(testContract.ByteCode, math.MaxUint64, types.EmptyBalance) + result := foa.Deploy( + testContract.ByteCode, + types.GasLimit(gethParams.MaxTxGas), + types.EmptyBalance, + ) require.NotNil(t, result.DeployedContractAddress) addr := *result.DeployedContractAddress require.Equal(t, types.StatusSuccessful, result.Status) @@ -706,7 +718,7 @@ func TestHandler_COA(t *testing.T) { ret := foa.Call( addr, testContract.MakeCallData(t, "random"), - math.MaxUint64, + types.GasLimit(gethParams.MaxTxGas), types.EmptyBalance) require.Equal(t, random.Bytes(), []byte(ret.ReturnedData)) diff --git a/fvm/evm/offchain/query/view.go b/fvm/evm/offchain/query/view.go index 24fff307f6f..bf982a018bd 100644 --- a/fvm/evm/offchain/query/view.go +++ b/fvm/evm/offchain/query/view.go @@ -147,15 +147,15 @@ func (v *View) DryCall( return nil, err } - res, err := bv.DirectCall( - &types.DirectCall{ - From: types.NewAddress(from), - To: types.NewAddress(to), - Data: data, - Value: value, - GasLimit: gasLimit, - }, - ) + call := &types.DirectCall{ + From: types.NewAddress(from), + To: types.NewAddress(to), + Data: data, + Value: value, + GasLimit: gasLimit, + } + call.SkipTxGasLimitCheck() + res, err := bv.DirectCall(call) if err != nil { return nil, err } diff --git a/fvm/evm/types/call.go b/fvm/evm/types/call.go index 973c95894ac..9fde11a43a0 100644 --- a/fvm/evm/types/call.go +++ b/fvm/evm/types/call.go @@ -54,6 +54,8 @@ type DirectCall struct { Value *big.Int GasLimit uint64 Nonce uint64 + + skipTxGasLimitCheck bool } // DirectCallFromEncoded constructs a DirectCall from encoded data @@ -132,6 +134,14 @@ func (dc *DirectCall) EmptyToField() bool { return dc.To == EmptyAddress } +func (dc *DirectCall) SkipTxGasLimitCheck() { + dc.skipTxGasLimitCheck = true +} + +func (dc *DirectCall) TxGasLimitEnabled() bool { + return dc.skipTxGasLimitCheck == false +} + func (dc *DirectCall) to() *gethCommon.Address { if !dc.EmptyToField() { ct := dc.To.ToCommon() From 556b19ce0f343af88af1ffdd72173c800c9334c3 Mon Sep 17 00:00:00 2001 From: Ardit Marku Date: Tue, 14 Oct 2025 10:59:13 +0300 Subject: [PATCH 5/5] Apply MaxTxGas limit check only if Osaka is activated --- fvm/evm/emulator/config.go | 9 +++++++++ fvm/evm/emulator/emulator.go | 24 ++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/fvm/evm/emulator/config.go b/fvm/evm/emulator/config.go index f3fa304a65e..545ea8f0a35 100644 --- a/fvm/evm/emulator/config.go +++ b/fvm/evm/emulator/config.go @@ -21,6 +21,10 @@ var ( PreviewnetPragueActivation = uint64(0) // already on Prague for PreviewNet TestnetPragueActivation = uint64(1746723600) // Thu May 08 2025 17:00:00 GMT+0000 (10am PDT) MainnetPragueActivation = uint64(1747328400) // Thu May 15 2025 17:00:00 GMT+0000 (10am PDT) + + PreviewnetFusakaActivation = uint64(0) // already on Fusaka for PreviewNet + TestnetFusakaActivation = uint64(1761695999) // Tuesday, October 28, 2025 11:59:59 PM + MainnetFusakaActivation = uint64(1764806399) // Wednesday, December 3, 2025 11:59:59 PM ) // Config aggregates all the configuration (chain, evm, block, tx, ...) @@ -100,6 +104,7 @@ func MakeChainConfig(chainID *big.Int) *gethParams.ChainConfig { ShanghaiTime: &zero, // already on Shanghai CancunTime: &zero, // already on Cancun PragueTime: nil, // this is conditionally set below + OsakaTime: nil, // this is conditionally set below VerkleTime: nil, // not on Verkle } @@ -113,6 +118,10 @@ func MakeChainConfig(chainID *big.Int) *gethParams.ChainConfig { if chainID.Cmp(types.FlowEVMPreviewNetChainID) == 0 { chainConfig.OsakaTime = &zero + } else if chainID.Cmp(types.FlowEVMTestNetChainID) == 0 { + chainConfig.OsakaTime = &TestnetFusakaActivation + } else if chainID.Cmp(types.FlowEVMMainNetChainID) == 0 { + chainConfig.OsakaTime = &MainnetFusakaActivation } return chainConfig diff --git a/fvm/evm/emulator/emulator.go b/fvm/evm/emulator/emulator.go index f68089c825f..3bdcc683673 100644 --- a/fvm/evm/emulator/emulator.go +++ b/fvm/evm/emulator/emulator.go @@ -114,20 +114,28 @@ type BlockView struct { // DirectCall executes a direct call func (bl *BlockView) DirectCall(call *types.DirectCall) (res *types.Result, err error) { - if call.TxGasLimitEnabled() && call.GasLimit > gethParams.MaxTxGas { - res := &types.Result{ - TxType: call.Type, - TxHash: call.Hash(), - ValidationError: fmt.Errorf("%w (cap: %d, tx: %d)", core.ErrGasLimitTooHigh, params.MaxTxGas, call.GasLimit), - } - return res, nil - } // construct a new procedure proc, err := bl.newProcedure() if err != nil { return nil, err } + if (proc.config.BlockContext.Time >= *bl.config.ChainConfig.OsakaTime) && call.TxGasLimitEnabled() && (call.GasLimit > gethParams.MaxTxGas) { + res := &types.Result{ + TxType: call.Type, + TxHash: call.Hash(), + } + res.SetValidationError( + fmt.Errorf( + "%w (cap: %d, tx: %d)", + core.ErrGasLimitTooHigh, + params.MaxTxGas, + call.GasLimit, + ), + ) + return res, nil + } + // Set the nonce for the call (needed for some operations like deployment) call.Nonce = proc.state.GetNonce(call.From.ToCommon())