From e517cdb1fad4db2ba8b036af15d6c0d5539fbb8c Mon Sep 17 00:00:00 2001 From: gop Date: Wed, 14 Aug 2024 14:19:30 -0500 Subject: [PATCH] Added stateGas value into the contract and dynamic and constant gas calculation is done based on the size of the state and contract storage trie --- core/block_validator.go | 5 +- core/evm.go | 1 + core/state_processor.go | 78 ++++---- core/state_transition.go | 6 +- core/types.go | 2 +- core/types/qi_tx.go | 2 +- core/vm/contract.go | 13 +- core/vm/contracts.go | 124 +++++++----- core/vm/evm.go | 62 +++--- core/vm/gas_table.go | 252 ++++++++++++----------- core/vm/instructions.go | 3 +- core/vm/interpreter.go | 18 +- core/vm/jump_table.go | 396 +++++++++++++++++++------------------ core/vm/operations_acl.go | 123 ++++++++---- core/vm/runtime/env.go | 19 +- core/vm/runtime/runtime.go | 42 ++-- core/worker.go | 4 +- params/protocol_params.go | 85 +++++--- 18 files changed, 681 insertions(+), 554 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index fe1f9f4bd6..072264c709 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -261,13 +261,16 @@ func (v *BlockValidator) SanityCheckWorkObjectShareViewBody(wo *types.WorkObject // transition, such as amount of used gas, the receipt roots and the state root // itself. ValidateState returns a database batch if the validation was a success // otherwise nil and an error is returned. -func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.StateDB, receipts types.Receipts, etxs types.Transactions, usedGas uint64) error { +func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.StateDB, receipts types.Receipts, etxs types.Transactions, usedGas uint64, usedState uint64) error { start := time.Now() header := types.CopyHeader(block.Header()) time1 := common.PrettyDuration(time.Since(start)) if block.GasUsed() != usedGas { return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) } + if block.StateUsed() != usedState { + return fmt.Errorf("invalid state used (remote: %d local: %d)", block.StateUsed(), usedState) + } time2 := common.PrettyDuration(time.Since(start)) time3 := common.PrettyDuration(time.Since(start)) // Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) diff --git a/core/evm.go b/core/evm.go index 14f8cb4f1f..5272804fcd 100644 --- a/core/evm.go +++ b/core/evm.go @@ -112,6 +112,7 @@ func NewEVMBlockContext(header *types.WorkObject, parent *types.WorkObject, chai GasLimit: header.GasLimit(), CheckIfEtxEligible: chain.CheckIfEtxIsEligible, EtxEligibleSlices: etxEligibleSlices, + QuaiStateSize: parent.QuaiStateSize(), // using the state size at the parent for all the gas calculations }, nil } diff --git a/core/state_processor.go b/core/state_processor.go index d6871b6adf..45f2a7c956 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -216,10 +216,11 @@ func NewStateProcessor(config *params.ChainConfig, hc *HeaderChain, engine conse // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*types.Transaction, []*types.Log, *state.StateDB, uint64, error) { +func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*types.Transaction, []*types.Log, *state.StateDB, uint64, uint64, error) { var ( receipts types.Receipts usedGas = new(uint64) + usedState = new(uint64) header = types.CopyWorkObject(block) blockHash = block.Hash() nodeLocation = p.hc.NodeLocation() @@ -231,7 +232,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty start := time.Now() parent := p.hc.GetBlock(block.ParentHash(nodeCtx), block.NumberU64(nodeCtx)-1) if parent == nil { - return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, errors.New("parent block is nil for the block given to process") + return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, 0, errors.New("parent block is nil for the block given to process") } time1 := common.PrettyDuration(time.Since(start)) @@ -246,13 +247,13 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty // Initialize a statedb statedb, err := state.New(parentEvmRoot, parentUtxoRoot, parentEtxSetRoot, parent.QuaiStateSize(), p.stateCache, p.utxoCache, p.etxCache, p.snaps, nodeLocation, p.logger) if err != nil { - return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, err + return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, 0, err } // Apply the previous inbound ETXs to the ETX set state prevInboundEtxs := rawdb.ReadInboundEtxs(p.hc.bc.db, header.ParentHash(nodeCtx)) if len(prevInboundEtxs) > 0 { if err := statedb.PushETXs(prevInboundEtxs); err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not push prev inbound etxs: %w", err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not push prev inbound etxs: %w", err) } } time2 := common.PrettyDuration(time.Since(start)) @@ -282,7 +283,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty blockContext, err := NewEVMBlockContext(header, parent, p.hc, nil) if err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, p.vmConfig) time3 := common.PrettyDuration(time.Since(start)) @@ -337,7 +338,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } fees, etxs, err, timing := ProcessQiTx(tx, p.hc, true, checkSig, header, statedb, gp, usedGas, p.hc.pool.signer, p.hc.NodeLocation(), *p.config.ChainID, &etxRLimit, &etxPLimit) if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } startEtxAppend := time.Now() for _, etx := range etxs { @@ -350,7 +351,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else { primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } totalFees.Add(totalFees, misc.QiToQuai(primeTerminus.WorkObjectHeader(), fees)) } @@ -367,7 +368,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty msg, err := tx.AsMessageWithSender(types.MakeSigner(p.config, header.Number(nodeCtx)), header.BaseFee(), senders[tx.Hash()]) if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } timeSignDelta := time.Since(startProcess) timeSign += timeSignDelta @@ -385,13 +386,13 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty // ETXs MUST be included in order, so popping the first from the queue must equal the first in the block etx, err := statedb.PopETX() if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not pop etx from statedb: %w", err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not pop etx from statedb: %w", err) } if etx == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("etx %x is nil", tx.Hash()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("etx %x is nil", tx.Hash()) } if etx.Hash() != tx.Hash() { - return nil, nil, nil, nil, 0, fmt.Errorf("invalid external transaction: etx %x is not in order or not found in unspent etx set", tx.Hash()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("invalid external transaction: etx %x is not in order or not found in unspent etx set", tx.Hash()) } // check if the tx is a coinbase tx // coinbase tx @@ -402,7 +403,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty if types.IsCoinBaseTx(tx) { iAddr, err := tx.To().InternalAddress() if err != nil { - return nil, nil, nil, nil, 0, errors.New("coinbase address is not in the chain scope") + return nil, nil, nil, nil, 0, 0, errors.New("coinbase address is not in the chain scope") } if tx.To().IsInQiLedgerScope() { value := tx.Value() @@ -421,7 +422,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } // the ETX hash is guaranteed to be unique if err := statedb.CreateUTXO(etx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), block.Number(nodeCtx)))); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } p.logger.Debugf("Creating UTXO for coinbase %032x with denomination %d index %d\n", tx.Hash(), denomination, outputIndex) outputIndex++ @@ -440,7 +441,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty lock := new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod)) primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } value := misc.QuaiToQi(primeTerminus.WorkObjectHeader(), etx.Value()) // convert Quai to Qi txGas := etx.Gas() @@ -449,7 +450,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } txGas -= params.TxGas if err := gp.SubGas(params.TxGas); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } *usedGas += params.TxGas totalEtxGas += params.TxGas @@ -468,13 +469,13 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } txGas -= params.CallValueTransferGas if err := gp.SubGas(params.CallValueTransferGas); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } *usedGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is totalEtxGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is // the ETX hash is guaranteed to be unique if err := statedb.CreateUTXO(etx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), etx.To().Bytes(), lock))); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } p.logger.Infof("Converting Quai to Qi %032x with denomination %d index %d lock %d\n", tx.Hash(), denomination, outputIndex, lock) outputIndex++ @@ -483,11 +484,11 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else { // There are no more checks to be made as the ETX is worked so add it to the set if err := statedb.CreateUTXO(etx.OriginatingTxHash(), etx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(etx.Value().Uint64()), etx.To().Bytes(), big.NewInt(0)))); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } // This Qi ETX should cost more gas if err := gp.SubGas(params.CallValueTransferGas); err != nil { - return nil, nil, nil, nil, 0, err + return nil, nil, nil, nil, 0, 0, err } *usedGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is totalEtxGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is @@ -500,7 +501,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty msg.SetLock(new(big.Int).Add(header.Number(nodeCtx), big.NewInt(params.ConversionLockPeriod))) primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } // Convert Qi to Quai msg.SetValue(misc.QiToQuai(primeTerminus.WorkObjectHeader(), etx.Value())) @@ -508,10 +509,10 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty p.logger.Infof("Converting Qi to Quai for ETX %032x with value %d lock %d\n", tx.Hash(), msg.Value().Uint64(), msg.Lock().Uint64()) } prevZeroBal := prepareApplyETX(statedb, msg.Value(), nodeLocation) - receipt, quaiFees, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, etx, usedGas, vmenv, &etxRLimit, &etxPLimit, p.logger) + receipt, quaiFees, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, etx, usedGas, usedState, vmenv, &etxRLimit, &etxPLimit, p.logger) statedb.SetBalance(common.ZeroInternal(nodeLocation), prevZeroBal) // Reset the balance to what it previously was. Residual balance will be lost if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } addReceipt = true if block.Coinbase().IsInQuaiLedgerScope() { @@ -519,7 +520,7 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else { primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } totalFees.Add(totalFees, misc.QuaiToQi(primeTerminus.WorkObjectHeader(), quaiFees)) } @@ -530,9 +531,9 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else if tx.Type() == types.QuaiTxType { startTimeTx := time.Now() - receipt, quaiFees, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, &etxRLimit, &etxPLimit, p.logger) + receipt, quaiFees, err = applyTransaction(msg, parent, p.config, p.hc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, usedState, vmenv, &etxRLimit, &etxPLimit, p.logger) if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } addReceipt = true timeTxDelta := time.Since(startTimeTx) @@ -542,12 +543,12 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty } else { primeTerminus := p.hc.GetHeaderByHash(header.PrimeTerminus()) if primeTerminus == nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not find prime terminus header %032x", header.PrimeTerminus()) } totalFees.Add(totalFees, misc.QuaiToQi(primeTerminus.WorkObjectHeader(), quaiFees)) } } else { - return nil, nil, nil, nil, 0, ErrTxTypeNotSupported + return nil, nil, nil, nil, 0, 0, ErrTxTypeNotSupported } for _, etx := range receipt.Etxs { if receipt.Status == types.ReceiptStatusSuccessful { @@ -564,19 +565,19 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty etxAvailable := false oldestIndex, err := statedb.GetOldestIndex() if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not get oldest index: %w", err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not get oldest index: %w", err) } // Check if there is at least one ETX in the set etx, err := statedb.ReadETX(oldestIndex) if err != nil { - return nil, nil, nil, nil, 0, fmt.Errorf("could not read etx: %w", err) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("could not read etx: %w", err) } if etx != nil { etxAvailable = true } if (etxAvailable && totalEtxGas < minimumEtxGas) || totalEtxGas > maximumEtxGas { p.logger.Errorf("prevInboundEtxs: %d, oldestIndex: %d, etxHash: %s", len(prevInboundEtxs), oldestIndex.Int64(), etx.Hash().Hex()) - return nil, nil, nil, nil, 0, fmt.Errorf("total gas used by ETXs %d is not within the range %d to %d", totalEtxGas, minimumEtxGas, maximumEtxGas) + return nil, nil, nil, nil, 0, 0, fmt.Errorf("total gas used by ETXs %d is not within the range %d to %d", totalEtxGas, minimumEtxGas, maximumEtxGas) } coinbaseReward := misc.CalculateReward(block.WorkObjectHeader()) @@ -629,10 +630,10 @@ func (p *StateProcessor) Process(block *types.WorkObject) (types.Receipts, []*ty "numTxs": len(block.Transactions()), }).Info("Total Tx Processing Time") - return receipts, emittedEtxs, allLogs, statedb, *usedGas, nil + return receipts, emittedEtxs, allLogs, statedb, *usedGas, *usedState, nil } -func applyTransaction(msg types.Message, parent *types.WorkObject, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { +func applyTransaction(msg types.Message, parent *types.WorkObject, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, usedState *uint64, evm *vm.EVM, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { nodeLocation := config.Location // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) @@ -670,6 +671,7 @@ func applyTransaction(msg types.Message, parent *types.WorkObject, config *param statedb.Finalise(true) *usedGas += result.UsedGas + *usedState += result.UsedState // Create a new receipt for the transaction, storing the intermediate root and gas used // by the tx. @@ -1193,7 +1195,7 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*t time1 := common.PrettyDuration(time.Since(start)) time2 := common.PrettyDuration(time.Since(start)) // Process our block - receipts, etxs, logs, statedb, usedGas, err := p.Process(block) + receipts, etxs, logs, statedb, usedGas, usedState, err := p.Process(block) if err != nil { return nil, err } @@ -1204,7 +1206,7 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*t }).Warn("Block hash changed after Processing the block") } time3 := common.PrettyDuration(time.Since(start)) - err = p.validator.ValidateState(block, statedb, receipts, etxs, usedGas) + err = p.validator.ValidateState(block, statedb, receipts, etxs, usedGas, usedState) if err != nil { return nil, err } @@ -1264,7 +1266,7 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*t // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, parentOrder int, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, header *types.WorkObject, tx *types.Transaction, usedGas *uint64, cfg vm.Config, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { +func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, parentOrder int, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, header *types.WorkObject, tx *types.Transaction, usedGas *uint64, usedState *uint64, cfg vm.Config, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { nodeCtx := config.Location.Context() msg, err := tx.AsMessage(types.MakeSigner(config, header.Number(nodeCtx)), header.BaseFee()) if err != nil { @@ -1293,11 +1295,11 @@ func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, pare vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) if tx.Type() == types.ExternalTxType { prevZeroBal := prepareApplyETX(statedb, msg.Value(), config.Location) - receipt, quaiFees, err := applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, vmenv, etxRLimit, etxPLimit, logger) + receipt, quaiFees, err := applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, usedState, vmenv, etxRLimit, etxPLimit, logger) statedb.SetBalance(common.ZeroInternal(config.Location), prevZeroBal) // Reset the balance to what it previously was (currently a failed external transaction removes all the sent coins from the supply and any residual balance is gone as well) return receipt, quaiFees, err } - return applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, vmenv, etxRLimit, etxPLimit, logger) + return applyTransaction(msg, parent, config, bc, author, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, usedState, vmenv, etxRLimit, etxPLimit, logger) } // GetVMConfig returns the block chain VM config. @@ -1502,7 +1504,7 @@ func (p *StateProcessor) StateAtBlock(block *types.WorkObject, reexec uint64, ba if currentBlock == nil { return nil, errors.New("detached block found trying to regenerate state") } - _, _, _, _, _, err := p.Process(currentBlock) + _, _, _, _, _, _, err := p.Process(currentBlock) if err != nil { return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(nodeCtx), err) } diff --git a/core/state_transition.go b/core/state_transition.go index 2ad9b62ea9..d32597c52c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -90,6 +90,7 @@ type Message interface { // message no matter the execution itself is successful or not. type ExecutionResult struct { UsedGas uint64 // Total used gas but include the refunded gas + UsedState uint64 // Total used state Err error // Any error encountered during the execution(listed in core/vm/errors.go) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) Etxs []*types.Transaction // External transactions generated from opETX @@ -317,6 +318,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if strings.Contains(err.Error(), ErrEtxGasLimitReached.Error()) { return &ExecutionResult{ UsedGas: params.TxGas, + UsedState: params.EtxStateUsed, Err: err, ReturnData: []byte{}, Etxs: nil, @@ -359,6 +361,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { vmerr error // vm errors do not effect consensus and are therefore not assigned to err contractAddr *common.Address ) + var stateUsed uint64 if contractCreation { var contract common.Address ret, contract, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value) @@ -374,7 +377,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { return nil, err } st.state.SetNonce(from, st.state.GetNonce(addr)+1) - ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value, st.msg.Lock()) + ret, st.gas, stateUsed, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value, st.msg.Lock()) } // At this point, the execution completed, so the ETX cache can be dumped and reset @@ -400,6 +403,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { return &ExecutionResult{ UsedGas: st.gasUsed(), + UsedState: stateUsed, Err: vmerr, ReturnData: ret, Etxs: etxs, diff --git a/core/types.go b/core/types.go index 34f962c4ec..ef76f92cd3 100644 --- a/core/types.go +++ b/core/types.go @@ -37,7 +37,7 @@ type Validator interface { // ValidateState validates the given statedb and optionally the receipts and // gas used. - ValidateState(block *types.WorkObject, state *state.StateDB, receipts types.Receipts, etxs types.Transactions, usedGas uint64) error + ValidateState(block *types.WorkObject, state *state.StateDB, receipts types.Receipts, etxs types.Transactions, usedGas uint64, usedState uint64) error } // Prefetcher is an interface for pre-caching transaction signatures and state. diff --git a/core/types/qi_tx.go b/core/types/qi_tx.go index 11ac6dd1b9..21ac00315f 100644 --- a/core/types/qi_tx.go +++ b/core/types/qi_tx.go @@ -144,7 +144,7 @@ func CalculateQiTxGas(transaction *Transaction, location common.Location) uint64 txGas += params.ETXGas + params.TxGas } else if location.Equal(*toAddr.Location()) && toAddr.IsInQuaiLedgerScope() { // This output creates a conversion - txGas += params.ETXGas + params.TxGas + params.ColdSloadCost + params.ColdSloadCost + params.SstoreSetGas + params.SstoreSetGas + txGas += params.ETXGas + params.TxGas + params.ColdSloadCost(big.NewInt(0), big.NewInt(0)) + params.ColdSloadCost(big.NewInt(0), big.NewInt(0)) + params.SstoreSetGas(big.NewInt(0), big.NewInt(0)) + params.SstoreSetGas(big.NewInt(0), big.NewInt(0)) } } return txGas diff --git a/core/vm/contract.go b/core/vm/contract.go index c97bb0ca2e..20da322ddb 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -58,8 +58,9 @@ type Contract struct { CodeAddr *common.Address Input []byte - Gas uint64 - value *big.Int + StateGas uint64 + Gas uint64 + value *big.Int } // NewContract returns a new contract environment for the execution of EVM. @@ -79,6 +80,9 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin // ensures a value is set c.value = value + // Create a state gas + c.StateGas = 0 + return c } @@ -172,6 +176,11 @@ func (c *Contract) UseGas(gas uint64) (ok bool) { return true } +// UseState keeps a counter on the gas used for state operation +func (c *Contract) UseState(gas uint64) { + c.StateGas += gas +} + // Address returns the contracts address func (c *Contract) Address() common.Address { return c.self.Address() diff --git a/core/vm/contracts.go b/core/vm/contracts.go index a685bc8192..460e281d5f 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -495,31 +495,36 @@ func RequiredGas(input []byte) uint64 { } // RedeemQuai executes the lockup contract to redeem the locked balance(s) for the sender -func RedeemQuai(statedb StateDB, sender common.Address, gas *types.GasPool, blockHeight *big.Int, lockupContractAddress common.Address) (uint64, error) { +func RedeemQuai(blockContext BlockContext, statedb StateDB, sender common.Address, gas *types.GasPool, blockHeight *big.Int, lockupContractAddress common.Address) (uint64, uint64, error) { internalContractAddress, err := lockupContractAddress.InternalAndQuaiAddress() if err != nil { - return 0, err + return 0, 0, err } + stateSize := blockContext.QuaiStateSize + contractSize := statedb.GetSize(internalContractAddress) // The current lock is the next available lock to redeem (in order of creation) currentLockHash := statedb.GetState(internalContractAddress, sender.Hash()) - gasUsed := params.ColdSloadCost - if gas.SubGas(params.ColdSloadCost) != nil { + coldSloadCost := params.ColdSloadCost(stateSize, contractSize) + sstoreResetGas := params.SstoreResetGas(stateSize, contractSize) + gasUsed := coldSloadCost + stateGas := coldSloadCost + if gas.SubGas(coldSloadCost) != nil { // This contract does not revert. If the caller runs out of gas, we just stop - return gasUsed, ErrOutOfGas + return gasUsed, stateGas, ErrOutOfGas } if (currentLockHash == common.Hash{}) { - return gasUsed, errors.New("lockup not found") + return gasUsed, stateGas, errors.New("lockup not found") } currentLockNumber := new(big.Int).SetBytes(currentLockHash[:]) if !currentLockNumber.IsUint64() { - return gasUsed, errors.New("account has locked too many times, overflows uint64") + return gasUsed, stateGas, errors.New("account has locked too many times, overflows uint64") } for i := int64(0); i < math.MaxInt64; i++ { // TODO: We should decide on a reasonable limit // Ensure we have enough gas to complete this step entirely - requiredGas := params.ColdSloadCost + params.SstoreResetGas + params.ColdSloadCost + params.SstoreResetGas + params.SstoreResetGas + params.CallValueTransferGas + requiredGas := coldSloadCost + sstoreResetGas + coldSloadCost + sstoreResetGas + sstoreResetGas + params.CallValueTransferGas if gas.Gas() < requiredGas { - return gasUsed, fmt.Errorf("insufficient gas to complete lockup redemption, required %d, have %d", requiredGas, gas.Gas()) + return gasUsed, stateGas, fmt.Errorf("insufficient gas to complete lockup redemption, required %d, have %d", requiredGas, gas.Gas()) } // The key is zero padded + sender's address + current lock pointer + 1 key := sender.Bytes() @@ -527,133 +532,148 @@ func RedeemQuai(statedb StateDB, sender common.Address, gas *types.GasPool, bloc key = binary.BigEndian.AppendUint64(key, currentLockNumber.Uint64()) key = append(key, byte(1)) // Set the 29th byte of the key to 1 to get lock height if len(key) > common.HashLength { - return gasUsed, errors.New("lockup key is too long, math is broken") + return gasUsed, stateGas, errors.New("lockup key is too long, math is broken") } lockHash := statedb.GetState(internalContractAddress, common.BytesToHash(key)) - gasUsed += params.ColdSloadCost - if gas.SubGas(params.ColdSloadCost) != nil { + gasUsed += coldSloadCost + stateGas += coldSloadCost + if gas.SubGas(coldSloadCost) != nil { // This contract does not revert. If the caller runs out of gas, we just stop - return gasUsed, ErrOutOfGas + return gasUsed, stateGas, ErrOutOfGas } if (lockHash == common.Hash{}) { // Lock doesn't exist, so we're done - return gasUsed, nil + return gasUsed, stateGas, nil } lock := new(big.Int).SetBytes(lockHash[:]) if lock.Cmp(blockHeight) > 0 { // lock not ready yet. Lockups are stored in FIFO order, so we don't have to go through the rest - return gasUsed, fmt.Errorf("lockup not ready yet, lock height: %d, current block height: %d", lock, blockHeight) + return gasUsed, stateGas, fmt.Errorf("lockup not ready yet, lock height: %d, current block height: %d", lock, blockHeight) } // Set the lock to zero statedb.SetState(internalContractAddress, common.BytesToHash(key), common.Hash{}) - gasUsed += params.SstoreResetGas - if gas.SubGas(params.SstoreResetGas) != nil { + gasUsed += sstoreResetGas + stateGas += sstoreResetGas + if gas.SubGas(sstoreResetGas) != nil { // This contract does not revert. If the caller runs out of gas, we just stop - return gasUsed, ErrOutOfGas + return gasUsed, stateGas, ErrOutOfGas } key[28] = 0 // Set the 29th byte of the key to 0 for balance balanceHash := statedb.GetState(internalContractAddress, common.BytesToHash(key)) - gasUsed += params.ColdSloadCost - if gas.SubGas(params.ColdSloadCost) != nil { - return gasUsed, ErrOutOfGas + gasUsed += coldSloadCost + stateGas += coldSloadCost + if gas.SubGas(coldSloadCost) != nil { + return gasUsed, stateGas, ErrOutOfGas } if (balanceHash == common.Hash{}) { // If locked balance after covnert is zero, either it doesn't exist or something is broken - return gasUsed, errors.New("balance not found") + return gasUsed, stateGas, errors.New("balance not found") } // Set the locked balance to zero statedb.SetState(internalContractAddress, common.BytesToHash(key), common.Hash{}) - gasUsed += params.SstoreResetGas - if gas.SubGas(params.SstoreResetGas) != nil { - return gasUsed, ErrOutOfGas + gasUsed += sstoreResetGas + stateGas += sstoreResetGas + if gas.SubGas(sstoreResetGas) != nil { + return gasUsed, stateGas, ErrOutOfGas } // Increment the current lock counter currentLockNumber.Add(currentLockNumber, big1) currentLockHash = common.BytesToHash(binary.BigEndian.AppendUint64([]byte{}, currentLockNumber.Uint64())) statedb.SetState(internalContractAddress, sender.Hash(), currentLockHash) - gasUsed += params.SstoreResetGas - if gas.SubGas(params.SstoreResetGas) != nil { - return gasUsed, ErrOutOfGas + gasUsed += sstoreResetGas + stateGas += sstoreResetGas + if gas.SubGas(sstoreResetGas) != nil { + return gasUsed, stateGas, ErrOutOfGas } // Redeem the balance for the sender balance := new(big.Int).SetBytes(balanceHash[:]) internal, err := sender.InternalAndQuaiAddress() if err != nil { - return gasUsed, err + return gasUsed, stateGas, err } statedb.AddBalance(internal, balance) gasUsed += params.CallValueTransferGas if gas.SubGas(params.CallValueTransferGas) != nil { - return gasUsed, ErrOutOfGas + return gasUsed, stateGas, ErrOutOfGas } } - return gasUsed, errors.New("account has locked too many times, overflows int64") + return gasUsed, stateGas, errors.New("account has locked too many times, overflows int64") } // AddNewLock adds a new locked balance to the lockup contract -func AddNewLock(statedb StateDB, toAddr common.Address, gas *types.GasPool, lock *big.Int, balance *big.Int, lockupContractAddress common.Address) (uint64, error) { +func AddNewLock(blockContext BlockContext, statedb StateDB, toAddr common.Address, gas *types.GasPool, lock *big.Int, balance *big.Int, lockupContractAddress common.Address) (uint64, uint64, error) { internalContractAddress, err := lockupContractAddress.InternalAndQuaiAddress() if err != nil { - return 0, err + return 0, 0, err } if len(lock.Bytes()) > common.HashLength || len(balance.Bytes()) > common.HashLength { - return 0, errors.New("lock or balance is too large") + return 0, 0, errors.New("lock or balance is too large") } + stateSize := blockContext.QuaiStateSize + contractSize := statedb.GetSize(internalContractAddress) currentLockHash := statedb.GetState(internalContractAddress, common.BytesToHash(toAddr.Bytes())) - gasUsed := params.ColdSloadCost - if gas.SubGas(params.ColdSloadCost) != nil { + coldSloadCost := params.ColdSloadCost(stateSize, contractSize) + sstoreSetGas := params.SstoreSetGas(stateSize, contractSize) + gasUsed := coldSloadCost + stateGas := coldSloadCost + if gas.SubGas(coldSloadCost) != nil { // This contract does not revert. If the caller runs out of gas, we just stop - return gasUsed, ErrOutOfGas + return gasUsed, stateGas, ErrOutOfGas } if (currentLockHash == common.Hash{}) { // No lock found, create a new one statedb.SetState(internalContractAddress, common.BytesToHash(toAddr.Bytes()), common.BytesToHash([]byte{1})) + gasUsed += sstoreSetGas + stateGas += sstoreSetGas currentLockHash = common.BytesToHash([]byte{1}) } currentLockNumber := new(big.Int).SetBytes(currentLockHash[:]) if !currentLockNumber.IsUint64() { - return gasUsed, errors.New("account has locked too many times, overflows uint64") + return gasUsed, stateGas, errors.New("account has locked too many times, overflows uint64") } for i := int64(0); i < math.MaxInt64; i++ { // TODO: We should decide on a reasonable limit // Ensure we have enough gas to complete this step entirely - requiredGas := params.ColdSloadCost + params.SstoreSetGas + params.SstoreSetGas + requiredGas := coldSloadCost + sstoreSetGas + sstoreSetGas if gas.Gas() < requiredGas { - return gasUsed, fmt.Errorf("insufficient gas to add new lock, required %d, got %d", requiredGas, gas.Gas()) + return gasUsed, stateGas, fmt.Errorf("insufficient gas to add new lock, required %d, got %d", requiredGas, gas.Gas()) } key := toAddr.Bytes() // Append current lock to the key key = binary.BigEndian.AppendUint64(key, currentLockNumber.Uint64()) key = append(key, byte(1)) // Set the 29th byte of the key to 1 for lockup if len(key) > common.HashLength { - return gasUsed, errors.New("lockup key is too long, math is broken") + return gasUsed, stateGas, errors.New("lockup key is too long, math is broken") } lockHash := statedb.GetState(internalContractAddress, common.BytesToHash(key)) - gasUsed += params.ColdSloadCost - if gas.SubGas(params.ColdSloadCost) != nil { + gasUsed += coldSloadCost + stateGas += coldSloadCost + if gas.SubGas(coldSloadCost) != nil { // This contract does not revert. If the caller runs out of gas, we just stop - return gasUsed, ErrOutOfGas + return gasUsed, stateGas, ErrOutOfGas } if (lockHash == common.Hash{}) { // Lock doesn't exist, so add the new one here statedb.SetState(internalContractAddress, common.BytesToHash(key), common.BytesToHash(lock.Bytes())) - gasUsed += params.SstoreSetGas - if gas.SubGas(params.SstoreSetGas) != nil { - return gasUsed, ErrOutOfGas + gasUsed += sstoreSetGas + stateGas += sstoreSetGas + if gas.SubGas(sstoreSetGas) != nil { + return gasUsed, stateGas, ErrOutOfGas } key[28] = 0 // Set the 29th byte of the key to 0 for balance statedb.SetState(internalContractAddress, common.BytesToHash(key), common.BytesToHash(balance.Bytes())) - gasUsed += params.SstoreSetGas - if gas.SubGas(params.SstoreSetGas) != nil { - return gasUsed, ErrOutOfGas + gasUsed += sstoreSetGas + stateGas += sstoreSetGas + if gas.SubGas(sstoreSetGas) != nil { + return gasUsed, stateGas, ErrOutOfGas } // Addition of new lock successful - return gasUsed, nil + return gasUsed, stateGas, nil } // Lock exists, increment the current lock counter (but don't store it) currentLockNumber.Add(currentLockNumber, big1) } - return gasUsed, errors.New("account has locked too many times, overflows int64") + return gasUsed, stateGas, errors.New("account has locked too many times, overflows int64") } diff --git a/core/vm/evm.go b/core/vm/evm.go index 9f1c7a27bc..46133fa1fa 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -79,12 +79,13 @@ type BlockContext struct { CheckIfEtxEligible CheckIfEtxEligibleFunc // Block information - Coinbase common.Address // Provides information for COINBASE - GasLimit uint64 // Provides information for GASLIMIT - BlockNumber *big.Int // Provides information for NUMBER - Time *big.Int // Provides information for TIME - Difficulty *big.Int // Provides information for DIFFICULTY - BaseFee *big.Int // Provides information for BASEFEE + Coinbase common.Address // Provides information for COINBASE + GasLimit uint64 // Provides information for GASLIMIT + BlockNumber *big.Int // Provides information for NUMBER + Time *big.Int // Provides information for TIME + Difficulty *big.Int // Provides information for DIFFICULTY + BaseFee *big.Int // Provides information for BASEFEE + QuaiStateSize *big.Int // Provides information for QUAISTATESIZE // Prime Terminus information for the given block EtxEligibleSlices common.Hash @@ -186,44 +187,46 @@ func (evm *EVM) Interpreter() *EVMInterpreter { // parameters. It also handles any necessary value transfer required and takes // the necessary steps to create accounts and reverses the state in case of an // execution error or failed value transfer. -func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, lock *big.Int) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, lock *big.Int) (ret []byte, leftOverGas uint64, stateGas uint64, err error) { if evm.Config.NoRecursion && evm.depth > 0 { - return nil, gas, nil + return nil, gas, 0, nil } // Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { - return nil, gas, ErrDepth + return nil, gas, 0, ErrDepth } // Fail if we're trying to transfer more than the available balance if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { - return nil, gas, ErrInsufficientBalance + return nil, gas, 0, ErrInsufficientBalance } lockupContractAddress := LockupContractAddresses[[2]byte{evm.chainConfig.Location[0], evm.chainConfig.Location[1]}] if addr.Equal(lockupContractAddress) { - gasUsed, err := RedeemQuai(evm.StateDB, caller.Address(), new(types.GasPool).AddGas(gas), evm.Context.BlockNumber, lockupContractAddress) + gasUsed, stateGasUsed, err := RedeemQuai(evm.Context, evm.StateDB, caller.Address(), new(types.GasPool).AddGas(gas), evm.Context.BlockNumber, lockupContractAddress) if gas > gasUsed { gas = gas - gasUsed } else { gas = 0 } + stateGas += stateGasUsed if err != nil { log.Global.Error("RedeemQuai failed", "err", err) } - return []byte{}, gas, err + return []byte{}, gas, stateGas, err } else if lock != nil && lock.Sign() != 0 { if err := evm.Context.Transfer(evm.StateDB, caller.Address(), lockupContractAddress, value); err != nil { - return nil, gas, err + return nil, gas, stateGas, err } - gasUsed, err := AddNewLock(evm.StateDB, addr, new(types.GasPool).AddGas(gas), lock, evm.Context.BlockNumber, lockupContractAddress) + gasUsed, stateGasUsed, err := AddNewLock(evm.Context, evm.StateDB, addr, new(types.GasPool).AddGas(gas), lock, evm.Context.BlockNumber, lockupContractAddress) if gas > gasUsed { gas = gas - gasUsed } else { gas = 0 } + stateGas += stateGasUsed if err != nil { log.Global.Error("AddNewLock failed", "err", err) } - return []byte{}, gas, err + return []byte{}, gas, stateGas, err } snapshot := evm.StateDB.Snapshot() p, isPrecompile, addr := evm.precompile(addr) @@ -238,12 +241,12 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil) } - return nil, gas, nil + return nil, gas, stateGas, nil } evm.StateDB.CreateAccount(internalAddr) } if err := evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value); err != nil { - return nil, gas, err + return nil, gas, stateGas, err } // Capture the tracer start/end events in debug mode @@ -270,6 +273,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(internalAddr), code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas + stateGas += contract.StateGas } } // When an error was returned by the EVM or when setting the creation code @@ -284,7 +288,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas //} else { // evm.StateDB.DiscardSnapshot(snapshot) } - return ret, gas, err + return ret, gas, stateGas, err } // CallCode executes the contract associated with the addr with the given input @@ -620,38 +624,38 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * return evm.create(caller, codeAndHash, gas, endowment, contractAddr) } -func (evm *EVM) CreateETX(toAddr common.Address, fromAddr common.Address, gas uint64, value *big.Int, data []byte) (ret []byte, leftOverGas uint64, err error) { +func (evm *EVM) CreateETX(toAddr common.Address, fromAddr common.Address, gas uint64, value *big.Int, data []byte) (ret []byte, leftOverGas uint64, stateGas uint64, err error) { // Verify address is not in context if toAddr.IsInQuaiLedgerScope() && common.IsInChainScope(toAddr.Bytes(), evm.chainConfig.Location) { - return []byte{}, 0, fmt.Errorf("%x is in chain scope, but CreateETX was called", toAddr) + return []byte{}, 0, 0, fmt.Errorf("%x is in chain scope, but CreateETX was called", toAddr) } conversion := false if toAddr.IsInQiLedgerScope() && common.IsInChainScope(toAddr.Bytes(), evm.chainConfig.Location) { conversion = true } if toAddr.IsInQiLedgerScope() && !common.IsInChainScope(toAddr.Bytes(), evm.chainConfig.Location) { - return []byte{}, 0, fmt.Errorf("%x is in qi scope and is not in the same location, but CreateETX was called", toAddr) + return []byte{}, 0, 0, fmt.Errorf("%x is in qi scope and is not in the same location, but CreateETX was called", toAddr) } else if conversion && value.Cmp(params.MinQuaiConversionAmount) < 0 { - return []byte{}, 0, fmt.Errorf("CreateETX conversion error: %d is not sufficient value, required amount: %d", value, params.MinQuaiConversionAmount) + return []byte{}, 0, 0, fmt.Errorf("CreateETX conversion error: %d is not sufficient value, required amount: %d", value, params.MinQuaiConversionAmount) } if gas < params.ETXGas { - return []byte{}, 0, fmt.Errorf("CreateETX error: %d is not sufficient gas, required amount: %d", gas, params.ETXGas) + return []byte{}, 0, 0, fmt.Errorf("CreateETX error: %d is not sufficient gas, required amount: %d", gas, params.ETXGas) } fromInternal, err := fromAddr.InternalAndQuaiAddress() if err != nil { - return []byte{}, 0, fmt.Errorf("CreateETX error: %s", err.Error()) + return []byte{}, 0, 0, fmt.Errorf("CreateETX error: %s", err.Error()) } gas = gas - params.ETXGas if gas < params.TxGas { // ETX must have enough gas to create a transaction - return []byte{}, 0, fmt.Errorf("CreateETX error: %d is not sufficient gas for ETX, required amount: %d", gas, params.TxGas) + return []byte{}, 0, 0, fmt.Errorf("CreateETX error: %d is not sufficient gas for ETX, required amount: %d", gas, params.TxGas) } // Fail if we're trying to transfer more than the available balance if !evm.Context.CanTransfer(evm.StateDB, fromAddr, value) { - return []byte{}, 0, fmt.Errorf("CreateETX: %x cannot transfer %d", fromAddr, value.Uint64()) + return []byte{}, 0, 0, fmt.Errorf("CreateETX: %x cannot transfer %d", fromAddr, value.Uint64()) } evm.StateDB.SubBalance(fromInternal, value) @@ -660,7 +664,7 @@ func (evm *EVM) CreateETX(toAddr common.Address, fromAddr common.Address, gas ui index := len(evm.ETXCache) // this is virtually guaranteed to be zero, but the logic is the same as opETX evm.ETXCacheLock.RUnlock() if index > math.MaxUint16 { - return []byte{}, 0, fmt.Errorf("CreateETX overflow error: too many ETXs in cache") + return []byte{}, 0, 0, fmt.Errorf("CreateETX overflow error: too many ETXs in cache") } // create external transaction etxInner := types.ExternalTx{Value: value, To: &toAddr, Sender: fromAddr, OriginatingTxHash: evm.Hash, ETXIndex: uint16(index), Gas: gas, Data: data, AccessList: evm.AccessList} @@ -668,14 +672,14 @@ func (evm *EVM) CreateETX(toAddr common.Address, fromAddr common.Address, gas ui // check if the etx is eligible to be sent to the to location if !conversion && !evm.Context.CheckIfEtxEligible(evm.Context.EtxEligibleSlices, *etx.To().Location()) { - return []byte{}, 0, fmt.Errorf("CreateETX error: ETX is not eligible to be sent to %x", etx.To()) + return []byte{}, 0, 0, fmt.Errorf("CreateETX error: ETX is not eligible to be sent to %x", etx.To()) } evm.ETXCacheLock.Lock() evm.ETXCache = append(evm.ETXCache, etx) evm.ETXCacheLock.Unlock() - return []byte{}, 0, nil // all leftover gas goes to the ETX + return []byte{}, 0, 0, nil // all leftover gas goes to the ETX } // Emitted ETXs must include some multiple of BaseFee as miner tip, to diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 7610c8a9e0..81f7b36fed 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -17,8 +17,6 @@ package vm import ( - "errors" - "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/common/math" "github.com/dominant-strategies/go-quai/params" @@ -26,9 +24,9 @@ import ( // memoryGasCost calculates the quadratic gas for memory expansion. It does so // only for the memory region that is expanded, not the total memory. -func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { +func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, uint64, error) { if newMemSize == 0 { - return 0, nil + return 0, 0, nil } // The maximum that will fit in a uint64 is max_word_count - 1. Anything above // that will result in an overflow. Additionally, a newMemSize which results in @@ -36,7 +34,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used // without overflowing the gas calculation. if newMemSize > 0x1FFFFFFFE0 { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } newMemSizeWords := toWordSize(newMemSize) newMemSize = newMemSizeWords * 32 @@ -50,9 +48,9 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { fee := newTotalFee - mem.lastGasCost mem.lastGasCost = newTotalFee - return fee, nil + return fee, 0, nil } - return 0, nil + return 0, 0, nil } // memoryCopierGas creates the gas functions for the following opcodes, and takes @@ -63,26 +61,26 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // EXTCODECOPY (stack poition 3) // RETURNDATACOPY (stack position 2) func memoryCopierGas(stackpos int) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { // Gas for expanding the memory - gas, err := memoryGasCost(mem, memorySize) + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } // And gas for copying data, charged per word at param.CopyGas words, overflow := stack.Back(stackpos).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, words); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } } @@ -92,117 +90,59 @@ var ( gasReturnDataCopy = memoryCopierGas(2) ) -// 0. If *gasleft* is less than or equal to 2300, fail the current call. -// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted. -// 2. If current value does not equal new value: -// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context): -// 2.1.1. If original value is 0, SSTORE_SET_GAS (20K) gas is deducted. -// 2.1.2. Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter. -// 2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses: -// 2.2.1. If original value is not 0: -// 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEARS_SCHEDULE gas from refund counter. -// 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEARS_SCHEDULE gas to refund counter. -// 2.2.2. If original value equals new value (this storage slot is reset): -// 2.2.2.1. If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. -// 2.2.2.2. Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. -func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGas { - return 0, errors.New("not enough gas for reentrancy sentry") - } - // Gas sentry honoured, do the actual gas calculation based on the stored value - var ( - y, x = stack.Back(1), stack.Back(0) - internalContractAddr, err = contract.Address().InternalAndQuaiAddress() - ) - if err != nil { - return 0, err - } - current := evm.StateDB.GetState(internalContractAddr, x.Bytes32()) - value := common.Hash(y.Bytes32()) - - if current == value { // noop (1) - return params.SloadGas, nil - } - original := evm.StateDB.GetCommittedState(internalContractAddr, x.Bytes32()) - if original == current { - if original == (common.Hash{}) { // create slot (2.1.1) - return params.SstoreSetGas, nil - } - if value == (common.Hash{}) { // delete slot (2.1.2b) - evm.StateDB.AddRefund(params.SstoreClearsScheduleRefund) - } - return params.SstoreResetGas, nil // write existing slot (2.1.2) - } - if original != (common.Hash{}) { - if current == (common.Hash{}) { // recreate slot (2.2.1.1) - evm.StateDB.SubRefund(params.SstoreClearsScheduleRefund) - } else if value == (common.Hash{}) { // delete slot (2.2.1.2) - evm.StateDB.AddRefund(params.SstoreClearsScheduleRefund) - } - } - if original == value { - if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) - evm.StateDB.AddRefund(params.SstoreSetGas - params.SloadGas) - } else { // reset to original existing slot (2.2.2.2) - evm.StateDB.AddRefund(params.SstoreResetGas - params.SloadGas) - } - } - return params.SloadGas, nil // dirty update (2.2) -} - func makeGasLog(n uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - gas, err := memoryGasCost(mem, memorySize) + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } var memorySizeGas uint64 if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + // make gas log doesnt use any state operation, so return 0 for stateGas used + return gas, 0, nil } } -func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } wordGas, overflow := stack.Back(1).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } // pureMemoryGascost is used by several operations, which aside from their // static cost have a dynamic cost which is solely based on the memory // expansion -func pureMemoryGascost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func pureMemoryGascost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { return memoryGasCost(mem, memorySize) } @@ -215,25 +155,25 @@ var ( gasCreate = pureMemoryGascost ) -func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } wordGas, overflow := stack.Back(2).Uint64WithOverflow() if overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } if gas, overflow = math.SafeAdd(gas, wordGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } -func gasExp(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExp(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) var ( @@ -241,100 +181,103 @@ func gasExp(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize overflow bool ) if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } -func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { var ( gas uint64 + stateGas uint64 transfersValue = !stack.Back(2).IsZero() address, err = common.Bytes20ToAddress(stack.Back(1).Bytes20(), evm.chainConfig.Location).InternalAndQuaiAddress() ) if err != nil { - return 0, err + return 0, 0, err } if transfersValue && evm.StateDB.Empty(address) { - gas += params.CallNewAccountGas + gas += params.CallNewAccountGas(evm.Context.QuaiStateSize) + stateGas = gas } if transfersValue { gas += params.CallValueTransferGas } - memoryGas, err := memoryGasCost(mem, memorySize) + memoryGas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } var overflow bool if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } evm.callGasTemp, err = callGas(contract.Gas, gas, stack.Back(0)) if err != nil { - return 0, err + return 0, 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, stateGas, nil } -func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - memoryGas, err := memoryGasCost(mem, memorySize) +func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + memoryGas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } var ( gas uint64 + stateGas uint64 overflow bool ) if stack.Back(2).Sign() != 0 { gas += params.CallValueTransferGas } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } evm.callGasTemp, err = callGas(contract.Gas, gas, stack.Back(0)) if err != nil { - return 0, err + return 0, 0, err } if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, stateGas, nil } -func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } evm.callGasTemp, err = callGas(contract.Gas, gas, stack.Back(0)) if err != nil { - return 0, err + return 0, 0, err } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } -func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { + gas, _, err := memoryGasCost(mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } evm.callGasTemp, err = callGas(contract.Gas, gas, stack.Back(0)) if err != nil { - return 0, err + return 0, 0, err } var overflow bool if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { - return 0, ErrGasUintOverflow + return 0, 0, ErrGasUintOverflow } - return gas, nil + return gas, 0, nil } func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { @@ -358,3 +301,56 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } return gas, nil } + +func gasWarmStorageRead(evm *EVM, contract *Contract) (uint64, uint64, error) { + contractAddr, err := contract.Address().InternalAddress() + if err != nil { + return 0, 0, err + } + contractSize := evm.StateDB.GetSize(contractAddr) + warmStorageGasCost := params.WarmStorageReadCost(evm.Context.QuaiStateSize, contractSize) + return warmStorageGasCost, warmStorageGasCost, nil +} + +// wrapping the constant gas values in the constantGasFunc signature, all these +// functions dont use the stateGas +func gasZero(evm *EVM, contract *Contract) (uint64, uint64, error) { + return 0, 0, nil +} +func gasQuickStep(evm *EVM, contract *Contract) (uint64, uint64, error) { + return GasQuickStep, 0, nil +} +func gasFastestStep(evm *EVM, contract *Contract) (uint64, uint64, error) { + return GasFastestStep, 0, nil +} +func gasFastStep(evm *EVM, contract *Contract) (uint64, uint64, error) { + return GasFastStep, 0, nil +} +func gasMidStep(evm *EVM, contract *Contract) (uint64, uint64, error) { + return GasMidStep, 0, nil +} +func gasSlowStep(evm *EVM, contract *Contract) (uint64, uint64, error) { + return GasSlowStep, 0, nil +} +func gasExtStep(evm *EVM, contract *Contract) (uint64, uint64, error) { + return GasExtStep, 0, nil +} + +func gasCreateConstant(evm *EVM, contract *Contract) (uint64, uint64, error) { + return params.CreateGas, 0, nil +} +func gasCreate2Constant(evm *EVM, contract *Contract) (uint64, uint64, error) { + return params.Create2Gas, 0, nil +} +func gasSha3Constant(evm *EVM, contract *Contract) (uint64, uint64, error) { + return params.Sha3Gas, 0, nil +} +func gasJumpDestGas(evm *EVM, contract *Contract) (uint64, uint64, error) { + return params.JumpdestGas, 0, nil +} +func gasSelfdestructConstant(evm *EVM, contract *Contract) (uint64, uint64, error) { + return params.SelfdestructGas, 0, nil +} +func gasEtx(evm *EVM, contract *Contract) (uint64, uint64, error) { + return params.ETXGas, 0, nil +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index ed80c7a35d..b75868c036 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -684,7 +684,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt bigVal = value.ToBig() } - ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal, nil) + ret, returnGas, stateGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal, nil) if err != nil { temp.Clear() @@ -699,6 +699,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt return nil, err } scope.Contract.Gas += returnGas + scope.Contract.StateGas += stateGas return ret, nil } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index b0e7435f31..d64d17b7a0 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -185,12 +185,17 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( return nil, ErrWriteProtection } } - // Static portion of gas - cost = operation.constantGas // For tracing - if !contract.UseGas(operation.constantGas) { + // Static portion of gas, the static portion now only applies to the compute operations + // previously considered state access static costs are modified based on the size of the + // state trie and the contract storage size trie + staticCost, stateCost, err := operation.constantGas(in.evm, contract) + if !contract.UseGas(staticCost) { return nil, ErrOutOfGas } + // Add the stateGas used into the contract.StateGas + contract.UseState(stateCost) + var memorySize uint64 // calculate the new memory size and expand the memory to fit // the operation @@ -212,11 +217,16 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // cost is explicitly set so that the capture state defer method can get the proper cost if operation.dynamicGas != nil { var dynamicCost uint64 - dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) + var stateCost uint64 + dynamicCost, stateCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) cost += dynamicCost // total cost, for debug tracing + if err != nil || !contract.UseGas(dynamicCost) { return nil, ErrOutOfGas } + + // Add the stateGas used into the contract.StateGas + contract.UseState(stateCost) } if memorySize > 0 { mem.Resize(memorySize) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index e292dae62f..c11437d34b 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -16,13 +16,10 @@ package vm -import ( - "github.com/dominant-strategies/go-quai/params" -) - type ( - executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) - gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 + executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) + gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, uint64, error) // last parameter is the requested memory size as a uint64 + constantGasFunc func(*EVM, *Contract) (uint64, uint64, error) // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) ) @@ -30,7 +27,7 @@ type ( type operation struct { // execute is the operation function execute executionFunc - constantGas uint64 + constantGas constantGasFunc dynamicGas gasFunc // minStack tells how many stack items are required minStack int @@ -61,7 +58,7 @@ func NewInstructionSet() JumpTable { instructionSet[DELEGATECALL] = &operation{ execute: opDelegateCall, dynamicGas: gasDelegateCallVariant, - constantGas: params.WarmStorageReadCost, + constantGas: gasWarmStorageRead, minStack: minStack(6, 1), maxStack: maxStack(6, 1), memorySize: memoryDelegateCall, @@ -70,7 +67,7 @@ func NewInstructionSet() JumpTable { instructionSet[EXP].dynamicGas = gasExp instructionSet[STATICCALL] = &operation{ execute: opStaticCall, - constantGas: params.WarmStorageReadCost, + constantGas: gasWarmStorageRead, dynamicGas: gasStaticCallVariant, minStack: minStack(6, 1), maxStack: maxStack(6, 1), @@ -79,55 +76,56 @@ func NewInstructionSet() JumpTable { } instructionSet[RETURNDATASIZE] = &operation{ execute: opReturnDataSize, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), } instructionSet[RETURNDATACOPY] = &operation{ execute: opReturnDataCopy, - constantGas: GasFastestStep, + constantGas: gasFastestStep, dynamicGas: gasReturnDataCopy, minStack: minStack(3, 0), maxStack: maxStack(3, 0), memorySize: memoryReturnDataCopy, } instructionSet[REVERT] = &operation{ - execute: opRevert, - dynamicGas: gasRevert, - minStack: minStack(2, 0), - maxStack: maxStack(2, 0), - memorySize: memoryRevert, - reverts: true, - returns: true, + execute: opRevert, + constantGas: gasZero, + dynamicGas: gasRevert, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryRevert, + reverts: true, + returns: true, } instructionSet[SHL] = &operation{ execute: opSHL, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), } instructionSet[SHR] = &operation{ execute: opSHR, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), } instructionSet[SAR] = &operation{ execute: opSAR, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), } instructionSet[EXTCODEHASH] = &operation{ execute: opExtCodeHash, - constantGas: params.WarmStorageReadCost, + constantGas: gasWarmStorageRead, dynamicGas: gasAccountCheck, minStack: minStack(1, 1), maxStack: maxStack(1, 1), } instructionSet[CREATE2] = &operation{ execute: opCreate2, - constantGas: params.Create2Gas, + constantGas: gasCreate2Constant, dynamicGas: gasCreate2, minStack: minStack(4, 1), maxStack: maxStack(4, 1), @@ -137,13 +135,13 @@ func NewInstructionSet() JumpTable { } instructionSet[CHAINID] = &operation{ execute: opChainID, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), } instructionSet[SELFBALANCE] = &operation{ execute: opSelfBalance, - constantGas: GasFastStep, + constantGas: gasFastStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), } @@ -154,146 +152,147 @@ func newInstructionSet() JumpTable { return JumpTable{ STOP: { execute: opStop, - constantGas: 0, + constantGas: gasZero, minStack: minStack(0, 0), maxStack: maxStack(0, 0), halts: true, }, ADD: { execute: opAdd, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, MUL: { execute: opMul, - constantGas: GasFastStep, + constantGas: gasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, SUB: { execute: opSub, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, DIV: { execute: opDiv, - constantGas: GasFastStep, + constantGas: gasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, SDIV: { execute: opSdiv, - constantGas: GasFastStep, + constantGas: gasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, MOD: { execute: opMod, - constantGas: GasFastStep, + constantGas: gasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, SMOD: { execute: opSmod, - constantGas: GasFastStep, + constantGas: gasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, ADDMOD: { execute: opAddmod, - constantGas: GasMidStep, + constantGas: gasMidStep, minStack: minStack(3, 1), maxStack: maxStack(3, 1), }, MULMOD: { execute: opMulmod, - constantGas: GasMidStep, + constantGas: gasMidStep, minStack: minStack(3, 1), maxStack: maxStack(3, 1), }, EXP: { - execute: opExp, - dynamicGas: gasExp, - minStack: minStack(2, 1), - maxStack: maxStack(2, 1), + execute: opExp, + constantGas: gasZero, + dynamicGas: gasExp, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), }, SIGNEXTEND: { execute: opSignExtend, - constantGas: GasFastStep, + constantGas: gasFastStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, LT: { execute: opLt, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, GT: { execute: opGt, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, SLT: { execute: opSlt, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, SGT: { execute: opSgt, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, EQ: { execute: opEq, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, ISZERO: { execute: opIszero, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, AND: { execute: opAnd, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, XOR: { execute: opXor, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, OR: { execute: opOr, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, NOT: { execute: opNot, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, BYTE: { execute: opByte, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(2, 1), maxStack: maxStack(2, 1), }, SHA3: { execute: opSha3, - constantGas: params.Sha3Gas, + constantGas: gasSha3Constant, dynamicGas: gasSha3, minStack: minStack(2, 1), maxStack: maxStack(2, 1), @@ -301,50 +300,50 @@ func newInstructionSet() JumpTable { }, ADDRESS: { execute: opAddress, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, BALANCE: { execute: opBalance, - constantGas: params.WarmStorageReadCost, + constantGas: gasWarmStorageRead, dynamicGas: gasAccountCheck, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, ORIGIN: { execute: opOrigin, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, CALLER: { execute: opCaller, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, CALLVALUE: { execute: opCallValue, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, CALLDATALOAD: { execute: opCallDataLoad, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, CALLDATASIZE: { execute: opCallDataSize, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, CALLDATACOPY: { execute: opCallDataCopy, - constantGas: GasFastestStep, + constantGas: gasFastestStep, dynamicGas: gasCallDataCopy, minStack: minStack(3, 0), maxStack: maxStack(3, 0), @@ -352,13 +351,13 @@ func newInstructionSet() JumpTable { }, CODESIZE: { execute: opCodeSize, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, CODECOPY: { execute: opCodeCopy, - constantGas: GasFastestStep, + constantGas: gasFastestStep, dynamicGas: gasCodeCopy, minStack: minStack(3, 0), maxStack: maxStack(3, 0), @@ -366,20 +365,20 @@ func newInstructionSet() JumpTable { }, GASPRICE: { execute: opGasprice, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, EXTCODESIZE: { execute: opExtCodeSize, - constantGas: params.WarmStorageReadCost, + constantGas: gasWarmStorageRead, dynamicGas: gasAccountCheck, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, EXTCODECOPY: { execute: opExtCodeCopy, - constantGas: params.WarmStorageReadCost, + constantGas: gasWarmStorageRead, dynamicGas: gasExtCodeCopy, minStack: minStack(4, 0), maxStack: maxStack(4, 0), @@ -387,49 +386,49 @@ func newInstructionSet() JumpTable { }, BLOCKHASH: { execute: opBlockhash, - constantGas: GasExtStep, + constantGas: gasExtStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, COINBASE: { execute: opCoinbase, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, TIMESTAMP: { execute: opTimestamp, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, NUMBER: { execute: opNumber, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, DIFFICULTY: { execute: opDifficulty, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, GASLIMIT: { execute: opGasLimit, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, POP: { execute: opPop, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(1, 0), maxStack: maxStack(1, 0), }, MLOAD: { execute: opMload, - constantGas: GasFastestStep, + constantGas: gasFastestStep, dynamicGas: gasMLoad, minStack: minStack(1, 1), maxStack: maxStack(1, 1), @@ -437,7 +436,7 @@ func newInstructionSet() JumpTable { }, MSTORE: { execute: opMstore, - constantGas: GasFastestStep, + constantGas: gasFastestStep, dynamicGas: gasMStore, minStack: minStack(2, 0), maxStack: maxStack(2, 0), @@ -445,7 +444,7 @@ func newInstructionSet() JumpTable { }, MSTORE8: { execute: opMstore8, - constantGas: GasFastestStep, + constantGas: gasFastestStep, dynamicGas: gasMStore8, memorySize: memoryMStore8, minStack: minStack(2, 0), @@ -453,483 +452,489 @@ func newInstructionSet() JumpTable { }, SLOAD: { execute: opSload, - constantGas: 0, + constantGas: gasZero, dynamicGas: gasSLoad, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, SSTORE: { - execute: opSstore, - dynamicGas: gasSStoreVariant, - minStack: minStack(2, 0), - maxStack: maxStack(2, 0), - writes: true, + execute: opSstore, + constantGas: gasZero, + dynamicGas: gasSStoreVariant, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + writes: true, }, JUMP: { execute: opJump, - constantGas: GasMidStep, + constantGas: gasMidStep, minStack: minStack(1, 0), maxStack: maxStack(1, 0), jumps: true, }, JUMPI: { execute: opJumpi, - constantGas: GasSlowStep, + constantGas: gasSlowStep, minStack: minStack(2, 0), maxStack: maxStack(2, 0), jumps: true, }, PC: { execute: opPc, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, MSIZE: { execute: opMsize, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, GAS: { execute: opGas, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, JUMPDEST: { execute: opJumpdest, - constantGas: params.JumpdestGas, + constantGas: gasJumpDestGas, minStack: minStack(0, 0), maxStack: maxStack(0, 0), }, PUSH1: { execute: opPush1, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH2: { execute: makePush(2, 2), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH3: { execute: makePush(3, 3), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH4: { execute: makePush(4, 4), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH5: { execute: makePush(5, 5), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH6: { execute: makePush(6, 6), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH7: { execute: makePush(7, 7), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH8: { execute: makePush(8, 8), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH9: { execute: makePush(9, 9), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH10: { execute: makePush(10, 10), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH11: { execute: makePush(11, 11), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH12: { execute: makePush(12, 12), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH13: { execute: makePush(13, 13), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH14: { execute: makePush(14, 14), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH15: { execute: makePush(15, 15), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH16: { execute: makePush(16, 16), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH17: { execute: makePush(17, 17), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH18: { execute: makePush(18, 18), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH19: { execute: makePush(19, 19), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH20: { execute: makePush(20, 20), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH21: { execute: makePush(21, 21), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH22: { execute: makePush(22, 22), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH23: { execute: makePush(23, 23), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH24: { execute: makePush(24, 24), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH25: { execute: makePush(25, 25), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH26: { execute: makePush(26, 26), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH27: { execute: makePush(27, 27), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH28: { execute: makePush(28, 28), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH29: { execute: makePush(29, 29), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH30: { execute: makePush(30, 30), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH31: { execute: makePush(31, 31), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, PUSH32: { execute: makePush(32, 32), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, DUP1: { execute: makeDup(1), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(1), maxStack: maxDupStack(1), }, DUP2: { execute: makeDup(2), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(2), maxStack: maxDupStack(2), }, DUP3: { execute: makeDup(3), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(3), maxStack: maxDupStack(3), }, DUP4: { execute: makeDup(4), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(4), maxStack: maxDupStack(4), }, DUP5: { execute: makeDup(5), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(5), maxStack: maxDupStack(5), }, DUP6: { execute: makeDup(6), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(6), maxStack: maxDupStack(6), }, DUP7: { execute: makeDup(7), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(7), maxStack: maxDupStack(7), }, DUP8: { execute: makeDup(8), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(8), maxStack: maxDupStack(8), }, DUP9: { execute: makeDup(9), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(9), maxStack: maxDupStack(9), }, DUP10: { execute: makeDup(10), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(10), maxStack: maxDupStack(10), }, DUP11: { execute: makeDup(11), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(11), maxStack: maxDupStack(11), }, DUP12: { execute: makeDup(12), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(12), maxStack: maxDupStack(12), }, DUP13: { execute: makeDup(13), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(13), maxStack: maxDupStack(13), }, DUP14: { execute: makeDup(14), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(14), maxStack: maxDupStack(14), }, DUP15: { execute: makeDup(15), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(15), maxStack: maxDupStack(15), }, DUP16: { execute: makeDup(16), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minDupStack(16), maxStack: maxDupStack(16), }, SWAP1: { execute: makeSwap(1), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(2), maxStack: maxSwapStack(2), }, SWAP2: { execute: makeSwap(2), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(3), maxStack: maxSwapStack(3), }, SWAP3: { execute: makeSwap(3), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(4), maxStack: maxSwapStack(4), }, SWAP4: { execute: makeSwap(4), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(5), maxStack: maxSwapStack(5), }, SWAP5: { execute: makeSwap(5), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(6), maxStack: maxSwapStack(6), }, SWAP6: { execute: makeSwap(6), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(7), maxStack: maxSwapStack(7), }, SWAP7: { execute: makeSwap(7), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(8), maxStack: maxSwapStack(8), }, SWAP8: { execute: makeSwap(8), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(9), maxStack: maxSwapStack(9), }, SWAP9: { execute: makeSwap(9), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(10), maxStack: maxSwapStack(10), }, SWAP10: { execute: makeSwap(10), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(11), maxStack: maxSwapStack(11), }, SWAP11: { execute: makeSwap(11), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(12), maxStack: maxSwapStack(12), }, SWAP12: { execute: makeSwap(12), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(13), maxStack: maxSwapStack(13), }, SWAP13: { execute: makeSwap(13), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(14), maxStack: maxSwapStack(14), }, SWAP14: { execute: makeSwap(14), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(15), maxStack: maxSwapStack(15), }, SWAP15: { execute: makeSwap(15), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(16), maxStack: maxSwapStack(16), }, SWAP16: { execute: makeSwap(16), - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minSwapStack(17), maxStack: maxSwapStack(17), }, LOG0: { - execute: makeLog(0), - dynamicGas: makeGasLog(0), - minStack: minStack(2, 0), - maxStack: maxStack(2, 0), - memorySize: memoryLog, - writes: true, + execute: makeLog(0), + constantGas: gasZero, + dynamicGas: makeGasLog(0), + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryLog, + writes: true, }, LOG1: { - execute: makeLog(1), - dynamicGas: makeGasLog(1), - minStack: minStack(3, 0), - maxStack: maxStack(3, 0), - memorySize: memoryLog, - writes: true, + execute: makeLog(1), + constantGas: gasZero, + dynamicGas: makeGasLog(1), + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryLog, + writes: true, }, LOG2: { - execute: makeLog(2), - dynamicGas: makeGasLog(2), - minStack: minStack(4, 0), - maxStack: maxStack(4, 0), - memorySize: memoryLog, - writes: true, + execute: makeLog(2), + constantGas: gasZero, + dynamicGas: makeGasLog(2), + minStack: minStack(4, 0), + maxStack: maxStack(4, 0), + memorySize: memoryLog, + writes: true, }, LOG3: { - execute: makeLog(3), - dynamicGas: makeGasLog(3), - minStack: minStack(5, 0), - maxStack: maxStack(5, 0), - memorySize: memoryLog, - writes: true, + execute: makeLog(3), + constantGas: gasZero, + dynamicGas: makeGasLog(3), + minStack: minStack(5, 0), + maxStack: maxStack(5, 0), + memorySize: memoryLog, + writes: true, }, LOG4: { - execute: makeLog(4), - dynamicGas: makeGasLog(4), - minStack: minStack(6, 0), - maxStack: maxStack(6, 0), - memorySize: memoryLog, - writes: true, + execute: makeLog(4), + constantGas: gasZero, + dynamicGas: makeGasLog(4), + minStack: minStack(6, 0), + maxStack: maxStack(6, 0), + memorySize: memoryLog, + writes: true, }, CREATE: { execute: opCreate, - constantGas: params.CreateGas, + constantGas: gasCreateConstant, dynamicGas: gasCreate, minStack: minStack(3, 1), maxStack: maxStack(3, 1), @@ -939,7 +944,7 @@ func newInstructionSet() JumpTable { }, CALL: { execute: opCall, - constantGas: params.WarmStorageReadCost, + constantGas: gasWarmStorageRead, dynamicGas: gasCallVariant, minStack: minStack(7, 1), maxStack: maxStack(7, 1), @@ -948,7 +953,7 @@ func newInstructionSet() JumpTable { }, CALLCODE: { execute: opCallCode, - constantGas: params.WarmStorageReadCost, + constantGas: gasWarmStorageRead, dynamicGas: gasCallCodeVariant, minStack: minStack(7, 1), maxStack: maxStack(7, 1), @@ -956,16 +961,17 @@ func newInstructionSet() JumpTable { returns: true, }, RETURN: { - execute: opReturn, - dynamicGas: gasReturn, - minStack: minStack(2, 0), - maxStack: maxStack(2, 0), - memorySize: memoryReturn, - halts: true, + execute: opReturn, + constantGas: gasZero, + dynamicGas: gasReturn, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + memorySize: memoryReturn, + halts: true, }, SELFDESTRUCT: { execute: opSuicide, - constantGas: params.SelfdestructGas, + constantGas: gasSelfdestructConstant, dynamicGas: gasSelfdestructVariant, minStack: minStack(1, 0), maxStack: maxStack(1, 0), @@ -974,13 +980,13 @@ func newInstructionSet() JumpTable { }, BASEFEE: { execute: opBaseFee, - constantGas: GasQuickStep, + constantGas: gasQuickStep, minStack: minStack(0, 1), maxStack: maxStack(0, 1), }, ETX: { execute: opETX, - constantGas: params.ETXGas, + constantGas: gasEtx, minStack: minStack(10, 1), maxStack: maxStack(10, 1), memorySize: memoryETX, @@ -988,13 +994,13 @@ func newInstructionSet() JumpTable { }, ISADDRINTERNAL: { execute: opIsAddressInternal, - constantGas: GasFastestStep, + constantGas: gasFastestStep, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, CONVERT: { execute: opConvert, - constantGas: params.ETXGas, + constantGas: gasEtx, minStack: minStack(4, 1), maxStack: maxStack(4, 1), writes: true, diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index a196e9cc1f..e13a5a27e0 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -18,32 +18,37 @@ package vm import ( "errors" + "math/big" "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/common/math" "github.com/dominant-strategies/go-quai/params" ) -func makeGasSStoreFunc(clearingRefund uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGas { - return 0, errors.New("not enough gas for reentrancy sentry") - } +func makeGasSStoreFunc(clearingRefund func(*big.Int, *big.Int) uint64) gasFunc { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.peek() slot = common.Hash(x.Bytes32()) cost = uint64(0) + stateGas = uint64(0) internalAddr, err = contract.Address().InternalAndQuaiAddress() ) if err != nil { - return 0, err + return 0, stateGas, err + } + storageSizeOfContract := evm.StateDB.GetSize(internalAddr) + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGas(evm.Context.QuaiStateSize, storageSizeOfContract) { + return 0, 0, errors.New("not enough gas for reentrancy sentry") } current := evm.StateDB.GetState(internalAddr, slot) // Check slot presence in the access list if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { - cost = params.ColdSloadCost + coldSLoadCost := params.ColdSloadCost(evm.Context.QuaiStateSize, storageSizeOfContract) + cost = coldSLoadCost + stateGas = coldSLoadCost // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddSlotToAccessList(contract.Address(), slot) if !addrPresent { @@ -56,33 +61,33 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { value := common.Hash(y.Bytes32()) if current == value { // noop (1) - return cost + params.WarmStorageReadCost, nil // SLOAD_GAS + return cost + params.WarmStorageReadCost(evm.Context.QuaiStateSize, storageSizeOfContract), stateGas, nil // SLOAD_GAS } original := evm.StateDB.GetCommittedState(internalAddr, x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) - return cost + params.SstoreSetGas, nil + return cost + params.SstoreSetGas(evm.Context.QuaiStateSize, storageSizeOfContract), stateGas, nil } if value == (common.Hash{}) { // delete slot (2.1.2b) - evm.StateDB.AddRefund(clearingRefund) + evm.StateDB.AddRefund(clearingRefund(evm.Context.QuaiStateSize, storageSizeOfContract)) } - return cost + (params.SstoreResetGas - params.ColdSloadCost), nil // write existing slot (2.1.2) + return cost + (params.SstoreResetGas(evm.Context.QuaiStateSize, storageSizeOfContract) - params.ColdSloadCost(evm.Context.QuaiStateSize, storageSizeOfContract)), stateGas, nil // write existing slot (2.1.2) } if original != (common.Hash{}) { if current == (common.Hash{}) { // recreate slot (2.2.1.1) - evm.StateDB.SubRefund(clearingRefund) + evm.StateDB.SubRefund(clearingRefund(evm.Context.QuaiStateSize, storageSizeOfContract)) } else if value == (common.Hash{}) { // delete slot (2.2.1.2) - evm.StateDB.AddRefund(clearingRefund) + evm.StateDB.AddRefund(clearingRefund(evm.Context.QuaiStateSize, storageSizeOfContract)) } } if original == value { if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) - evm.StateDB.AddRefund(params.SstoreSetGas - params.WarmStorageReadCost) + evm.StateDB.AddRefund(params.SstoreSetGas(evm.Context.QuaiStateSize, storageSizeOfContract) - params.WarmStorageReadCost(evm.Context.QuaiStateSize, storageSizeOfContract)) } else { // reset to original existing slot (2.2.2.2) - evm.StateDB.AddRefund((params.SstoreResetGas - params.ColdSloadCost) - params.WarmStorageReadCost) + evm.StateDB.AddRefund((params.SstoreResetGas(evm.Context.QuaiStateSize, storageSizeOfContract) - params.ColdSloadCost(evm.Context.QuaiStateSize, storageSizeOfContract)) - params.WarmStorageReadCost(evm.Context.QuaiStateSize, storageSizeOfContract)) } } - return cost + params.WarmStorageReadCost, nil // dirty update (2.2) + return cost + params.WarmStorageReadCost(evm.Context.QuaiStateSize, storageSizeOfContract), stateGas, nil // dirty update (2.2) } } @@ -91,41 +96,57 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // whose storage is being read) is not yet in accessed_storage_keys, // charge 2100 gas and add the pair to accessed_storage_keys. // If the pair is already in accessed_storage_keys, charge 100 gas. -func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { loc := stack.peek() slot := common.Hash(loc.Bytes32()) + internalAddr, err := contract.Address().InternalAddress() + if err != nil { + return 0, 0, err + } + storageSizeOfContract := evm.StateDB.GetSize(internalAddr) + var stateGas uint64 // TODO: check the calculation // Check slot presence in the access list if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { // If the caller cannot afford the cost, this change will be rolled back // If he does afford it, we can skip checking the same thing later on, during execution evm.StateDB.AddSlotToAccessList(contract.Address(), slot) - return params.ColdSloadCost, nil + return params.ColdSloadCost(evm.Context.QuaiStateSize, storageSizeOfContract), stateGas, nil } - return params.WarmStorageReadCost, nil + return params.WarmStorageReadCost(evm.Context.QuaiStateSize, storageSizeOfContract), stateGas, nil } // gasExtCodeCopy implements extcodecopy gas calculation // > If the target is not in accessed_addresses, // > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. // > Otherwise, charge WARM_STORAGE_READ_COST gas. -func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { // memory expansion first - gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + // TODO: check the stateGas value + gas, stateGas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) if err != nil { - return 0, err + return 0, 0, err } addr := common.Bytes20ToAddress(stack.peek().Bytes20(), evm.chainConfig.Location) // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { evm.StateDB.AddAddressToAccessList(addr) var overflow bool + // contract internal address + contractIAddr, err := contract.Address().InternalAddress() + if err != nil { + return 0, 0, err + } + contractSize := evm.StateDB.GetSize(contractIAddr) + coldAccountAccessCost := params.ColdAccountAccessCost(evm.Context.QuaiStateSize, contractSize) + warmStorageReadCost := params.WarmStorageReadCost(evm.Context.QuaiStateSize, contractSize) // We charge (cold-warm), since 'warm' is already charged as constantGas - if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessCost-params.WarmStorageReadCost); overflow { - return 0, ErrGasUintOverflow + if gas, overflow = math.SafeAdd(gas, coldAccountAccessCost-warmStorageReadCost); overflow { + return 0, 0, ErrGasUintOverflow } - return gas, nil + stateGas += coldAccountAccessCost + warmStorageReadCost + return gas, stateGas, nil } - return gas, nil + return gas, stateGas, nil } // gasAccountCheck checks whether the first stack item (as address) is present in the access list. @@ -135,32 +156,49 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem // - extcodehash, // - extcodesize, // - (ext) balance -func gasAccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasAccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { addr := common.Bytes20ToAddress(stack.peek().Bytes20(), evm.chainConfig.Location) // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(addr) // The warm storage read cost is already charged as constantGas - return params.ColdAccountAccessCost - params.WarmStorageReadCost, nil + // contract internal address + contractIAddr, err := contract.Address().InternalAddress() + if err != nil { + return 0, 0, err + } + contractSize := evm.StateDB.GetSize(contractIAddr) + coldAccountAccessCost := params.ColdAccountAccessCost(evm.Context.QuaiStateSize, contractSize) + warmStorageReadCost := params.WarmStorageReadCost(evm.Context.QuaiStateSize, contractSize) + stateGas := coldAccountAccessCost - warmStorageReadCost + return coldAccountAccessCost - warmStorageReadCost, stateGas, nil } - return 0, nil + return 0, 0, nil } func makeCallVariantGasCall(oldCalculator gasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { addr := common.Bytes20ToAddress(stack.Back(1).Bytes20(), evm.chainConfig.Location) // Check slot presence in the access list warmAccess := evm.StateDB.AddressInAccessList(addr) // The WarmStorageReadCost (100) is already deducted in the form of a constant cost, so // the cost to charge for cold access, if any, is Cold - Warm - coldCost := params.ColdAccountAccessCost - params.WarmStorageReadCost + // contract internal address + contractIAddr, err := contract.Address().InternalAddress() + if err != nil { + return 0, 0, err + } + contractSize := evm.StateDB.GetSize(contractIAddr) + coldAccountAccessCost := params.ColdAccountAccessCost(evm.Context.QuaiStateSize, contractSize) + warmStorageReadCost := params.WarmStorageReadCost(evm.Context.QuaiStateSize, contractSize) + coldCost := coldAccountAccessCost - warmStorageReadCost if !warmAccess { evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available // gas for call if !contract.UseGas(coldCost) { - return 0, ErrOutOfGas + return 0, 0, ErrOutOfGas } } // Now call the old calculator, which takes into account @@ -168,16 +206,17 @@ func makeCallVariantGasCall(oldCalculator gasFunc) gasFunc { // - transfer value // - memory expansion // - 63/64ths rule - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + gas, _, err := oldCalculator(evm, contract, stack, mem, memorySize) if warmAccess || err != nil { - return gas, err + return gas, 0, err } // In case of a cold access, we temporarily add the cold charge back, and also // add it to the returned gas. By adding it to the return, it will be charged // outside of this function, as part of the dynamic gas, and that will make it // also become correctly reported to tracers. contract.Gas += coldCost - return gas + coldCost, nil + contract.StateGas += coldCost + return gas + coldCost, coldCost, nil } } @@ -196,23 +235,25 @@ var ( // makeSelfdestructGasFn can create the selfdestruct dynamic gas function func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { - gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, uint64, error) { var ( gas uint64 + stateGas uint64 address = common.Bytes20ToAddress(stack.peek().Bytes20(), evm.chainConfig.Location) internalAddress, err = address.InternalAndQuaiAddress() ) if err != nil { - return 0, err + return 0, 0, err } contractAddress, err := contract.Address().InternalAndQuaiAddress() if err != nil { - return 0, err + return 0, 0, err } + storageSizeOfContract := evm.StateDB.GetSize(contractAddress) if !evm.StateDB.AddressInAccessList(address) { // If the caller cannot afford the cost, this change will be rolled back evm.StateDB.AddAddressToAccessList(address) - gas = params.ColdAccountAccessCost + gas = params.ColdAccountAccessCost(evm.Context.QuaiStateSize, storageSizeOfContract) } // if empty and transfers value if evm.StateDB.Empty(internalAddress) && evm.StateDB.GetBalance(contractAddress).Sign() != 0 { @@ -221,7 +262,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { if refundsEnabled && !evm.StateDB.HasSuicided(contractAddress) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } - return gas, nil + return gas, stateGas, nil } return gasFunc } diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 462f6fa959..6588a7b750 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -27,15 +27,16 @@ func NewEnv(cfg *Config) *vm.EVM { GasPrice: cfg.GasPrice, } blockContext := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - GetHash: cfg.GetHashFn, - Coinbase: cfg.Coinbase, - BlockNumber: cfg.BlockNumber, - Time: cfg.Time, - Difficulty: cfg.Difficulty, - GasLimit: cfg.GasLimit, - BaseFee: cfg.BaseFee, + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: cfg.GetHashFn, + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber, + Time: cfg.Time, + Difficulty: cfg.Difficulty, + GasLimit: cfg.GasLimit, + BaseFee: cfg.BaseFee, + QuaiStateSize: cfg.QuaiStateSize, } return vm.NewEVM(blockContext, txContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 02ac390ac5..0a6d98c1a8 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -35,20 +35,21 @@ import ( // Config is a basic type specifying certain configuration flags for running // the EVM. type Config struct { - ChainConfig *params.ChainConfig - Difficulty *big.Int - Origin common.Address - Coinbase common.Address - BlockNumber *big.Int - Time *big.Int - GasLimit uint64 - GasPrice *big.Int - Value *big.Int - Lock *big.Int - Debug bool - EVMConfig vm.Config - BaseFee *big.Int - Logger *logrus.Logger + ChainConfig *params.ChainConfig + Difficulty *big.Int + Origin common.Address + Coinbase common.Address + BlockNumber *big.Int + Time *big.Int + GasLimit uint64 + GasPrice *big.Int + Value *big.Int + Lock *big.Int + Debug bool + EVMConfig vm.Config + BaseFee *big.Int + QuaiStateSize *big.Int + Logger *logrus.Logger State *state.StateDB GetHashFn func(n uint64) common.Hash @@ -71,6 +72,9 @@ func setDefaults(cfg *Config) { if cfg.GasLimit == 0 { cfg.GasLimit = math.MaxUint64 } + if cfg.QuaiStateSize == nil { + cfg.QuaiStateSize = new(big.Int) + } if cfg.GasPrice == nil { cfg.GasPrice = new(big.Int) } @@ -127,7 +131,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(internal, code) // Call the code with the given configuration. - ret, _, err := vmenv.Call( + ret, _, _, err := vmenv.Call( sender, common.BytesToAddress([]byte("contract"), cfg.ChainConfig.Location), input, @@ -171,13 +175,13 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { // // Call, unlike Execute, requires a config and also requires the State field to // be set. -func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, error) { +func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, uint64, error) { setDefaults(cfg) vmenv := NewEnv(cfg) _, err := cfg.Origin.InternalAndQuaiAddress() if err != nil { - return []byte{}, 0, err + return []byte{}, 0, 0, err } statedb := cfg.State @@ -186,7 +190,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er statedb.PrepareAccessList(cfg.Origin, &address, vm.ActivePrecompiles(rules, cfg.ChainConfig.Location), nil) // Call the code with the given configuration. - ret, leftOverGas, err := vmenv.Call( + ret, leftOverGas, stateGas, err := vmenv.Call( vm.AccountRef(cfg.Origin), address, input, @@ -194,5 +198,5 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er cfg.Value, cfg.Lock, ) - return ret, leftOverGas, err + return ret, leftOverGas, stateGas, err } diff --git a/core/worker.go b/core/worker.go index a697b8c530..52be49c8bd 100644 --- a/core/worker.go +++ b/core/worker.go @@ -848,7 +848,8 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t snap := env.state.Snapshot() // retrieve the gas used int and pass in the reference to the ApplyTransaction gasUsed := env.wo.GasUsed() - receipt, quaiFees, err := ApplyTransaction(w.chainConfig, parent, *env.parentOrder, w.hc, &env.coinbase, env.gasPool, env.state, env.wo, tx, &gasUsed, *w.hc.bc.processor.GetVMConfig(), &env.etxRLimit, &env.etxPLimit, w.logger) + stateUsed := env.wo.StateUsed() + receipt, quaiFees, err := ApplyTransaction(w.chainConfig, parent, *env.parentOrder, w.hc, &env.coinbase, env.gasPool, env.state, env.wo, tx, &gasUsed, &stateUsed, *w.hc.bc.processor.GetVMConfig(), &env.etxRLimit, &env.etxPLimit, w.logger) if err != nil { w.logger.WithFields(log.Fields{ "err": err, @@ -866,6 +867,7 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t // This extra step is needed because previously the GasUsed was a public method and direct update of the value // was possible. env.wo.Header().SetGasUsed(gasUsed) + env.wo.Header().SetStateUsed(stateUsed) env.txs = append(env.txs, tx) env.quaiFees = new(big.Int).Add(env.quaiFees, quaiFees) env.receipts = append(env.receipts, receipt) diff --git a/params/protocol_params.go b/params/protocol_params.go index f91d6a5210..de6f6bdf7f 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -17,6 +17,7 @@ package params import ( + "math" "math/big" "github.com/dominant-strategies/go-quai/common" @@ -35,10 +36,10 @@ const ( StateLimitBoundDivisor uint64 = 1024 // The bound divisor of the gas limit, used in update calculations. PercentStateUsedThreshold uint64 = 90 // Percent Gas used threshold at which the gas limit adjusts + EtxStateUsed uint64 = 10000 // state used by a simple etx + MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis. - ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction. CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero. - CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior. TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions. TxGasContractCreation uint64 = 53000 // Per transaction that creates a contract. NOTE: Not payable on data of calls between transactions. TxDataZeroGas uint64 = 4 // Per byte of data attached to a transaction that equals zero. NOTE: Not payable on data of calls between transactions. @@ -56,30 +57,6 @@ const ( Sha3Gas uint64 = 30 // Once per SHA3 operation. Sha3WordGas uint64 = 6 // Once per word of the SHA3 operation's data. - SstoreClearGas uint64 = 5000 // Once per SSTORE operation if the zeroness doesn't change. - SstoreRefundGas uint64 = 15000 // Once per SSTORE operation if the zeroness changes to zero. - - NetSstoreNoopGas uint64 = 200 // Once per SSTORE operation if the value doesn't change. - NetSstoreInitGas uint64 = 20000 // Once per SSTORE operation from clean zero. - NetSstoreCleanGas uint64 = 5000 // Once per SSTORE operation from clean non-zero. - NetSstoreDirtyGas uint64 = 200 // Once per SSTORE operation from dirty. - - NetSstoreClearRefund uint64 = 15000 // Once per SSTORE operation for clearing an originally existing storage slot - NetSstoreResetRefund uint64 = 4800 // Once per SSTORE operation for resetting to the original non-zero value - NetSstoreResetClearRefund uint64 = 19800 // Once per SSTORE operation for resetting to the original zero value - - SstoreSentryGas uint64 = 2300 // Minimum gas required to be present for an SSTORE call, not consumed - SstoreSetGas uint64 = 20000 // Once per SSTORE operation from clean zero to non-zero - SstoreResetGas uint64 = 5000 // Once per SSTORE operation from clean non-zero to something else - - ColdAccountAccessCost = uint64(2600) // COLD_ACCOUNT_ACCESS_COST - ColdSloadCost = uint64(2100) // COLD_SLOAD_COST - WarmStorageReadCost = uint64(100) // WARM_STORAGE_READ_COST - - // SSTORE_CLEARS_SCHEDULE is defined as SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST - // Which becomes: 5000 - 2100 + 1900 = 4800 - SstoreClearsScheduleRefund uint64 = SstoreResetGas - ColdSloadCost + TxAccessListStorageKeyGas // Once per SSTORE operation for clearing an originally existing storage slot - JumpdestGas uint64 = 1 // Once per JUMPDEST operation. EpochDuration uint64 = 30000 // Duration between proof-of-work epochs. @@ -101,11 +78,7 @@ const ( TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in access list // These have been changed during the course of the chain - CallGas uint64 = 700 // Static portion of gas for CALL-derivates - BalanceGas uint64 = 700 // The cost of a BALANCE operation - ExtcodeSizeGas uint64 = 700 // Cost of EXTCODESIZE - SloadGas uint64 = 800 - ExtcodeHashGas uint64 = 700 // Cost of EXTCODEHASH + SloadGas uint64 = 800 // This is only used in the Qi tx processing SelfdestructGas uint64 = 5000 // Cost of SELFDESTRUCT // EXP has a dynamic portion depending on the size of the exponent @@ -223,3 +196,53 @@ func RegionEntropyTarget(expansionNum uint8) *big.Int { timeFactor := int64(max(numZones, 2)) return new(big.Int).Mul(big.NewInt(timeFactor), new(big.Int).SetUint64(numZones)) } + +// Gas calculation functions + +func SstoreSetGas(stateSize, contractSize *big.Int) uint64 { + return CalculateGasWithStateScaling(stateSize, contractSize, 20000) // Once per SSTORE operation from clean zero to non-zero +} + +func SstoreSentryGas(stateSize, contractSize *big.Int) uint64 { + return CalculateGasWithStateScaling(stateSize, contractSize, 2300) // Minimum gas required to be present for an SSTORE call, not consumed +} + +func ColdAccountAccessCost(stateSize, contractSize *big.Int) uint64 { + return CalculateGasWithStateScaling(stateSize, contractSize, 2600) // COLD_ACCOUNT_ACCESS_COST +} + +func WarmStorageReadCost(stateSize, contractSize *big.Int) uint64 { + return CalculateGasWithStateScaling(stateSize, contractSize, 100) // WARM_STORAGE_READ_COST +} + +func ColdSloadCost(stateSize, contractSize *big.Int) uint64 { + return CalculateGasWithStateScaling(stateSize, contractSize, 2100) // COLD_SLOAD_COST +} + +func SstoreResetGas(stateSize, contractSize *big.Int) uint64 { + return CalculateGasWithStateScaling(stateSize, contractSize, 5000) // Once per SSTORE operation from clean non-zero to something else +} + +func CallNewAccountGas(stateSize *big.Int) uint64 { + return CalculateGasWithStateScaling(stateSize, big.NewInt(0), 25000) // Paid for CALL when the destination address didn't exist prior. +} + +// SSTORE_CLEARS_SCHEDULE is defined as SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST +// Which becomes: 5000 - 2100 + 1900 = 4800 +func SstoreClearsScheduleRefund(stateSize, contractSize *big.Int) uint64 { + return SstoreResetGas(stateSize, contractSize) - ColdSloadCost(stateSize, contractSize) + TxAccessListStorageKeyGas // Once per SSTORE operation for clearing an originally existing storage slot +} + +func CalculateGasWithStateScaling(stateSize, contractSize *big.Int, baseRate uint64) uint64 { + var stateSizeFloat, contractSizeFloat, scalingFactor float64 + if stateSize.Sign() != 0 { + stateSizeFloat, _ = stateSize.Float64() + scalingFactor += math.Log(stateSizeFloat) + } + if contractSize.Sign() != 0 { + contractSizeFloat, _ = contractSize.Float64() + scalingFactor += math.Log(contractSizeFloat) + } + // If we can assume that the gas price constants is correct for level 7 trie + return (uint64(scalingFactor) * baseRate) / 7 +}