Merge pull request #3111 from obscuren/gas-price-fork
core, core/vm: added gas price variance table (EIP #150)
This commit is contained in:
@ -21,6 +21,7 @@ import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
var ChainConfigNotFoundErr = errors.New("ChainConfig not found") // general config not found error
|
||||
@ -35,6 +36,8 @@ type ChainConfig struct {
|
||||
DAOForkBlock *big.Int `json:"daoForkBlock"` // TheDAO hard-fork switch block (nil = no fork)
|
||||
DAOForkSupport bool `json:"daoForkSupport"` // Whether the nodes supports or opposes the DAO hard-fork
|
||||
|
||||
HomesteadGasRepriceBlock *big.Int `json:"homesteadGasRepriceBlock"` // Homestead gas reprice switch block (nil = no fork)
|
||||
|
||||
VmConfig vm.Config `json:"-"`
|
||||
}
|
||||
|
||||
@ -45,3 +48,14 @@ func (c *ChainConfig) IsHomestead(num *big.Int) bool {
|
||||
}
|
||||
return num.Cmp(c.HomesteadBlock) >= 0
|
||||
}
|
||||
|
||||
// GasTable returns the gas table corresponding to the current phase (homestead or homestead reprice).
|
||||
//
|
||||
// The returned GasTable's fields shouldn't, under any circumstances, be changed.
|
||||
func (c *ChainConfig) GasTable(num *big.Int) params.GasTable {
|
||||
if c.HomesteadGasRepriceBlock == nil || num == nil || num.Cmp(c.HomesteadGasRepriceBlock) < 0 {
|
||||
return params.GasTableHomestead
|
||||
}
|
||||
|
||||
return params.GasTableHomesteadGasRepriceFork
|
||||
}
|
||||
|
@ -20,12 +20,16 @@ import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// RuleSet is an interface that defines the current rule set during the
|
||||
// execution of the EVM instructions (e.g. whether it's homestead)
|
||||
type RuleSet interface {
|
||||
IsHomestead(*big.Int) bool
|
||||
// GasTable returns the gas prices for this phase, which is based on
|
||||
// block number passed in.
|
||||
GasTable(*big.Int) params.GasTable
|
||||
}
|
||||
|
||||
// Environment is an EVM requirement and helper which allows access to outside
|
||||
|
@ -35,8 +35,27 @@ var (
|
||||
GasStop = big.NewInt(0)
|
||||
|
||||
GasContractByte = big.NewInt(200)
|
||||
|
||||
n64 = big.NewInt(64)
|
||||
)
|
||||
|
||||
// calcGas returns the actual gas cost of the call.
|
||||
//
|
||||
// The cost of gas was changed during the homestead price change HF. To allow for EIP150
|
||||
// to be implemented. The returned gas is gas - base * 63 / 64.
|
||||
func callGas(gasTable params.GasTable, availableGas, base, callCost *big.Int) *big.Int {
|
||||
if gasTable.CreateBySuicide != nil {
|
||||
availableGas = new(big.Int).Sub(availableGas, base)
|
||||
g := new(big.Int).Div(availableGas, n64)
|
||||
g.Sub(availableGas, g)
|
||||
|
||||
if g.Cmp(callCost) < 0 {
|
||||
return g
|
||||
}
|
||||
}
|
||||
return callCost
|
||||
}
|
||||
|
||||
// baseCheck checks for any stack error underflows
|
||||
func baseCheck(op OpCode, stack *Stack, gas *big.Int) error {
|
||||
// PUSH and DUP are a bit special. They all cost the same but we do want to have checking on stack push limit
|
||||
@ -127,18 +146,19 @@ var _baseCheck = map[OpCode]req{
|
||||
MSIZE: {0, GasQuickStep, 1},
|
||||
GAS: {0, GasQuickStep, 1},
|
||||
BLOCKHASH: {1, GasExtStep, 1},
|
||||
BALANCE: {1, GasExtStep, 1},
|
||||
EXTCODESIZE: {1, GasExtStep, 1},
|
||||
EXTCODECOPY: {4, GasExtStep, 0},
|
||||
BALANCE: {1, Zero, 1},
|
||||
EXTCODESIZE: {1, Zero, 1},
|
||||
EXTCODECOPY: {4, Zero, 0},
|
||||
SLOAD: {1, params.SloadGas, 1},
|
||||
SSTORE: {2, Zero, 0},
|
||||
SHA3: {2, params.Sha3Gas, 1},
|
||||
CREATE: {3, params.CreateGas, 1},
|
||||
CALL: {7, params.CallGas, 1},
|
||||
CALLCODE: {7, params.CallGas, 1},
|
||||
DELEGATECALL: {6, params.CallGas, 1},
|
||||
JUMPDEST: {0, params.JumpdestGas, 0},
|
||||
// Zero is calculated in the gasSwitch
|
||||
CALL: {7, Zero, 1},
|
||||
CALLCODE: {7, Zero, 1},
|
||||
DELEGATECALL: {6, Zero, 1},
|
||||
SUICIDE: {1, Zero, 0},
|
||||
JUMPDEST: {0, params.JumpdestGas, 0},
|
||||
RETURN: {2, Zero, 0},
|
||||
PUSH1: {0, GasFastestStep, 1},
|
||||
DUP1: {0, Zero, 1},
|
||||
|
@ -514,7 +514,12 @@ func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract
|
||||
input = memory.Get(offset.Int64(), size.Int64())
|
||||
gas = new(big.Int).Set(contract.Gas)
|
||||
)
|
||||
contract.UseGas(contract.Gas)
|
||||
if env.RuleSet().GasTable(env.BlockNumber()).CreateBySuicide != nil {
|
||||
gas.Div(gas, n64)
|
||||
gas = gas.Sub(contract.Gas, gas)
|
||||
}
|
||||
|
||||
contract.UseGas(gas)
|
||||
_, addr, suberr := env.Create(contract, input, gas, contract.Price, value)
|
||||
// Push item on the stack based on the returned error. If the ruleset is
|
||||
// homestead we must check for CodeStoreOutOfGasError (homestead only
|
||||
|
@ -25,12 +25,16 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// The default, always homestead, rule set for the vm env
|
||||
type ruleSet struct{}
|
||||
|
||||
func (ruleSet) IsHomestead(*big.Int) bool { return true }
|
||||
func (ruleSet) GasTable(*big.Int) params.GasTable {
|
||||
return params.GasTableHomesteadGasRepriceFork
|
||||
}
|
||||
|
||||
// Config is a basic type specifying certain configuration flags for running
|
||||
// the EVM.
|
||||
|
@ -16,10 +16,17 @@
|
||||
|
||||
package vm
|
||||
|
||||
import "math/big"
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
type ruleSet struct {
|
||||
hs *big.Int
|
||||
}
|
||||
|
||||
func (r ruleSet) IsHomestead(n *big.Int) bool { return n.Cmp(r.hs) >= 0 }
|
||||
func (r ruleSet) GasTable(*big.Int) params.GasTable {
|
||||
return params.GasTableHomestead
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ type EVM struct {
|
||||
env Environment
|
||||
jumpTable vmJumpTable
|
||||
cfg Config
|
||||
gasTable params.GasTable
|
||||
}
|
||||
|
||||
// New returns a new instance of the EVM.
|
||||
@ -52,6 +53,7 @@ func New(env Environment, cfg Config) *EVM {
|
||||
env: env,
|
||||
jumpTable: newJumpTable(env.RuleSet(), env.BlockNumber()),
|
||||
cfg: cfg,
|
||||
gasTable: env.RuleSet().GasTable(env.BlockNumber()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +171,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
|
||||
// Get the memory location of pc
|
||||
op = contract.GetOp(pc)
|
||||
// calculate the new memory size and gas price for the current executing opcode
|
||||
newMemSize, cost, err = calculateGasAndSize(evm.env, contract, caller, op, statedb, mem, stack)
|
||||
newMemSize, cost, err = calculateGasAndSize(evm.gasTable, evm.env, contract, caller, op, statedb, mem, stack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -234,7 +236,7 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
|
||||
|
||||
// calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
|
||||
// the operation. This does not reduce gas or resizes the memory.
|
||||
func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) {
|
||||
func calculateGasAndSize(gasTable params.GasTable, env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *Stack) (*big.Int, *big.Int, error) {
|
||||
var (
|
||||
gas = new(big.Int)
|
||||
newMemSize *big.Int = new(big.Int)
|
||||
@ -246,6 +248,24 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef
|
||||
|
||||
// stack Check, memory resize & gas phase
|
||||
switch op {
|
||||
case SUICIDE:
|
||||
// if suicide is not nil: homestead gas fork
|
||||
if gasTable.CreateBySuicide != nil {
|
||||
gas.Set(gasTable.Suicide)
|
||||
if !env.Db().Exist(common.BigToAddress(stack.data[len(stack.data)-1])) {
|
||||
gas.Add(gas, gasTable.CreateBySuicide)
|
||||
}
|
||||
}
|
||||
|
||||
if !statedb.HasSuicided(contract.Address()) {
|
||||
statedb.AddRefund(params.SuicideRefundGas)
|
||||
}
|
||||
case EXTCODESIZE:
|
||||
gas.Set(gasTable.ExtcodeSize)
|
||||
case BALANCE:
|
||||
gas.Set(gasTable.Balance)
|
||||
case SLOAD:
|
||||
gas.Set(gasTable.SLoad)
|
||||
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
|
||||
n := int(op - SWAP1 + 2)
|
||||
err := stack.require(n)
|
||||
@ -274,6 +294,8 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef
|
||||
gas.Add(gas, new(big.Int).Mul(mSize, params.LogDataGas))
|
||||
|
||||
newMemSize = calcMemSize(mStart, mSize)
|
||||
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case EXP:
|
||||
gas.Add(gas, new(big.Int).Mul(big.NewInt(int64(len(stack.data[stack.len()-2].Bytes()))), params.ExpByteGas))
|
||||
case SSTORE:
|
||||
@ -302,67 +324,100 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef
|
||||
g = params.SstoreResetGas
|
||||
}
|
||||
gas.Set(g)
|
||||
case SUICIDE:
|
||||
if !statedb.HasSuicided(contract.Address()) {
|
||||
statedb.AddRefund(params.SuicideRefundGas)
|
||||
}
|
||||
case MLOAD:
|
||||
newMemSize = calcMemSize(stack.peek(), u256(32))
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case MSTORE8:
|
||||
newMemSize = calcMemSize(stack.peek(), u256(1))
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case MSTORE:
|
||||
newMemSize = calcMemSize(stack.peek(), u256(32))
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case RETURN:
|
||||
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2])
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case SHA3:
|
||||
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-2])
|
||||
|
||||
words := toWordSize(stack.data[stack.len()-2])
|
||||
gas.Add(gas, words.Mul(words, params.Sha3WordGas))
|
||||
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case CALLDATACOPY:
|
||||
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3])
|
||||
|
||||
words := toWordSize(stack.data[stack.len()-3])
|
||||
gas.Add(gas, words.Mul(words, params.CopyGas))
|
||||
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case CODECOPY:
|
||||
newMemSize = calcMemSize(stack.peek(), stack.data[stack.len()-3])
|
||||
|
||||
words := toWordSize(stack.data[stack.len()-3])
|
||||
gas.Add(gas, words.Mul(words, params.CopyGas))
|
||||
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case EXTCODECOPY:
|
||||
gas.Set(gasTable.ExtcodeCopy)
|
||||
|
||||
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-4])
|
||||
|
||||
words := toWordSize(stack.data[stack.len()-4])
|
||||
gas.Add(gas, words.Mul(words, params.CopyGas))
|
||||
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case CREATE:
|
||||
newMemSize = calcMemSize(stack.data[stack.len()-2], stack.data[stack.len()-3])
|
||||
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
case CALL, CALLCODE:
|
||||
gas.Add(gas, stack.data[stack.len()-1])
|
||||
gas.Set(gasTable.Calls)
|
||||
|
||||
if op == CALL {
|
||||
if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
|
||||
gas.Add(gas, params.CallNewAccountGas)
|
||||
}
|
||||
}
|
||||
|
||||
if len(stack.data[stack.len()-3].Bytes()) > 0 {
|
||||
gas.Add(gas, params.CallValueTransferGas)
|
||||
}
|
||||
|
||||
x := calcMemSize(stack.data[stack.len()-6], stack.data[stack.len()-7])
|
||||
y := calcMemSize(stack.data[stack.len()-4], stack.data[stack.len()-5])
|
||||
|
||||
newMemSize = common.BigMax(x, y)
|
||||
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
|
||||
cg := callGas(gasTable, contract.Gas, gas, stack.data[stack.len()-1])
|
||||
// Replace the stack item with the new gas calculation. This means that
|
||||
// either the original item is left on the stack or the item is replaced by:
|
||||
// (availableGas - gas) * 63 / 64
|
||||
// We replace the stack item so that it's available when the opCall instruction is
|
||||
// called. This information is otherwise lost due to the dependency on *current*
|
||||
// available gas.
|
||||
stack.data[stack.len()-1] = cg
|
||||
gas.Add(gas, cg)
|
||||
|
||||
case DELEGATECALL:
|
||||
gas.Add(gas, stack.data[stack.len()-1])
|
||||
gas.Set(gasTable.Calls)
|
||||
|
||||
x := calcMemSize(stack.data[stack.len()-5], stack.data[stack.len()-6])
|
||||
y := calcMemSize(stack.data[stack.len()-3], stack.data[stack.len()-4])
|
||||
|
||||
newMemSize = common.BigMax(x, y)
|
||||
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
|
||||
cg := callGas(gasTable, contract.Gas, gas, stack.data[stack.len()-1])
|
||||
// Replace the stack item with the new gas calculation. This means that
|
||||
// either the original item is left on the stack or the item is replaced by:
|
||||
// (availableGas - gas) * 63 / 64
|
||||
// We replace the stack item so that it's available when the opCall instruction is
|
||||
// called.
|
||||
stack.data[stack.len()-1] = cg
|
||||
gas.Add(gas, cg)
|
||||
|
||||
}
|
||||
quadMemGas(mem, newMemSize, gas)
|
||||
|
||||
return newMemSize, gas, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user