github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/wasm/types/types.go (about) 1 package types 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 wasmvmtypes "github.com/CosmWasm/wasmvm/types" 9 codectypes "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec/types" 10 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 11 sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors" 12 "github.com/gogo/protobuf/proto" 13 ) 14 15 const ( 16 defaultMemoryCacheSize uint32 = 100 // in MiB 17 defaultSmartQueryGasLimit uint64 = 3_000_000 18 defaultContractDebugMode = false 19 20 // ContractAddrLen defines a valid address length for contracts 21 ContractAddrLen = 32 22 // SDKAddrLen defines a valid address length that was used in sdk address generation 23 SDKAddrLen = 20 24 ) 25 26 func (m Model) ValidateBasic() error { 27 if len(m.Key) == 0 { 28 return sdkerrors.Wrap(ErrEmpty, "key") 29 } 30 return nil 31 } 32 33 func (c CodeInfo) ValidateBasic() error { 34 if len(c.CodeHash) == 0 { 35 return sdkerrors.Wrap(ErrEmpty, "code hash") 36 } 37 if _, err := sdk.AccAddressFromBech32(c.Creator); err != nil { 38 return sdkerrors.Wrap(err, "creator") 39 } 40 if err := c.InstantiateConfig.ValidateBasic(); err != nil { 41 return sdkerrors.Wrap(err, "instantiate config") 42 } 43 return nil 44 } 45 46 // NewCodeInfo fills a new CodeInfo struct 47 func NewCodeInfo(codeHash []byte, creator sdk.AccAddress, instantiatePermission AccessConfig) CodeInfo { 48 return CodeInfo{ 49 CodeHash: codeHash, 50 Creator: creator.String(), 51 InstantiateConfig: instantiatePermission, 52 } 53 } 54 55 var AllCodeHistoryTypes = []ContractCodeHistoryOperationType{ContractCodeHistoryOperationTypeGenesis, ContractCodeHistoryOperationTypeInit, ContractCodeHistoryOperationTypeMigrate} 56 57 // NewContractInfo creates a new instance of a given WASM contract info 58 func NewContractInfo(codeID uint64, creator, admin sdk.AccAddress, label string, createdAt *AbsoluteTxPosition) ContractInfo { 59 var adminAddr string 60 if !admin.Empty() { 61 adminAddr = admin.String() 62 } 63 return ContractInfo{ 64 CodeID: codeID, 65 Creator: creator.String(), 66 Admin: adminAddr, 67 Label: label, 68 Created: createdAt, 69 } 70 } 71 72 // validatable is an optional interface that can be implemented by an ContractInfoExtension to enable validation 73 type validatable interface { 74 ValidateBasic() error 75 } 76 77 // ValidateBasic does syntax checks on the data. If an extension is set and has the `ValidateBasic() error` method, then 78 // the method is called as well. It is recommend to implement `ValidateBasic` so that the data is verified in the setter 79 // but also in the genesis import process. 80 func (c *ContractInfo) ValidateBasic() error { 81 if c.CodeID == 0 { 82 return sdkerrors.Wrap(ErrEmpty, "code id") 83 } 84 if _, err := sdk.AccAddressFromBech32(c.Creator); err != nil { 85 return sdkerrors.Wrap(err, "creator") 86 } 87 if len(c.Admin) != 0 { 88 if _, err := sdk.AccAddressFromBech32(c.Admin); err != nil { 89 return sdkerrors.Wrap(err, "admin") 90 } 91 } 92 if err := validateLabel(c.Label); err != nil { 93 return sdkerrors.Wrap(err, "label") 94 } 95 if c.Extension == nil { 96 return nil 97 } 98 99 e, ok := c.Extension.GetCachedValue().(validatable) 100 if !ok { 101 return nil 102 } 103 if err := e.ValidateBasic(); err != nil { 104 return sdkerrors.Wrap(err, "extension") 105 } 106 return nil 107 } 108 109 // SetExtension set new extension data. Calls `ValidateBasic() error` on non nil values when method is implemented by 110 // the extension. 111 func (c *ContractInfo) SetExtension(ext ContractInfoExtension) error { 112 if ext == nil { 113 c.Extension = nil 114 return nil 115 } 116 if e, ok := ext.(validatable); ok { 117 if err := e.ValidateBasic(); err != nil { 118 return err 119 } 120 } 121 any, err := codectypes.NewAnyWithValue(ext) 122 if err != nil { 123 return sdkerrors.Wrap(sdkerrors.ErrPackAny, err.Error()) 124 } 125 126 c.Extension = any 127 return nil 128 } 129 130 // ReadExtension copies the extension value to the pointer passed as argument so that there is no need to cast 131 // For example with a custom extension of type `MyContractDetails` it will look as following: 132 // 133 // var d MyContractDetails 134 // if err := info.ReadExtension(&d); err != nil { 135 // return nil, sdkerrors.Wrap(err, "extension") 136 // } 137 func (c *ContractInfo) ReadExtension(e ContractInfoExtension) error { 138 rv := reflect.ValueOf(e) 139 if rv.Kind() != reflect.Ptr || rv.IsNil() { 140 return sdkerrors.Wrap(sdkerrors.ErrInvalidType, "not a pointer") 141 } 142 if c.Extension == nil { 143 return nil 144 } 145 146 cached := c.Extension.GetCachedValue() 147 elem := reflect.ValueOf(cached).Elem() 148 if !elem.Type().AssignableTo(rv.Elem().Type()) { 149 return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "extension is of type %s but argument of %s", elem.Type(), rv.Elem().Type()) 150 } 151 rv.Elem().Set(elem) 152 return nil 153 } 154 155 func (c ContractInfo) InitialHistory(initMsg []byte) ContractCodeHistoryEntry { 156 return ContractCodeHistoryEntry{ 157 Operation: ContractCodeHistoryOperationTypeInit, 158 CodeID: c.CodeID, 159 Updated: c.Created, 160 Msg: initMsg, 161 } 162 } 163 164 func (c *ContractInfo) AddMigration(ctx sdk.Context, codeID uint64, msg []byte) ContractCodeHistoryEntry { 165 h := ContractCodeHistoryEntry{ 166 Operation: ContractCodeHistoryOperationTypeMigrate, 167 CodeID: codeID, 168 Updated: NewAbsoluteTxPosition(ctx), 169 Msg: msg, 170 } 171 c.CodeID = codeID 172 return h 173 } 174 175 // ResetFromGenesis resets contracts timestamp and history. 176 func (c *ContractInfo) ResetFromGenesis(ctx sdk.Context) ContractCodeHistoryEntry { 177 c.Created = NewAbsoluteTxPosition(ctx) 178 return ContractCodeHistoryEntry{ 179 Operation: ContractCodeHistoryOperationTypeGenesis, 180 CodeID: c.CodeID, 181 Updated: c.Created, 182 } 183 } 184 185 // AdminAddr convert into sdk.AccAddress or nil when not set 186 func (c *ContractInfo) AdminAddr() sdk.AccAddress { 187 if c.Admin == "" { 188 return nil 189 } 190 admin, err := sdk.AccAddressFromBech32(c.Admin) 191 if err != nil { // should never happen 192 panic(err.Error()) 193 } 194 return admin 195 } 196 197 // ContractInfoExtension defines the extension point for custom data to be stored with a contract info 198 type ContractInfoExtension interface { 199 proto.Message 200 String() string 201 } 202 203 var _ codectypes.UnpackInterfacesMessage = &ContractInfo{} 204 205 // UnpackInterfaces implements codectypes.UnpackInterfaces 206 func (c *ContractInfo) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { 207 var details ContractInfoExtension 208 if err := unpacker.UnpackAny(c.Extension, &details); err != nil { 209 return err 210 } 211 return codectypes.UnpackInterfaces(details, unpacker) 212 } 213 214 // NewAbsoluteTxPosition gets a block position from the context 215 func NewAbsoluteTxPosition(ctx sdk.Context) *AbsoluteTxPosition { 216 // we must safely handle nil gas meters 217 var index uint64 218 meter := ctx.BlockGasMeter() 219 if meter != nil { 220 index = meter.GasConsumed() 221 } 222 height := ctx.BlockHeight() 223 if height < 0 { 224 panic(fmt.Sprintf("unsupported height: %d", height)) 225 } 226 return &AbsoluteTxPosition{ 227 BlockHeight: uint64(height), 228 TxIndex: index, 229 } 230 } 231 232 // LessThan can be used to sort 233 func (a *AbsoluteTxPosition) LessThan(b *AbsoluteTxPosition) bool { 234 if a == nil { 235 return true 236 } 237 if b == nil { 238 return false 239 } 240 return a.BlockHeight < b.BlockHeight || (a.BlockHeight == b.BlockHeight && a.TxIndex < b.TxIndex) 241 } 242 243 // AbsoluteTxPositionLen number of elements in byte representation 244 const AbsoluteTxPositionLen = 16 245 246 // Bytes encodes the object into a 16 byte representation with big endian block height and tx index. 247 func (a *AbsoluteTxPosition) Bytes() []byte { 248 if a == nil { 249 panic("object must not be nil") 250 } 251 r := make([]byte, AbsoluteTxPositionLen) 252 copy(r[0:], sdk.Uint64ToBigEndian(a.BlockHeight)) 253 copy(r[8:], sdk.Uint64ToBigEndian(a.TxIndex)) 254 return r 255 } 256 257 // NewEnv initializes the environment for a contract instance 258 func NewEnv(ctx sdk.Context, contractAddr sdk.AccAddress) wasmvmtypes.Env { 259 // safety checks before casting below 260 if ctx.BlockHeight() < 0 { 261 panic("Block height must never be negative") 262 } 263 nano := ctx.BlockTime().UnixNano() 264 if nano < 1 { 265 panic("Block (unix) time must never be empty or negative ") 266 } 267 268 env := wasmvmtypes.Env{ 269 Block: wasmvmtypes.BlockInfo{ 270 Height: uint64(ctx.BlockHeight()), 271 Time: uint64(nano), 272 ChainID: ctx.ChainID(), 273 }, 274 Contract: wasmvmtypes.ContractInfo{ 275 Address: contractAddr.String(), 276 }, 277 } 278 if txCounter, ok := TXCounter(ctx); ok { 279 env.Transaction = &wasmvmtypes.TransactionInfo{Index: txCounter} 280 } 281 return env 282 } 283 284 // NewInfo initializes the MessageInfo for a contract instance 285 func NewInfo(creator sdk.AccAddress, deposit sdk.CoinAdapters) wasmvmtypes.MessageInfo { 286 return wasmvmtypes.MessageInfo{ 287 Sender: creator.String(), 288 Funds: NewWasmCoins(deposit), 289 } 290 } 291 292 // NewWasmCoins translates between Cosmos SDK coins and Wasm coins 293 func NewWasmCoins(cosmosCoins sdk.CoinAdapters) (wasmCoins []wasmvmtypes.Coin) { 294 for _, coin := range cosmosCoins { 295 wasmCoin := wasmvmtypes.Coin{ 296 Denom: coin.Denom, 297 Amount: coin.Amount.String(), 298 } 299 wasmCoins = append(wasmCoins, wasmCoin) 300 } 301 return wasmCoins 302 } 303 304 // WasmConfig is the extra config required for wasm 305 type WasmConfig struct { 306 // SimulationGasLimit is the max gas to be used in a tx simulation call. 307 // When not set the consensus max block gas is used instead 308 SimulationGasLimit *uint64 309 // SimulationGasLimit is the max gas to be used in a smart query contract call 310 SmartQueryGasLimit uint64 311 // MemoryCacheSize in MiB not bytes 312 MemoryCacheSize uint32 313 // ContractDebugMode log what contract print 314 ContractDebugMode bool 315 } 316 317 // DefaultWasmConfig returns the default settings for WasmConfig 318 func DefaultWasmConfig() WasmConfig { 319 return WasmConfig{ 320 SmartQueryGasLimit: defaultSmartQueryGasLimit, 321 MemoryCacheSize: defaultMemoryCacheSize, 322 ContractDebugMode: defaultContractDebugMode, 323 } 324 } 325 326 // VerifyAddressLen ensures that the address matches the expected length 327 func VerifyAddressLen() func(addr []byte) error { 328 return func(addr []byte) error { 329 if len(addr) != ContractAddrLen && len(addr) != SDKAddrLen { 330 return sdkerrors.ErrInvalidAddress 331 } 332 return nil 333 } 334 } 335 336 // IsSubset will return true if the caller is the same as the superset, 337 // or if the caller is more restrictive than the superset. 338 func (a AccessConfig) IsSubset(superSet AccessConfig) bool { 339 switch superSet.Permission { 340 case AccessTypeEverybody: 341 // Everything is a subset of this 342 return a.Permission != AccessTypeUnspecified 343 case AccessTypeNobody: 344 // Only an exact match is a subset of this 345 return a.Permission == AccessTypeNobody 346 case AccessTypeOnlyAddress: 347 // A subset addrs match or nobody 348 if a.Permission == AccessTypeNobody { 349 return true 350 } 351 if a.Permission == AccessTypeOnlyAddress { 352 m := make(map[string]struct{}) 353 for _, addr := range strings.Split(superSet.Address, ",") { 354 m[addr] = struct{}{} 355 } 356 for _, addr := range strings.Split(a.Address, ",") { 357 if _, ok := m[addr]; !ok { 358 return false 359 } 360 } 361 return true 362 } 363 return false 364 default: 365 return false 366 } 367 }