-
Notifications
You must be signed in to change notification settings - Fork 210
[Flow EVM] Upgrade to Ethereum Glamsterdam hard-fork #8554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
fc1a868
940cdaf
63b0ab3
88d4278
ec96ee9
37ce6b8
f0cb810
4c30ba0
636fc90
76373bb
9d07806
e4f54af
94c2edc
99db4ec
f8b7463
474f6dd
c7689aa
b79f49f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ import ( | |
| gethVM "github.com/ethereum/go-ethereum/core/vm" | ||
| "github.com/ethereum/go-ethereum/eth/tracers" | ||
| gethParams "github.com/ethereum/go-ethereum/params" | ||
| "github.com/holiman/uint256" | ||
|
|
||
| "github.com/onflow/flow-go/fvm/evm/types" | ||
| ) | ||
|
|
@@ -25,6 +26,10 @@ var ( | |
| PreviewnetOsakaActivation = uint64(0) // already on Osaka for PreviewNet | ||
| TestnetOsakaActivation = uint64(1763575200) // Wednesday, November 19, 2025 18:00:00 GMT+0000 | ||
| MainnetOsakaActivation = uint64(1764784800) // Wednesday, December 03, 2025 18:00:00 GMT+0000 | ||
|
|
||
| PreviewnetAmsterdamActivation = uint64(0) // already on Amsterdam for PreviewNet | ||
| TestnetAmsterdamActivation = uint64(1798740000) // Thursday, December 31, 2026 at 18:00:00 GMT+0000 | ||
| MainnetAmsterdamActivation = uint64(1798740000) // Thursday, December 31, 2026 at 18:00:00 GMT+0000 | ||
| ) | ||
|
|
||
| // Config aggregates all the configuration (chain, evm, block, tx, ...) | ||
|
|
@@ -102,22 +107,26 @@ func MakeChainConfig(chainID *big.Int) *gethParams.ChainConfig { | |
| MuirGlacierBlock: bigZero, // already on MuirGlacier | ||
|
|
||
| // Fork scheduling based on timestamps | ||
| 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 | ||
| ShanghaiTime: &zero, // already on Shanghai | ||
| CancunTime: &zero, // already on Cancun | ||
| PragueTime: nil, // this is conditionally set below | ||
| OsakaTime: nil, // this is conditionally set below | ||
| AmsterdamTime: nil, // this is conditionally set below | ||
| UBTTime: nil, // not on Verkle (a.k.a UBT) | ||
| } | ||
|
|
||
| if chainID.Cmp(types.FlowEVMPreviewNetChainID) == 0 { | ||
| chainConfig.PragueTime = &PreviewnetPragueActivation | ||
| chainConfig.OsakaTime = &PreviewnetOsakaActivation | ||
| chainConfig.AmsterdamTime = &PreviewnetAmsterdamActivation | ||
| } else if chainID.Cmp(types.FlowEVMTestNetChainID) == 0 { | ||
| chainConfig.PragueTime = &TestnetPragueActivation | ||
| chainConfig.OsakaTime = &TestnetOsakaActivation | ||
| chainConfig.AmsterdamTime = &TestnetAmsterdamActivation | ||
| } else if chainID.Cmp(types.FlowEVMMainNetChainID) == 0 { | ||
| chainConfig.PragueTime = &MainnetPragueActivation | ||
| chainConfig.OsakaTime = &MainnetOsakaActivation | ||
| chainConfig.AmsterdamTime = &MainnetAmsterdamActivation | ||
| } | ||
|
|
||
| return chainConfig | ||
|
|
@@ -135,8 +144,7 @@ func defaultConfig() *Config { | |
| NoBaseFee: true, | ||
| }, | ||
| TxContext: &gethVM.TxContext{ | ||
| GasPrice: new(big.Int), | ||
| BlobFeeCap: new(big.Int), | ||
| GasPrice: new(uint256.Int), | ||
| }, | ||
| BlockContext: &gethVM.BlockContext{ | ||
| CanTransfer: gethCore.CanTransfer, | ||
|
|
@@ -188,7 +196,7 @@ func WithOrigin(origin gethCommon.Address) Option { | |
| // WithGasPrice sets the gas price for the transaction (usually the one sets by the sender) | ||
| func WithGasPrice(gasPrice *big.Int) Option { | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return func(c *Config) *Config { | ||
| c.TxContext.GasPrice = gasPrice | ||
| c.TxContext.GasPrice = uint256.MustFromBig(gasPrice) | ||
| return c | ||
|
m-Peter marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
@@ -264,6 +272,14 @@ func WithRandom(rand *gethCommon.Hash) Option { | |
| } | ||
| } | ||
|
|
||
| // WithSlotNum sets the slot number in the block context | ||
| func WithSlotNum(slotNum uint64) Option { | ||
| return func(c *Config) *Config { | ||
| c.BlockContext.SlotNum = slotNum | ||
| return c | ||
| } | ||
| } | ||
|
|
||
| // WithTransactionTracer sets a transaction tracer | ||
| func WithTransactionTracer(tracer *tracers.Tracer) Option { | ||
| return func(c *Config) *Config { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,7 @@ func newConfig(ctx types.BlockContext) *Config { | |
| WithExtraPrecompiledContracts(ctx.ExtraPrecompiledContracts), | ||
| WithGetBlockHashFunction(ctx.GetHashFunc), | ||
| WithRandom(&ctx.Random), | ||
| WithSlotNum(ctx.SlotNum), | ||
| WithTransactionTracer(ctx.Tracer), | ||
| WithBlockTotalGasUsedSoFar(ctx.TotalGasUsedSoFar), | ||
| WithBlockTxCountSoFar(ctx.TxCountSoFar), | ||
|
|
@@ -162,6 +163,9 @@ func (bl *BlockView) DirectCall(call *types.DirectCall) (res *types.Result, err | |
| case types.WithdrawCallSubType: | ||
| return proc.withdrawFrom(call) | ||
| case types.DeployCallSubType: | ||
| // this is effectively deploying only COAs, with the `to` field | ||
| // being generated by the COA address allocator and set as the | ||
| // deployment target address | ||
| if !call.EmptyToField() { | ||
| return proc.deployAt(call) | ||
| } | ||
|
|
@@ -307,23 +311,55 @@ func (bl *BlockView) DryRunTransaction( | |
| return nil, err | ||
| } | ||
|
|
||
| // convert tx into message | ||
| msg, err := gethCore.TransactionToMessage( | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can no longer use |
||
| tx, | ||
| GetSigner(bl.config), | ||
| proc.config.BlockContext.BaseFee, | ||
| ) | ||
|
|
||
| // we can ignore invalid signature errors since we don't expect signed transactions | ||
| if !errors.Is(err, gethTypes.ErrInvalidSig) { | ||
| return nil, err | ||
| value, overflow := uint256.FromBig(tx.Value()) | ||
| if overflow { | ||
| return nil, fmt.Errorf("value exceeds 256 bits: address %v", from.Hex()) | ||
| } | ||
| gasPrice, overflow := uint256.FromBig(tx.GasPrice()) | ||
| if overflow { | ||
| return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", gethCore.ErrFeeCapVeryHigh, | ||
| from.Hex(), tx.GasPrice().BitLen()) | ||
| } | ||
| txGasFeeCap := tx.GasFeeCap() | ||
| gasFeeCap, overflow := uint256.FromBig(txGasFeeCap) | ||
| if overflow { | ||
| return nil, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", gethCore.ErrFeeCapVeryHigh, | ||
| from.Hex(), tx.GasFeeCap().BitLen()) | ||
| } | ||
| txGasTipCap := tx.GasTipCap() | ||
| gasTipCap, overflow := uint256.FromBig(txGasTipCap) | ||
| if overflow { | ||
| return nil, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", gethCore.ErrTipVeryHigh, | ||
| from.Hex(), tx.GasTipCap().BitLen()) | ||
| } | ||
|
|
||
| msg := &gethCore.Message{ | ||
| From: from, | ||
| To: tx.To(), | ||
| Value: value, | ||
| Data: tx.Data(), | ||
| Nonce: tx.Nonce(), | ||
| GasLimit: tx.Gas(), | ||
| GasPrice: gasPrice, | ||
| GasTipCap: gasTipCap, | ||
| GasFeeCap: gasFeeCap, | ||
| SetCodeAuthorizations: tx.SetCodeAuthorizations(), | ||
| AccessList: tx.AccessList(), | ||
| SkipNonceChecks: true, | ||
| SkipTransactionChecks: true, | ||
| } | ||
|
|
||
| // If baseFee provided, set gasPrice to effectiveGasPrice. | ||
| baseFee := proc.config.BlockContext.BaseFee | ||
| if baseFee != nil { | ||
| effectiveGasPrice := new(big.Int).Add(baseFee, txGasTipCap) | ||
| if effectiveGasPrice.Cmp(txGasFeeCap) > 0 { | ||
| effectiveGasPrice = txGasFeeCap | ||
| } | ||
| // EffectiveGasPrice is already capped by txGasFeeCap, therefore | ||
| // the overflow check is not required. | ||
| msg.GasPrice = uint256.MustFromBig(effectiveGasPrice) | ||
| } | ||
|
|
||
| // use the from as the signer | ||
| msg.From = from | ||
| // we need to skip nonce/transaction checks for dry run | ||
| msg.SkipNonceChecks = true | ||
| msg.SkipTransactionChecks = true | ||
|
|
||
| // run and return without committing the state changes | ||
| return proc.run(msg, tx.Hash(), tx.Type()) | ||
|
|
@@ -524,14 +560,30 @@ func (proc *procedure) deployAt( | |
| }() | ||
| } | ||
|
|
||
| addr := call.To.ToCommon() | ||
| caller := call.From.ToCommon() | ||
| // pre check 1 - check balance of the source | ||
| if call.Value.Sign() != 0 && | ||
| !proc.evm.Context.CanTransfer(proc.state, call.From.ToCommon(), castedValue) { | ||
| !proc.evm.Context.CanTransfer(proc.state, caller, castedValue) { | ||
| res.SetValidationError(gethCore.ErrInsufficientFundsForTransfer) | ||
| return res, nil | ||
| } | ||
|
|
||
| // increment the nonce for the caller | ||
| nonce := proc.state.GetNonce(caller) | ||
| if nonce+1 < nonce { | ||
| res.SetValidationError(gethVM.ErrNonceUintOverflow) | ||
| return res, nil | ||
| } | ||
| proc.state.SetNonce( | ||
| caller, | ||
| nonce+1, | ||
| gethTracing.NonceChangeContractCreator, | ||
| ) | ||
|
|
||
| addr := call.To.ToCommon() | ||
| // update access list (Berlin) | ||
| proc.state.AddAddressToAccessList(addr) | ||
|
|
||
| // pre check 2 - ensure there's no existing eoa or contract is deployed at the address | ||
| contractHash := proc.state.GetCodeHash(addr) | ||
| if proc.state.GetNonce(addr) != 0 || | ||
|
|
@@ -540,48 +592,49 @@ func (proc *procedure) deployAt( | |
| return res, nil | ||
| } | ||
|
|
||
| callerCommon := call.From.ToCommon() | ||
| // setup caller if doesn't exist | ||
| if !proc.state.Exist(callerCommon) { | ||
| proc.state.CreateAccount(callerCommon) | ||
| // create a new account on the state only if the object was not present. | ||
| // it might be possible the contract code is deployed to a pre-existent | ||
| // account with non-zero balance. | ||
| if !proc.state.Exist(addr) { | ||
| proc.state.CreateAccount(addr) | ||
| } | ||
| // increment the nonce for the caller | ||
| proc.state.SetNonce( | ||
| callerCommon, | ||
| proc.state.GetNonce(callerCommon)+1, | ||
| gethTracing.NonceChangeContractCreator, | ||
| ) | ||
|
|
||
| // setup account | ||
| proc.state.CreateAccount(addr) | ||
| // CreateContract means that regardless of whether the account previously existed | ||
| // in the state trie or not, it _now_ becomes created as a _contract_ account. | ||
| // This is performed _prior_ to executing the initcode, since the initcode | ||
| // acts inside that account. | ||
| proc.state.CreateContract(addr) | ||
| proc.state.SetNonce(addr, 1, gethTracing.NonceChangeNewContract) // (EIP-158) | ||
| rules := proc.config.ChainRules() | ||
| if call.Value.Sign() > 0 { | ||
| proc.evm.Context.Transfer( // transfer value | ||
| proc.state, | ||
| callerCommon, | ||
| caller, | ||
| addr, | ||
| uint256.MustFromBig(call.Value), | ||
| &rules, | ||
| ) | ||
| } | ||
|
|
||
| // run code through interpreter | ||
| // this would check for errors and computes the final bytes to be stored under account | ||
| var err error | ||
| // initialise a new contract and set the code that is to be used by the EVM. | ||
| // the contract is a scoped environment for this execution context only. | ||
| gasBudget := gethVM.NewGasBudget(call.GasLimit) | ||
| contract := gethVM.NewContract( | ||
| callerCommon, | ||
| caller, | ||
| addr, | ||
| castedValue, | ||
| call.GasLimit, | ||
| gasBudget, | ||
| nil, | ||
| ) | ||
|
|
||
| contract.SetCallCode(gethCrypto.Keccak256Hash(call.Data), call.Data) | ||
| // update access list (Berlin) | ||
| proc.state.AddAddressToAccessList(addr) | ||
| // explicitly set the code to a null hash to prevent caching of jump analysis | ||
| // for the initialization code. | ||
| contract.SetCallCode(gethCommon.Hash{}, call.Data) | ||
| contract.IsDeployment = true | ||
|
|
||
| ret, err := proc.evm.Run(contract, nil, false) | ||
| gasCost := uint64(len(ret)) * gethParams.CreateDataGas | ||
| res.GasConsumed = gasCost | ||
| createDataGas := uint64(len(ret)) * gethParams.CreateDataGas | ||
| res.GasConsumed = createDataGas | ||
|
|
||
|
m-Peter marked this conversation as resolved.
|
||
| // handle errors | ||
| if err != nil { | ||
|
|
@@ -593,34 +646,37 @@ func (proc *procedure) deployAt( | |
| return res, nil | ||
| } | ||
|
|
||
| // update gas usage | ||
| if gasCost > call.GasLimit { | ||
| // check whether the max code size has been exceeded | ||
| if err := gethVM.CheckMaxCodeSize(&rules, uint64(len(ret))); err != nil { | ||
| // consume all the remaining gas (Homestead) | ||
| res.GasConsumed = call.GasLimit | ||
| res.VMError = gethVM.ErrCodeStoreOutOfGas | ||
| res.VMError = gethVM.ErrMaxCodeSizeExceeded | ||
| return res, nil | ||
| } | ||
|
|
||
| // check max code size (EIP-158) | ||
| if len(ret) > gethParams.MaxCodeSize { | ||
| // reject code starting with 0xEF (EIP-3541) | ||
| if len(ret) >= 1 && ret[0] == 0xEF { | ||
| // consume all the remaining gas (Homestead) | ||
| res.GasConsumed = call.GasLimit | ||
| res.VMError = gethVM.ErrMaxCodeSizeExceeded | ||
| res.VMError = gethVM.ErrInvalidCode | ||
| return res, nil | ||
| } | ||
|
|
||
| // reject code starting with 0xEF (EIP-3541) | ||
| if len(ret) >= 1 && ret[0] == 0xEF { | ||
| // update gas usage | ||
| if !contract.UseGas(gethVM.GasCosts{RegularGas: createDataGas}, proc.evm.Config.Tracer, gethTracing.GasChangeCallCodeStorage) { | ||
| // consume all the remaining gas (Homestead) | ||
| res.GasConsumed = call.GasLimit | ||
| res.VMError = gethVM.ErrInvalidCode | ||
| res.VMError = gethVM.ErrCodeStoreOutOfGas | ||
| return res, nil | ||
| } | ||
|
|
||
| res.DeployedContractAddress = &call.To | ||
| res.CumulativeGasUsed = proc.config.BlockTotalGasUsedSoFar + res.GasConsumed | ||
|
|
||
| proc.state.SetCode(addr, ret, gethTracing.CodeChangeContractCreation) | ||
| if len(ret) > 0 { | ||
| proc.state.SetCode(addr, ret, gethTracing.CodeChangeContractCreation) | ||
| } | ||
|
|
||
| res.StateChangeCommitment, err = proc.commit(true) | ||
| return res, err | ||
| } | ||
|
|
@@ -676,14 +732,10 @@ func (proc *procedure) run( | |
| // Set gas pool based on block gas limit | ||
| // if the block gas limit is set to anything than max | ||
| // we need to update this code. | ||
| gasPool := (*gethCore.GasPool)(&proc.config.BlockContext.GasLimit) | ||
| gasPool := gethCore.NewGasPool(proc.config.BlockContext.GasLimit) | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure why the DefaultBlockLevelGasLimit = uint64(math.MaxUint64)but given that we apply only a single // Do not panic if the gas pool is nil. This is allowed when executing
// a single message via RPC invocation.
if gp == nil {
gp = NewGasPool(msg.GasLimit)
}and use as a sane default the |
||
|
|
||
| // transit the state | ||
| execResult, err := gethCore.ApplyMessage( | ||
| proc.evm, | ||
| msg, | ||
| gasPool, | ||
| ) | ||
| execResult, err := gethCore.ApplyMessage(proc.evm, msg, gasPool) | ||
| if err != nil { | ||
| // if the error is a fatal error or a non-fatal state error or a backend err return it | ||
| // this condition should never happen given all StateDB errors are withheld for the commit time. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TestnetAmsterdamActivation&MainnetAmsterdamActivationwill be updated accordingly when there's official activation times.