github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/wasm/keeper/gas_register.go (about)

     1  package keeper
     2  
     3  import (
     4  	wasmvmtypes "github.com/CosmWasm/wasmvm/types"
     5  	storetypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/types"
     6  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
     7  	sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors"
     8  
     9  	"github.com/fibonacci-chain/fbc/x/wasm/types"
    10  )
    11  
    12  const (
    13  	// DefaultGasMultiplier is how many CosmWasm gas points = 1 Cosmos SDK gas point.
    14  	//
    15  	// CosmWasm gas strategy is documented in https://github.com/CosmWasm/cosmwasm/blob/v1.0.0-beta/docs/GAS.md.
    16  	// Cosmos SDK reference costs can be found here: https://github.com/fibonacci-chain/fbc/libs/cosmos-sdk/blob/v0.42.10/store/types/gas.go#L198-L209.
    17  	//
    18  	// The original multiplier of 100 up to CosmWasm 0.16 was based on
    19  	//     "A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io
    20  	//     Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read)"
    21  	// as well as manual Wasmer benchmarks from 2019. This was then multiplied by 150_000
    22  	// in the 0.16 -> 1.0 upgrade (https://github.com/CosmWasm/cosmwasm/pull/1120).
    23  	//
    24  	// The multiplier deserves more reproducible benchmarking and a strategy that allows easy adjustments.
    25  	// This is tracked in https://github.com/fibonacci-chain/fbc/issues/566 and https://github.com/fibonacci-chain/fbc/issues/631.
    26  	// Gas adjustments are consensus breaking but may happen in any release marked as consensus breaking.
    27  	// Do not make assumptions on how much gas an operation will consume in places that are hard to adjust,
    28  	// such as hardcoding them in contracts.
    29  	//
    30  	// Please note that all gas prices returned to wasmvm should have this multiplied.
    31  	// Benchmarks and numbers were discussed in: https://github.com/fibonacci-chain/fbc/pull/634#issuecomment-938055852
    32  	DefaultGasMultiplier uint64 = 140_000_000
    33  	// DefaultInstanceCost is how much SDK gas we charge each time we load a WASM instance.
    34  	// Creating a new instance is costly, and this helps put a recursion limit to contracts calling contracts.
    35  	// Benchmarks and numbers were discussed in: https://github.com/fibonacci-chain/fbc/pull/634#issuecomment-938056803
    36  	DefaultInstanceCost uint64 = 60_000
    37  	// DefaultCompileCost is how much SDK gas is charged *per byte* for compiling WASM code.
    38  	// Benchmarks and numbers were discussed in: https://github.com/fibonacci-chain/fbc/pull/634#issuecomment-938056803
    39  	DefaultCompileCost uint64 = 3
    40  	// DefaultEventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events.
    41  	// This is used with len(key) + len(value)
    42  	DefaultEventAttributeDataCost uint64 = 1
    43  	// DefaultContractMessageDataCost is how much SDK gas is charged *per byte* of the message that goes to the contract
    44  	// This is used with len(msg). Note that the message is deserialized in the receiving contract and this is charged
    45  	// with wasm gas already. The derserialization of results is also charged in wasmvm. I am unsure if we need to add
    46  	// additional costs here.
    47  	// Note: also used for error fields on reply, and data on reply. Maybe these should be pulled out to a different (non-zero) field
    48  	DefaultContractMessageDataCost uint64 = 0
    49  	// DefaultPerAttributeCost is how much SDK gas we charge per attribute count.
    50  	DefaultPerAttributeCost uint64 = 10
    51  	// DefaultPerCustomEventCost is how much SDK gas we charge per event count.
    52  	DefaultPerCustomEventCost uint64 = 20
    53  	// DefaultEventAttributeDataFreeTier number of bytes of total attribute data we do not charge.
    54  	DefaultEventAttributeDataFreeTier = 100
    55  )
    56  
    57  // GasRegister abstract source for gas costs
    58  type GasRegister interface {
    59  	// NewContractInstanceCosts costs to crate a new contract instance from code
    60  	NewContractInstanceCosts(pinned bool, msgLen int) sdk.Gas
    61  	// CompileCosts costs to persist and "compile" a new wasm contract
    62  	CompileCosts(byteLength int) sdk.Gas
    63  	// InstantiateContractCosts costs when interacting with a wasm contract
    64  	InstantiateContractCosts(pinned bool, msgLen int) sdk.Gas
    65  	// ReplyCosts costs to to handle a message reply
    66  	ReplyCosts(pinned bool, reply wasmvmtypes.Reply) sdk.Gas
    67  	// EventCosts costs to persist an event
    68  	EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Events) sdk.Gas
    69  	// ToWasmVMGas converts from sdk gas to wasmvm gas
    70  	ToWasmVMGas(source sdk.Gas) uint64
    71  	// FromWasmVMGas converts from wasmvm gas to sdk gas
    72  	FromWasmVMGas(source uint64) sdk.Gas
    73  }
    74  
    75  // WasmGasRegisterConfig config type
    76  type WasmGasRegisterConfig struct {
    77  	// InstanceCost costs when interacting with a wasm contract
    78  	InstanceCost sdk.Gas
    79  	// CompileCosts costs to persist and "compile" a new wasm contract
    80  	CompileCost sdk.Gas
    81  	// GasMultiplier is how many cosmwasm gas points = 1 sdk gas point
    82  	// SDK reference costs can be found here: https://github.com/fibonacci-chain/fbc/libs/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164
    83  	GasMultiplier sdk.Gas
    84  	// EventPerAttributeCost is how much SDK gas is charged *per byte* for attribute data in events.
    85  	// This is used with len(key) + len(value)
    86  	EventPerAttributeCost sdk.Gas
    87  	// EventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events.
    88  	// This is used with len(key) + len(value)
    89  	EventAttributeDataCost sdk.Gas
    90  	// EventAttributeDataFreeTier number of bytes of total attribute data that is free of charge
    91  	EventAttributeDataFreeTier uint64
    92  	// ContractMessageDataCost SDK gas charged *per byte* of the message that goes to the contract
    93  	// This is used with len(msg)
    94  	ContractMessageDataCost sdk.Gas
    95  	// CustomEventCost cost per custom event
    96  	CustomEventCost uint64
    97  }
    98  
    99  // DefaultGasRegisterConfig default values
   100  func DefaultGasRegisterConfig() WasmGasRegisterConfig {
   101  	return WasmGasRegisterConfig{
   102  		InstanceCost:               DefaultInstanceCost,
   103  		CompileCost:                DefaultCompileCost,
   104  		GasMultiplier:              DefaultGasMultiplier,
   105  		EventPerAttributeCost:      DefaultPerAttributeCost,
   106  		CustomEventCost:            DefaultPerCustomEventCost,
   107  		EventAttributeDataCost:     DefaultEventAttributeDataCost,
   108  		EventAttributeDataFreeTier: DefaultEventAttributeDataFreeTier,
   109  		ContractMessageDataCost:    DefaultContractMessageDataCost,
   110  	}
   111  }
   112  
   113  // WasmGasRegister implements GasRegister interface
   114  type WasmGasRegister struct {
   115  	c WasmGasRegisterConfig
   116  }
   117  
   118  // NewDefaultWasmGasRegister creates instance with default values
   119  func NewDefaultWasmGasRegister() WasmGasRegister {
   120  	return NewWasmGasRegister(DefaultGasRegisterConfig())
   121  }
   122  
   123  // NewWasmGasRegister constructor
   124  func NewWasmGasRegister(c WasmGasRegisterConfig) WasmGasRegister {
   125  	if c.GasMultiplier == 0 {
   126  		panic(sdkerrors.Wrap(sdkerrors.ErrLogic, "GasMultiplier can not be 0"))
   127  	}
   128  	return WasmGasRegister{
   129  		c: c,
   130  	}
   131  }
   132  
   133  // NewContractInstanceCosts costs to crate a new contract instance from code
   134  func (g WasmGasRegister) NewContractInstanceCosts(pinned bool, msgLen int) storetypes.Gas {
   135  	return g.InstantiateContractCosts(pinned, msgLen)
   136  }
   137  
   138  // CompileCosts costs to persist and "compile" a new wasm contract
   139  func (g WasmGasRegister) CompileCosts(byteLength int) storetypes.Gas {
   140  	if byteLength < 0 {
   141  		panic(sdkerrors.Wrap(types.ErrInvalid, "negative length"))
   142  	}
   143  	return g.c.CompileCost * uint64(byteLength)
   144  }
   145  
   146  // InstantiateContractCosts costs when interacting with a wasm contract
   147  func (g WasmGasRegister) InstantiateContractCosts(pinned bool, msgLen int) sdk.Gas {
   148  	if msgLen < 0 {
   149  		panic(sdkerrors.Wrap(types.ErrInvalid, "negative length"))
   150  	}
   151  	dataCosts := sdk.Gas(msgLen) * g.c.ContractMessageDataCost
   152  	if pinned {
   153  		return dataCosts
   154  	}
   155  	return g.c.InstanceCost + dataCosts
   156  }
   157  
   158  // ReplyCosts costs to to handle a message reply
   159  func (g WasmGasRegister) ReplyCosts(pinned bool, reply wasmvmtypes.Reply) sdk.Gas {
   160  	var eventGas sdk.Gas
   161  	msgLen := len(reply.Result.Err)
   162  	if reply.Result.Ok != nil {
   163  		msgLen += len(reply.Result.Ok.Data)
   164  		var attrs []wasmvmtypes.EventAttribute
   165  		for _, e := range reply.Result.Ok.Events {
   166  			eventGas += sdk.Gas(len(e.Type)) * g.c.EventAttributeDataCost
   167  			attrs = append(attrs, e.Attributes...)
   168  		}
   169  		// apply free tier on the whole set not per event
   170  		eventGas += g.EventCosts(attrs, nil)
   171  	}
   172  	return eventGas + g.InstantiateContractCosts(pinned, msgLen)
   173  }
   174  
   175  // EventCosts costs to persist an event
   176  func (g WasmGasRegister) EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Events) sdk.Gas {
   177  	gas, remainingFreeTier := g.eventAttributeCosts(attrs, g.c.EventAttributeDataFreeTier)
   178  	for _, e := range events {
   179  		gas += g.c.CustomEventCost
   180  		gas += sdk.Gas(len(e.Type)) * g.c.EventAttributeDataCost // no free tier with event type
   181  		var attrCost sdk.Gas
   182  		attrCost, remainingFreeTier = g.eventAttributeCosts(e.Attributes, remainingFreeTier)
   183  		gas += attrCost
   184  	}
   185  	return gas
   186  }
   187  
   188  func (g WasmGasRegister) eventAttributeCosts(attrs []wasmvmtypes.EventAttribute, freeTier uint64) (sdk.Gas, uint64) {
   189  	if len(attrs) == 0 {
   190  		return 0, freeTier
   191  	}
   192  	var storedBytes uint64
   193  	for _, l := range attrs {
   194  		storedBytes += uint64(len(l.Key)) + uint64(len(l.Value))
   195  	}
   196  	storedBytes, freeTier = calcWithFreeTier(storedBytes, freeTier)
   197  	// total Length * costs + attribute count * costs
   198  	r := sdk.NewIntFromUint64(g.c.EventAttributeDataCost).Mul(sdk.NewIntFromUint64(storedBytes)).
   199  		Add(sdk.NewIntFromUint64(g.c.EventPerAttributeCost).Mul(sdk.NewIntFromUint64(uint64(len(attrs)))))
   200  	if !r.IsUint64() {
   201  		panic(sdk.ErrorOutOfGas{Descriptor: "overflow"})
   202  	}
   203  	return r.Uint64(), freeTier
   204  }
   205  
   206  // apply free tier
   207  func calcWithFreeTier(storedBytes uint64, freeTier uint64) (uint64, uint64) {
   208  	if storedBytes <= freeTier {
   209  		return 0, freeTier - storedBytes
   210  	}
   211  	storedBytes -= freeTier
   212  	return storedBytes, 0
   213  }
   214  
   215  // ToWasmVMGas convert to wasmVM contract runtime gas unit
   216  func (g WasmGasRegister) ToWasmVMGas(source storetypes.Gas) uint64 {
   217  	x := source * g.c.GasMultiplier
   218  	if x < source {
   219  		panic(sdk.ErrorOutOfGas{Descriptor: "overflow"})
   220  	}
   221  	return x
   222  }
   223  
   224  // FromWasmVMGas converts to SDK gas unit
   225  func (g WasmGasRegister) FromWasmVMGas(source uint64) sdk.Gas {
   226  	return source / g.c.GasMultiplier
   227  }