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 }