// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
)
// 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 ) {
if newMemSize == 0 {
return 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 a
// newMemSizeWords larger than 0xFFFFFFFF will cause the square operation
// to overflow.
// The constant 0x1FFFFFFFE0 is the highest number that can be used without
// overflowing the gas calculation
if newMemSize > 0x1FFFFFFFE0 {
return 0 , errGasUintOverflow
}
newMemSizeWords := toWordSize ( newMemSize )
newMemSize = newMemSizeWords * 32
if newMemSize > uint64 ( mem . Len ( ) ) {
square := newMemSizeWords * newMemSizeWords
linCoef := newMemSizeWords * params . MemoryGas
quadCoef := square / params . QuadCoeffDiv
newTotalFee := linCoef + quadCoef
fee := newTotalFee - mem . lastGasCost
mem . lastGasCost = newTotalFee
return fee , nil
}
return 0 , nil
}
func gasCallDataCopy ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
var overflow bool
if gas , overflow = math . SafeAdd ( gas , GasFastestStep ) ; overflow {
return 0 , errGasUintOverflow
}
words , overflow := bigUint64 ( stack . Back ( 2 ) )
if overflow {
return 0 , errGasUintOverflow
}
if words , overflow = math . SafeMul ( toWordSize ( words ) , params . CopyGas ) ; overflow {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , words ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasReturnDataCopy ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
var overflow bool
if gas , overflow = math . SafeAdd ( gas , GasFastestStep ) ; overflow {
return 0 , errGasUintOverflow
}
words , overflow := bigUint64 ( stack . Back ( 2 ) )
if overflow {
return 0 , errGasUintOverflow
}
if words , overflow = math . SafeMul ( toWordSize ( words ) , params . CopyGas ) ; overflow {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , words ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasSStore ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var (
y , x = stack . Back ( 1 ) , stack . Back ( 0 )
current = evm . StateDB . GetState ( contract . Address ( ) , common . BigToHash ( x ) )
)
// The legacy gas metering only takes into consideration the current state
// Legacy rules should be applied if we are in Petersburg (removal of EIP-1283)
// OR Constantinople is not active
if evm . chainRules . IsPetersburg || ! evm . chainRules . IsConstantinople {
// This checks for 3 scenario's and calculates gas accordingly:
//
// 1. From a zero-value address to a non-zero value (NEW VALUE)
// 2. From a non-zero value address to a zero-value address (DELETE)
// 3. From a non-zero to a non-zero (CHANGE)
switch {
case current == ( common . Hash { } ) && y . Sign ( ) != 0 : // 0 => non 0
return params . SstoreSetGas , nil
case current != ( common . Hash { } ) && y . Sign ( ) == 0 : // non 0 => 0
evm . StateDB . AddRefund ( params . SstoreRefundGas )
return params . SstoreClearGas , nil
default : // non 0 => non 0 (or 0 => 0)
return params . SstoreResetGas , nil
}
}
// The new gas metering is based on net gas costs (EIP-1283):
//
// 1. If current value equals new value (this is a no-op), 200 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, 20000 gas is deducted.
// 2.1.2. Otherwise, 5000 gas is deducted. If new value is 0, add 15000 gas to refund counter.
// 2.2. If original value does not equal current value (this storage slot is dirty), 200 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), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0.
// 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 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 19800 gas to refund counter.
// 2.2.2.2. Otherwise, add 4800 gas to refund counter.
value := common . BigToHash ( y )
if current == value { // noop (1)
return params . NetSstoreNoopGas , nil
}
original := evm . StateDB . GetCommittedState ( contract . Address ( ) , common . BigToHash ( x ) )
if original == current {
if original == ( common . Hash { } ) { // create slot (2.1.1)
return params . NetSstoreInitGas , nil
}
if value == ( common . Hash { } ) { // delete slot (2.1.2b)
evm . StateDB . AddRefund ( params . NetSstoreClearRefund )
}
return params . NetSstoreCleanGas , 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 . NetSstoreClearRefund )
} else if value == ( common . Hash { } ) { // delete slot (2.2.1.2)
evm . StateDB . AddRefund ( params . NetSstoreClearRefund )
}
}
if original == value {
if original == ( common . Hash { } ) { // reset to original inexistent slot (2.2.2.1)
evm . StateDB . AddRefund ( params . NetSstoreResetClearRefund )
} else { // reset to original existing slot (2.2.2.2)
evm . StateDB . AddRefund ( params . NetSstoreResetRefund )
}
}
return params . NetSstoreDirtyGas , nil
}
func makeGasLog ( n uint64 ) gasFunc {
return func ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
requestedSize , overflow := bigUint64 ( stack . Back ( 1 ) )
if overflow {
return 0 , errGasUintOverflow
}
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
if gas , overflow = math . SafeAdd ( gas , params . LogGas ) ; overflow {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , n * params . LogTopicGas ) ; overflow {
return 0 , errGasUintOverflow
}
var memorySizeGas uint64
if memorySizeGas , overflow = math . SafeMul ( requestedSize , params . LogDataGas ) ; overflow {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , memorySizeGas ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
}
func gasSha3 ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var overflow bool
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
if gas , overflow = math . SafeAdd ( gas , params . Sha3Gas ) ; overflow {
return 0 , errGasUintOverflow
}
wordGas , overflow := bigUint64 ( stack . Back ( 1 ) )
if overflow {
return 0 , errGasUintOverflow
}
if wordGas , overflow = math . SafeMul ( toWordSize ( wordGas ) , params . Sha3WordGas ) ; overflow {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , wordGas ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasCodeCopy ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
var overflow bool
if gas , overflow = math . SafeAdd ( gas , GasFastestStep ) ; overflow {
return 0 , errGasUintOverflow
}
wordGas , overflow := bigUint64 ( stack . Back ( 2 ) )
if overflow {
return 0 , errGasUintOverflow
}
if wordGas , overflow = math . SafeMul ( toWordSize ( wordGas ) , params . CopyGas ) ; overflow {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , wordGas ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasExtCodeCopy ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
var overflow bool
if gas , overflow = math . SafeAdd ( gas , gt . ExtcodeCopy ) ; overflow {
return 0 , errGasUintOverflow
}
wordGas , overflow := bigUint64 ( stack . Back ( 3 ) )
if overflow {
return 0 , errGasUintOverflow
}
if wordGas , overflow = math . SafeMul ( toWordSize ( wordGas ) , params . CopyGas ) ; overflow {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , wordGas ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasExtCodeHash ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
return gt . ExtcodeHash , nil
}
func gasMLoad ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var overflow bool
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , GasFastestStep ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasMStore8 ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var overflow bool
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , GasFastestStep ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasMStore ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var overflow bool
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , GasFastestStep ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasCreate ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var overflow bool
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
if gas , overflow = math . SafeAdd ( gas , params . CreateGas ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasCreate2 ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var overflow bool
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
if gas , overflow = math . SafeAdd ( gas , params . Create2Gas ) ; overflow {
return 0 , errGasUintOverflow
}
wordGas , overflow := bigUint64 ( stack . Back ( 2 ) )
if overflow {
return 0 , errGasUintOverflow
}
if wordGas , overflow = math . SafeMul ( toWordSize ( wordGas ) , params . Sha3WordGas ) ; overflow {
return 0 , errGasUintOverflow
}
if gas , overflow = math . SafeAdd ( gas , wordGas ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasBalance ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
return gt . Balance , nil
}
func gasExtCodeSize ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
return gt . ExtcodeSize , nil
}
func gasSLoad ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
return gt . SLoad , nil
}
func gasExp ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
expByteLen := uint64 ( ( stack . data [ stack . len ( ) - 2 ] . BitLen ( ) + 7 ) / 8 )
var (
gas = expByteLen * gt . ExpByte // no overflow check required. Max is 256 * ExpByte gas
overflow bool
)
if gas , overflow = math . SafeAdd ( gas , params . ExpGas ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasCall ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var (
gas = gt . Calls
transfersValue = stack . Back ( 2 ) . Sign ( ) != 0
address = common . BigToAddress ( stack . Back ( 1 ) )
eip158 = evm . ChainConfig ( ) . IsEIP158 ( evm . BlockNumber )
)
if eip158 {
if transfersValue && evm . StateDB . Empty ( address ) {
gas += params . CallNewAccountGas
}
} else if ! evm . StateDB . Exist ( address ) {
gas += params . CallNewAccountGas
}
if transfersValue {
gas += params . CallValueTransferGas
}
memoryGas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
var overflow bool
if gas , overflow = math . SafeAdd ( gas , memoryGas ) ; overflow {
return 0 , errGasUintOverflow
}
evm . callGasTemp , err = callGas ( gt , contract . Gas , gas , stack . Back ( 0 ) )
if err != nil {
return 0 , err
}
if gas , overflow = math . SafeAdd ( gas , evm . callGasTemp ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasCallCode ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
gas := gt . Calls
if stack . Back ( 2 ) . Sign ( ) != 0 {
gas += params . CallValueTransferGas
}
memoryGas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
var overflow bool
if gas , overflow = math . SafeAdd ( gas , memoryGas ) ; overflow {
return 0 , errGasUintOverflow
}
evm . callGasTemp , err = callGas ( gt , contract . Gas , gas , stack . Back ( 0 ) )
if err != nil {
return 0 , err
}
if gas , overflow = math . SafeAdd ( gas , evm . callGasTemp ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasReturn ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
return memoryGasCost ( mem , memorySize )
}
func gasRevert ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
return memoryGasCost ( mem , memorySize )
}
func gasSuicide ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
var gas uint64
// EIP150 homestead gas reprice fork:
if evm . ChainConfig ( ) . IsEIP150 ( evm . BlockNumber ) {
gas = gt . Suicide
var (
address = common . BigToAddress ( stack . Back ( 0 ) )
eip158 = evm . ChainConfig ( ) . IsEIP158 ( evm . BlockNumber )
)
if eip158 {
// if empty and transfers value
if evm . StateDB . Empty ( address ) && evm . StateDB . GetBalance ( contract . Address ( ) ) . Sign ( ) != 0 {
gas += gt . CreateBySuicide
}
} else if ! evm . StateDB . Exist ( address ) {
gas += gt . CreateBySuicide
}
}
if ! evm . StateDB . HasSuicided ( contract . Address ( ) ) {
evm . StateDB . AddRefund ( params . SuicideRefundGas )
}
return gas , nil
}
func gasDelegateCall ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
var overflow bool
if gas , overflow = math . SafeAdd ( gas , gt . Calls ) ; overflow {
return 0 , errGasUintOverflow
}
evm . callGasTemp , err = callGas ( gt , contract . Gas , gas , stack . Back ( 0 ) )
if err != nil {
return 0 , err
}
if gas , overflow = math . SafeAdd ( gas , evm . callGasTemp ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}
func gasStaticCall ( gt params . GasTable , evm * EVM , contract * Contract , stack * Stack , mem * Memory , memorySize uint64 ) ( uint64 , error ) {
gas , err := memoryGasCost ( mem , memorySize )
if err != nil {
return 0 , err
}
var overflow bool
if gas , overflow = math . SafeAdd ( gas , gt . Calls ) ; overflow {
return 0 , errGasUintOverflow
}
evm . callGasTemp , err = callGas ( gt , contract . Gas , gas , stack . Back ( 0 ) )
if err != nil {
return 0 , err
}
if gas , overflow = math . SafeAdd ( gas , evm . callGasTemp ) ; overflow {
return 0 , errGasUintOverflow
}
return gas , nil
}