package types import ( "fmt" "math" ) // Gas consumption descriptors. const ( GasIterNextCostFlatDesc = "IterNextFlat" GasValuePerByteDesc = "ValuePerByte" GasWritePerByteDesc = "WritePerByte" GasReadPerByteDesc = "ReadPerByte" GasWriteCostFlatDesc = "WriteFlat" GasReadCostFlatDesc = "ReadFlat" GasHasDesc = "Has" GasDeleteDesc = "Delete" ) // Gas measured by the SDK type Gas = uint64 // ErrorNegativeGasConsumed defines an error thrown when the amount of gas refunded results in a // negative gas consumed amount. type ErrorNegativeGasConsumed struct { Descriptor string } // ErrorOutOfGas defines an error thrown when an action results in out of gas. type ErrorOutOfGas struct { Descriptor string } // ErrorGasOverflow defines an error thrown when an action results gas consumption // unsigned integer overflow. type ErrorGasOverflow struct { Descriptor string } // GasMeter interface to track gas consumption type GasMeter interface { GasConsumed() Gas GasConsumedToLimit() Gas Limit() Gas ConsumeGas(amount Gas, descriptor string) RefundGas(amount Gas, descriptor string) IsPastLimit() bool IsOutOfGas() bool String() string } type basicGasMeter struct { limit Gas consumed Gas } // NewGasMeter returns a reference to a new basicGasMeter. func NewGasMeter(limit Gas) GasMeter { return &basicGasMeter{ limit: limit, consumed: 0, } } func (g *basicGasMeter) GasConsumed() Gas { return g.consumed } func (g *basicGasMeter) Limit() Gas { return g.limit } func (g *basicGasMeter) GasConsumedToLimit() Gas { if g.IsPastLimit() { return g.limit } return g.consumed } // addUint64Overflow performs the addition operation on two uint64 integers and // returns a boolean on whether or not the result overflows. func addUint64Overflow(a, b uint64) (uint64, bool) { if math.MaxUint64-a < b { return 0, true } return a + b, false } func (g *basicGasMeter) ConsumeGas(amount Gas, descriptor string) { var overflow bool g.consumed, overflow = addUint64Overflow(g.consumed, amount) if overflow { g.consumed = math.MaxUint64 panic(ErrorGasOverflow{descriptor}) } if g.consumed > g.limit { panic(ErrorOutOfGas{descriptor}) } } // RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the // gas consumed, the function will panic. // // Use case: This functionality enables refunding gas to the transaction or block gas pools so that // EVM-compatible chains can fully support the go-ethereum StateDb interface. // See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference. func (g *basicGasMeter) RefundGas(amount Gas, descriptor string) { if g.consumed < amount { panic(ErrorNegativeGasConsumed{Descriptor: descriptor}) } g.consumed -= amount } func (g *basicGasMeter) IsPastLimit() bool { return g.consumed > g.limit } func (g *basicGasMeter) IsOutOfGas() bool { return g.consumed >= g.limit } func (g *basicGasMeter) String() string { return fmt.Sprintf("BasicGasMeter:\n limit: %d\n consumed: %d", g.limit, g.consumed) } type infiniteGasMeter struct { consumed Gas } // NewInfiniteGasMeter returns a reference to a new infiniteGasMeter. func NewInfiniteGasMeter() GasMeter { return &infiniteGasMeter{ consumed: 0, } } func (g *infiniteGasMeter) GasConsumed() Gas { return g.consumed } func (g *infiniteGasMeter) GasConsumedToLimit() Gas { return g.consumed } func (g *infiniteGasMeter) Limit() Gas { return 0 } func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) { var overflow bool // TODO: Should we set the consumed field after overflow checking? g.consumed, overflow = addUint64Overflow(g.consumed, amount) if overflow { panic(ErrorGasOverflow{descriptor}) } } // RefundGas will deduct the given amount from the gas consumed. If the amount is greater than the // gas consumed, the function will panic. // // Use case: This functionality enables refunding gas to the trasaction or block gas pools so that // EVM-compatible chains can fully support the go-ethereum StateDb interface. // See https://github.com/cosmos/cosmos-sdk/pull/9403 for reference. func (g *infiniteGasMeter) RefundGas(amount Gas, descriptor string) { if g.consumed < amount { panic(ErrorNegativeGasConsumed{Descriptor: descriptor}) } g.consumed -= amount } func (g *infiniteGasMeter) IsPastLimit() bool { return false } func (g *infiniteGasMeter) IsOutOfGas() bool { return false } func (g *infiniteGasMeter) String() string { return fmt.Sprintf("InfiniteGasMeter:\n consumed: %d", g.consumed) } // GasConfig defines gas cost for each operation on KVStores type GasConfig struct { HasCost Gas DeleteCost Gas ReadCostFlat Gas ReadCostPerByte Gas WriteCostFlat Gas WriteCostPerByte Gas IterNextCostFlat Gas } // KVGasConfig returns a default gas config for KVStores. func KVGasConfig() GasConfig { return GasConfig{ HasCost: 1000, DeleteCost: 1000, ReadCostFlat: 1000, ReadCostPerByte: 3, WriteCostFlat: 2000, WriteCostPerByte: 30, IterNextCostFlat: 30, } } // TransientGasConfig returns a default gas config for TransientStores. func TransientGasConfig() GasConfig { return GasConfig{ HasCost: 100, DeleteCost: 100, ReadCostFlat: 100, ReadCostPerByte: 0, WriteCostFlat: 200, WriteCostPerByte: 3, IterNextCostFlat: 3, } }