From 3a015480d601353dd0bc192a5fa4d816be0927e7 Mon Sep 17 00:00:00 2001 From: slandymani Date: Wed, 9 Oct 2024 23:27:53 +0300 Subject: [PATCH] add wasm module --- database/schema/14-wasm.sql | 53 ++++++ database/types/wasm.go | 205 ++++++++++++++++++++ database/utils/wasm.go | 35 ++++ database/wasm.go | 249 ++++++++++++++++++++++++ modules/wasm/handle_genesis.go | 113 +++++++++++ modules/wasm/handle_msg.go | 270 +++++++++++++++++++++++++++ modules/wasm/module.go | 36 ++++ modules/wasm/source/local/source.go | 139 ++++++++++++++ modules/wasm/source/remote/source.go | 121 ++++++++++++ modules/wasm/source/source.go | 12 ++ modules/wasm/utils_contracts.go | 131 +++++++++++++ types/wasm.go | 151 +++++++++++++++ 12 files changed, 1515 insertions(+) create mode 100644 database/schema/14-wasm.sql create mode 100644 database/types/wasm.go create mode 100644 database/utils/wasm.go create mode 100644 database/wasm.go create mode 100644 modules/wasm/handle_genesis.go create mode 100644 modules/wasm/handle_msg.go create mode 100644 modules/wasm/module.go create mode 100644 modules/wasm/source/local/source.go create mode 100644 modules/wasm/source/remote/source.go create mode 100644 modules/wasm/source/source.go create mode 100644 modules/wasm/utils_contracts.go create mode 100644 types/wasm.go diff --git a/database/schema/14-wasm.sql b/database/schema/14-wasm.sql new file mode 100644 index 000000000..7fdcea6ed --- /dev/null +++ b/database/schema/14-wasm.sql @@ -0,0 +1,53 @@ +CREATE TYPE ACCESS_CONFIG AS + ( + permission INT, + addresses TEXT + ); + +CREATE TABLE wasm_params +( + one_row_id BOOLEAN NOT NULL DEFAULT TRUE PRIMARY KEY, + code_upload_access ACCESS_CONFIG NOT NULL, + instantiate_default_permission INT NOT NULL, + height BIGINT NOT NULL +); + +CREATE TABLE wasm_code +( + sender TEXT NULL, + byte_code BYTEA NOT NULL, + instantiate_permission ACCESS_CONFIG NULL, + code_id BIGINT NOT NULL UNIQUE, + height BIGINT NOT NULL +); +CREATE INDEX wasm_code_height_index ON wasm_code (height); + +CREATE TABLE wasm_contract +( + sender TEXT NULL, + creator TEXT NOT NULL REFERENCES account (address), + admin TEXT NULL, + code_id BIGINT NOT NULL REFERENCES wasm_code (code_id), + label TEXT NULL, + raw_contract_message JSONB NOT NULL DEFAULT '{}'::JSONB, + funds COIN[] NOT NULL DEFAULT '{}', + contract_address TEXT NOT NULL UNIQUE, + data TEXT NULL, + instantiated_at TIMESTAMP NOT NULL, + contract_info_extension TEXT NULL, + contract_states JSONB NOT NULL DEFAULT '{}'::JSONB, + height BIGINT NOT NULL +); +CREATE INDEX wasm_contract_height_index ON wasm_contract (height); + +CREATE TABLE wasm_execute_contract +( + sender TEXT NOT NULL, + contract_address TEXT NOT NULL REFERENCES wasm_contract (contract_address), + raw_contract_message JSONB NOT NULL DEFAULT '{}'::JSONB, + funds COIN[] NOT NULL DEFAULT '{}', + data TEXT NULL, + executed_at TIMESTAMP NOT NULL, + height BIGINT NOT NULL +); +CREATE INDEX execute_contract_height_index ON wasm_execute_contract (height); \ No newline at end of file diff --git a/database/types/wasm.go b/database/types/wasm.go new file mode 100644 index 000000000..b05456004 --- /dev/null +++ b/database/types/wasm.go @@ -0,0 +1,205 @@ +package types + +import ( + "database/sql/driver" + "fmt" + "time" + + wasmtypes "github.com/ODIN-PROTOCOL/wasmd/x/wasm/types" +) + +// DbAccessConfig represents the information stored inside the database about a single access_config +type DbAccessConfig struct { + Permission int `db:"permission"` + Addresses []string `db:"addresses"` +} + +// NewDbAccessConfig builds a DbAccessConfig starting from an CosmWasm type AccessConfig +func NewDbAccessConfig(accessCfg *wasmtypes.AccessConfig) DbAccessConfig { + return DbAccessConfig{ + Permission: int(accessCfg.Permission), + Addresses: accessCfg.Addresses, + } +} + +// Value implements driver.Valuer +func (cfg *DbAccessConfig) Value() (driver.Value, error) { + if cfg != nil { + return fmt.Sprintf("(%d,%s)", cfg.Permission, cfg.Addresses), nil + } + + return fmt.Sprintf("(%d,%s)", wasmtypes.AccessTypeUnspecified, ""), nil +} + +// Equal tells whether a and b represent the same access_config +func (cfg *DbAccessConfig) Equal(b *DbAccessConfig) bool { + return cfg.Permission == b.Permission +} + +// ===================== Params ===================== + +// WasmParams represents the CosmWasm code in x/wasm module +type WasmParams struct { + CodeUploadAccess *DbAccessConfig `db:"code_upload_access"` + InstantiateDefaultPermission int32 `db:"instantiate_default_permission"` + Height int64 `db:"height"` +} + +// NewWasmParams allows to build a new x/wasm params instance +func NewWasmParams( + codeUploadAccess *DbAccessConfig, instantiateDefaultPermission int32, height int64, +) WasmParams { + return WasmParams{ + CodeUploadAccess: codeUploadAccess, + InstantiateDefaultPermission: instantiateDefaultPermission, + Height: height, + } +} + +// ===================== Code ===================== + +// WasmCodeRow represents a single row inside the "wasm_code" table +type WasmCodeRow struct { + Sender string `db:"sender"` + WasmByteCode string `db:"wasm_byte_code"` + InstantiatePermission *DbAccessConfig `db:"instantiate_permission"` + CodeID int64 `db:"code_id"` + Height int64 `db:"height"` +} + +// NewWasmCodeRow allows to easily create a new NewWasmCodeRow +func NewWasmCodeRow( + sender string, + wasmByteCode string, + instantiatePermission *DbAccessConfig, + codeID int64, + height int64, +) WasmCodeRow { + return WasmCodeRow{ + Sender: sender, + WasmByteCode: wasmByteCode, + InstantiatePermission: instantiatePermission, + CodeID: codeID, + Height: height, + } +} + +// Equals return true if one WasmCodeRow representing the same row as the original one +func (a WasmCodeRow) Equals(b WasmCodeRow) bool { + return a.Sender == b.Sender && + a.WasmByteCode == b.WasmByteCode && + a.InstantiatePermission.Equal(b.InstantiatePermission) && + a.CodeID == b.CodeID && + a.Height == b.Height +} + +// ===================== Contract ===================== + +// WasmContractRow represents a single row inside the "wasm_contract" table +type WasmContractRow struct { + Sender string `db:"sender"` + Creator string `db:"creator"` + Admin string `db:"admin"` + CodeID int64 `db:"code_id"` + Label string `db:"label"` + RawContractMessage string `db:"raw_contract_message"` + Funds *DbCoins `db:"funds"` + ContractAddress string `db:"contract_address"` + Data string `db:"data"` + InstantiatedAt time.Time `db:"instantiated_at"` + ContractInfoExtension string `db:"contract_info_extension"` + ContractStates string `db:"contract_states"` + Height int64 `db:"height"` +} + +// NewWasmContractRow allows to easily create a new WasmContractRow +func NewWasmContractRow( + sender string, + admin string, + codeID int64, + label string, + rawContractMessage string, + funds *DbCoins, + contractAddress string, + data string, + instantiatedAt time.Time, + creator string, + contractInfoExtension string, + height int64, +) WasmContractRow { + return WasmContractRow{ + Sender: sender, + Admin: admin, + CodeID: codeID, + Label: label, + RawContractMessage: rawContractMessage, + Funds: funds, + ContractAddress: contractAddress, + Data: data, + InstantiatedAt: instantiatedAt, + Creator: creator, + ContractInfoExtension: contractInfoExtension, + Height: height, + } +} + +// Equals return true if one WasmContractRow representing the same row as the original one +func (a WasmContractRow) Equals(b WasmContractRow) bool { + return a.Sender == b.Sender && + a.Creator == b.Creator && + a.Admin == b.Admin && + a.CodeID == b.CodeID && + a.Label == b.Label && + a.RawContractMessage == b.RawContractMessage && + a.Funds.Equal(b.Funds) && + a.ContractAddress == b.ContractAddress && + a.Data == b.Data && + a.InstantiatedAt == b.InstantiatedAt && + a.ContractInfoExtension == b.ContractInfoExtension && + a.Height == b.Height +} + +// ===================== Execute Contract ===================== + +// WasmExecuteContractRow represents a single row inside the "wasm_execute_contract" table +type WasmExecuteContractRow struct { + Sender string `db:"sender"` + ContractAddress string `db:"contract_address"` + RawContractMessage string `db:"raw_contract_message"` + Funds *DbCoins `db:"funds"` + Data string `db:"data"` + ExecutedAt time.Time `db:"executed_at"` + Height int64 `db:"height"` +} + +// NewWasmExecuteContractRow allows to easily create a new WasmExecuteContractRow +func NewWasmExecuteContractRow( + sender string, + contractAddress string, + rawContractMessage string, + funds *DbCoins, + data string, + executedAt time.Time, + height int64, +) WasmExecuteContractRow { + return WasmExecuteContractRow{ + Sender: sender, + RawContractMessage: rawContractMessage, + Funds: funds, + ContractAddress: contractAddress, + Data: data, + ExecutedAt: executedAt, + Height: height, + } +} + +// Equals return true if one WasmExecuteContractRow representing the same row as the original one +func (a WasmExecuteContractRow) Equals(b WasmExecuteContractRow) bool { + return a.Sender == b.Sender && + a.ContractAddress == b.ContractAddress && + a.RawContractMessage == b.RawContractMessage && + a.Funds.Equal(b.Funds) && + a.Data == b.Data && + a.ExecutedAt == b.ExecutedAt && + a.Height == b.Height +} diff --git a/database/utils/wasm.go b/database/utils/wasm.go new file mode 100644 index 000000000..bc2f9e6a1 --- /dev/null +++ b/database/utils/wasm.go @@ -0,0 +1,35 @@ +package utils + +import "github.com/forbole/callisto/v4/types" + +func SplitWasmContracts(contracts []types.WasmContract, paramsNumber int) [][]types.WasmContract { + maxBalancesPerSlice := maxPostgreSQLParams / paramsNumber + slices := make([][]types.WasmContract, len(contracts)/maxBalancesPerSlice+1) + + sliceIndex := 0 + for index, contract := range contracts { + slices[sliceIndex] = append(slices[sliceIndex], contract) + + if index > 0 && index%(maxBalancesPerSlice-1) == 0 { + sliceIndex++ + } + } + + return slices +} + +func SplitWasmExecuteContracts(executeContracts []types.WasmExecuteContract, paramsNumber int) [][]types.WasmExecuteContract { + maxBalancesPerSlice := maxPostgreSQLParams / paramsNumber + slices := make([][]types.WasmExecuteContract, len(executeContracts)/maxBalancesPerSlice+1) + + sliceIndex := 0 + for index, executeContract := range executeContracts { + slices[sliceIndex] = append(slices[sliceIndex], executeContract) + + if index > 0 && index%(maxBalancesPerSlice-1) == 0 { + sliceIndex++ + } + } + + return slices +} diff --git a/database/wasm.go b/database/wasm.go new file mode 100644 index 000000000..d6414ccdb --- /dev/null +++ b/database/wasm.go @@ -0,0 +1,249 @@ +package database + +import ( + "fmt" + + dbtypes "github.com/forbole/callisto/v4/database/types" + dbutils "github.com/forbole/callisto/v4/database/utils" + "github.com/forbole/callisto/v4/types" + "github.com/lib/pq" +) + +// SaveWasmParams allows to store the wasm params +func (db *Db) SaveWasmParams(params types.WasmParams) error { + stmt := ` +INSERT INTO wasm_params(code_upload_access, instantiate_default_permission, height) +VALUES ($1, $2, $3) +ON CONFLICT (one_row_id) DO UPDATE + SET code_upload_access = excluded.code_upload_access, + instantiate_default_permission = excluded.instantiate_default_permission, + height = excluded.height +WHERE wasm_params.height <= excluded.height +` + accessConfig := dbtypes.NewDbAccessConfig(params.CodeUploadAccess) + cfgValue, _ := accessConfig.Value() + + _, err := db.SQL.Exec(stmt, + cfgValue, params.InstantiateDefaultPermission, params.Height, + ) + if err != nil { + return fmt.Errorf("error while saving wasm params: %s", err) + } + + return nil +} + +// SaveWasmCode allows to store a single wasm code +func (db *Db) SaveWasmCode(wasmCode types.WasmCode) error { + return db.SaveWasmCodes([]types.WasmCode{wasmCode}) +} + +// SaveWasmCodes allows to store the wasm code slice +func (db *Db) SaveWasmCodes(wasmCodes []types.WasmCode) error { + stmt := ` +INSERT INTO wasm_code(sender, byte_code, instantiate_permission, code_id, height) +VALUES ` + + if len(wasmCodes) == 0 { + return fmt.Errorf("wasm codes list is empty") + } + + var args []interface{} + for i, code := range wasmCodes { + ii := i * 5 + + var accessConfig dbtypes.DbAccessConfig + if code.InstantiatePermission != nil { + accessConfig = dbtypes.NewDbAccessConfig(code.InstantiatePermission) + } + + cfgValue, _ := accessConfig.Value() + + stmt += fmt.Sprintf("($%d, $%d, $%d, $%d, $%d),", ii+1, ii+2, ii+3, ii+4, ii+5) + args = append(args, code.Sender, code.WasmByteCode, cfgValue, code.CodeID, code.Height) + } + + stmt = stmt[:len(stmt)-1] // Remove trailing "," + + stmt += ` + ON CONFLICT (code_id) DO UPDATE + SET sender = excluded.sender, + byte_code = excluded.byte_code, + instantiate_permission = excluded.instantiate_permission, + height = excluded.height + WHERE wasm_code.height <= excluded.height` + + _, err := db.SQL.Exec(stmt, args...) + if err != nil { + return fmt.Errorf("error while saving wasm code: %s", err) + } + + return nil +} + +// SaveWasmContracts allows to store the wasm contract slice +func (db *Db) SaveWasmContracts(contracts []types.WasmContract) error { + paramsNumber := 13 + slices := dbutils.SplitWasmContracts(contracts, paramsNumber) + + for _, contracts := range slices { + if len(contracts) == 0 { + continue + } + + err := db.saveWasmContracts(paramsNumber, contracts) + if err != nil { + return fmt.Errorf("error while storing contracts: %s", err) + } + } + + return nil +} + +func (db *Db) saveWasmContracts(paramsNumber int, wasmContracts []types.WasmContract) error { + + stmt := ` +INSERT INTO wasm_contract +(sender, creator, admin, code_id, label, raw_contract_message, funds, contract_address, +data, instantiated_at, contract_info_extension, contract_states, height) +VALUES ` + + var args []interface{} + var accounts []types.Account + + for i, contract := range wasmContracts { + ii := i * paramsNumber + stmt += fmt.Sprintf("($%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d),", + ii+1, ii+2, ii+3, ii+4, ii+5, ii+6, ii+7, ii+8, ii+9, ii+10, ii+11, ii+12, ii+13) + args = append(args, + contract.Sender, contract.Creator, contract.Admin, contract.CodeID, contract.Label, string(contract.RawContractMsg), + pq.Array(dbtypes.NewDbCoins(contract.Funds)), contract.ContractAddress, contract.Data, + contract.InstantiatedAt, contract.ContractInfoExtension, string(contract.ContractStates), contract.Height, + ) + accounts = append(accounts, types.NewAccount(contract.Creator), types.NewAccount(contract.Sender)) + } + + err := db.SaveAccounts(accounts) + if err != nil { + return fmt.Errorf("error while storing wasm contract creator account: %s", err) + } + + stmt = stmt[:len(stmt)-1] // Remove trailing "," + stmt += ` + ON CONFLICT (contract_address) DO UPDATE + SET sender = excluded.sender, + creator = excluded.creator, + admin = excluded.admin, + code_id = excluded.code_id, + label = excluded.label, + raw_contract_message = excluded.raw_contract_message, + funds = excluded.funds, + data = excluded.data, + instantiated_at = excluded.instantiated_at, + contract_info_extension = excluded.contract_info_extension, + contract_states = excluded.contract_states, + height = excluded.height + WHERE wasm_contract.height <= excluded.height` + + _, err = db.SQL.Exec(stmt, args...) + if err != nil { + return fmt.Errorf("error while saving wasm contracts: %s", err) + } + + return nil +} + +// SaveWasmExecuteContract allows to store the wasm contract +func (db *Db) SaveWasmExecuteContract(wasmExecuteContract types.WasmExecuteContract) error { + return db.SaveWasmExecuteContracts([]types.WasmExecuteContract{wasmExecuteContract}) +} + +// SaveWasmExecuteContracts allows to store the wasm contract slice +func (db *Db) SaveWasmExecuteContracts(executeContracts []types.WasmExecuteContract) error { + paramsNumber := 7 + slices := dbutils.SplitWasmExecuteContracts(executeContracts, paramsNumber) + + for _, contracts := range slices { + if len(contracts) == 0 { + continue + } + + err := db.saveWasmExecuteContracts(paramsNumber, executeContracts) + if err != nil { + return fmt.Errorf("error while storing contracts: %s", err) + } + } + + return nil +} + +func (db *Db) saveWasmExecuteContracts(paramNumber int, executeContracts []types.WasmExecuteContract) error { + stmt := ` +INSERT INTO wasm_execute_contract +(sender, contract_address, raw_contract_message, funds, data, executed_at, height) +VALUES ` + + var args []interface{} + for i, executeContract := range executeContracts { + ii := i * paramNumber + stmt += fmt.Sprintf("($%d, $%d, $%d, $%d, $%d, $%d, $%d),", + ii+1, ii+2, ii+3, ii+4, ii+5, ii+6, ii+7) + args = append(args, + executeContract.Sender, executeContract.ContractAddress, string(executeContract.RawContractMsg), + pq.Array(dbtypes.NewDbCoins(executeContract.Funds)), executeContract.Data, executeContract.ExecutedAt, executeContract.Height) + } + + stmt = stmt[:len(stmt)-1] // Remove trailing "," + + stmt += ` ON CONFLICT DO NOTHING` + + _, err := db.SQL.Exec(stmt, args...) + if err != nil { + return fmt.Errorf("error while saving wasm execute contracts: %s", err) + } + + return nil +} + +func (db *Db) UpdateContractWithMsgMigrateContract( + sender string, contractAddress string, codeID uint64, rawContractMsg []byte, data string, +) error { + + stmt := `UPDATE wasm_contract SET +sender = $1, code_id = $2, raw_contract_message = $3, data = $4 +WHERE contract_address = $5 ` + + _, err := db.SQL.Exec(stmt, + sender, codeID, string(rawContractMsg), data, + contractAddress, + ) + if err != nil { + return fmt.Errorf("error while updating wasm contract from contract migration: %s", err) + + } + return nil +} + +func (db *Db) UpdateContractAdmin(sender string, contractAddress string, newAdmin string) error { + + stmt := `UPDATE wasm_contract SET +sender = $1, admin = $2 WHERE contract_address = $2 ` + + _, err := db.SQL.Exec(stmt, sender, newAdmin, contractAddress) + if err != nil { + return fmt.Errorf("error while updating wsm contract admin: %s", err) + } + return nil +} + +func (db *Db) UpdateMsgInvolvedAccountsAddresses(contractAddress string, txHash string) error { + + stmt := `UPDATE message SET +involved_accounts_addresses = ARRAY_APPEND(involved_accounts_addresses, $1) WHERE transaction_hash = $2 ` + + _, err := db.SQL.Exec(stmt, contractAddress, txHash) + if err != nil { + return fmt.Errorf("error while updating wasm contract message: %s", err) + } + return nil +} diff --git a/modules/wasm/handle_genesis.go b/modules/wasm/handle_genesis.go new file mode 100644 index 000000000..1ab715b05 --- /dev/null +++ b/modules/wasm/handle_genesis.go @@ -0,0 +1,113 @@ +package wasm + +import ( + "encoding/json" + "fmt" + + wasmtypes "github.com/ODIN-PROTOCOL/wasmd/x/wasm/types" + cbfttypes "github.com/cometbft/cometbft/types" + "github.com/forbole/callisto/v4/types" + "github.com/rs/zerolog/log" +) + +// HandleGenesis implements GenesisModule +func (m *Module) HandleGenesis(doc *cbfttypes.GenesisDoc, appState map[string]json.RawMessage) error { + log.Debug().Str("module", "wasm").Msg("parsing genesis") + + // Read the genesis state + var genState wasmtypes.GenesisState + err := m.cdc.UnmarshalJSON(appState[wasmtypes.ModuleName], &genState) + if err != nil { + return fmt.Errorf("error while unmarshaling wasm genesis state: %s", err) + } + + err = m.SaveGenesisParams(genState.Params, doc.InitialHeight) + if err != nil { + return fmt.Errorf("error while saving genesis wasm params: %s", err) + } + + err = m.SaveGenesisCodes(genState.Codes, doc.InitialHeight) + if err != nil { + return fmt.Errorf("error while saving genesis wasm codes: %s", err) + } + + err = m.SaveGenesisContracts(genState.Contracts, doc) + if err != nil { + return fmt.Errorf("error while saving genesis wasm contracts: %s", err) + } + + return nil +} + +func (m *Module) SaveGenesisParams(params wasmtypes.Params, initHeight int64) error { + err := m.db.SaveWasmParams( + types.NewWasmParams(¶ms.CodeUploadAccess, int32(params.InstantiateDefaultPermission), initHeight), + ) + if err != nil { + return fmt.Errorf("error while saving genesis wasm params: %s", err) + } + return nil +} + +func (m *Module) SaveGenesisCodes(codes []wasmtypes.Code, initHeight int64) error { + log.Debug().Str("module", "wasm").Str("operation", "genesis codes"). + Int("code counts", len(codes)).Msg("parsing genesis") + + var wasmCodes = []types.WasmCode{} + for _, code := range codes { + if code.CodeID != 0 { + instantiateConfig := code.CodeInfo.InstantiateConfig + wasmCodes = append(wasmCodes, types.NewWasmCode( + "", code.CodeBytes, &instantiateConfig, code.CodeID, initHeight, + )) + } + } + + if len(wasmCodes) == 0 { + return nil + } + + err := m.db.SaveWasmCodes(wasmCodes) + if err != nil { + return fmt.Errorf("error while saving genesis wasm codes: %s", err) + } + + return nil +} + +func (m *Module) SaveGenesisContracts(contracts []wasmtypes.Contract, doc *cbfttypes.GenesisDoc) error { + log.Debug().Str("module", "wasm").Str("operation", "genesis contracts"). + Int("contract counts", len(contracts)).Msg("parsing genesis") + + for _, contract := range contracts { + + // Unpack contract info extension + var contractInfoExt string + if contract.ContractInfo.Extension != nil { + var extentionI wasmtypes.ContractInfoExtension + err := m.cdc.UnpackAny(contract.ContractInfo.Extension, &extentionI) + if err != nil { + return fmt.Errorf("error while unpacking genesis contract info extension: %s", err) + } + contractInfoExt = extentionI.String() + } + + // Get contract states + contractStates, err := m.source.GetContractStates(doc.InitialHeight, contract.ContractAddress) + if err != nil { + return fmt.Errorf("error while getting genesis contract states: %s", err) + } + + contract := types.NewWasmContract( + "", contract.ContractInfo.Admin, contract.ContractInfo.CodeID, contract.ContractInfo.Label, nil, nil, + contract.ContractAddress, "", doc.GenesisTime, contract.ContractInfo.Creator, contractInfoExt, contractStates, doc.InitialHeight, + ) + + err = m.db.SaveWasmContracts([]types.WasmContract{contract}) + if err != nil { + return fmt.Errorf("error while saving genesis wasm contracts: %s", err) + } + } + + return nil +} diff --git a/modules/wasm/handle_msg.go b/modules/wasm/handle_msg.go new file mode 100644 index 000000000..1564f9859 --- /dev/null +++ b/modules/wasm/handle_msg.go @@ -0,0 +1,270 @@ +package wasm + +import ( + "encoding/base64" + "fmt" + "strconv" + "time" + + wasmtypes "github.com/ODIN-PROTOCOL/wasmd/x/wasm/types" + "github.com/forbole/callisto/v4/types" + "github.com/forbole/callisto/v4/utils" + juno "github.com/forbole/juno/v6/types" +) + +var msgFilter = map[string]bool{ + "/cosmwasm.wasm.v1.MsgStoreCode": true, + "/cosmwasm.wasm.v1.MsgInstantiateContract": true, + "/cosmwasm.wasm.v1.MsgInstantiateContract2": true, + "/cosmwasm.wasm.v1.MsgExecuteContract": true, + "/cosmwasm.wasm.v1.MsgMigrateContract": true, + "/cosmwasm.wasm.v1.MsgUpdateAdmin": true, + "/cosmwasm.wasm.v1.MsgClearAdmin": true, +} + +// HandleMsgExec implements modules.AuthzMessageModule +func (m *Module) HandleMsgExec(index int, _ int, executedMsg juno.Message, tx *juno.Transaction) error { + return m.HandleMsg(index, executedMsg, tx) +} + +// HandleMsg implements modules.MessageModule +func (m *Module) HandleMsg(index int, msg juno.Message, tx *juno.Transaction) error { + if _, ok := msgFilter[msg.GetType()]; !ok { + return nil + } + + switch msg.GetType() { + case "/cosmwasm.wasm.v1.MsgStoreCode": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &wasmtypes.MsgStoreCode{}) + err := m.HandleMsgStoreCode(index, tx, cosmosMsg) + if err != nil { + return fmt.Errorf("error while handling MsgStoreCode: %s", err) + } + case "/cosmwasm.wasm.v1.MsgInstantiateContract": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &wasmtypes.MsgInstantiateContract{}) + err := m.HandleMsgInstantiateContract(index, tx, cosmosMsg) + if err != nil { + return fmt.Errorf("error while handling MsgInstantiateContract: %s", err) + } + case "/cosmwasm.wasm.v1.MsgInstantiateContract2": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &wasmtypes.MsgInstantiateContract2{}) + err := m.HandleMsgInstantiateContract(index, tx, &wasmtypes.MsgInstantiateContract{ + Sender: cosmosMsg.Sender, + Admin: cosmosMsg.Admin, + CodeID: cosmosMsg.CodeID, + Label: cosmosMsg.Label, + Msg: cosmosMsg.Msg, + Funds: cosmosMsg.Funds, + }) + if err != nil { + return fmt.Errorf("error while handling MsgInstantiateContract: %s", err) + } + case "/cosmwasm.wasm.v1.MsgExecuteContract": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &wasmtypes.MsgExecuteContract{}) + err := m.HandleMsgExecuteContract(index, tx, cosmosMsg) + if err != nil { + return fmt.Errorf("error while handling MsgExecuteContract: %s", err) + } + case "/cosmwasm.wasm.v1.MsgMigrateContract": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &wasmtypes.MsgMigrateContract{}) + err := m.HandleMsgMigrateContract(index, tx, cosmosMsg) + if err != nil { + return fmt.Errorf("error while handling MsgMigrateContract: %s", err) + } + case "/cosmwasm.wasm.v1.MsgUpdateAdmin": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &wasmtypes.MsgUpdateAdmin{}) + err := m.HandleMsgUpdateAdmin(cosmosMsg, tx) + if err != nil { + return fmt.Errorf("error while handling MsgUpdateAdmin: %s", err) + } + case "/cosmwasm.wasm.v1.MsgClearAdmin": + cosmosMsg := utils.UnpackMessage(m.cdc, msg.GetBytes(), &wasmtypes.MsgClearAdmin{}) + err := m.HandleMsgClearAdmin(cosmosMsg, tx) + if err != nil { + return fmt.Errorf("error while handling MsgClearAdmin: %s", err) + } + } + + return nil +} + +// HandleMsgStoreCode allows to properly handle a MsgStoreCode +// The Store Code Event is to upload the contract code on the chain, where a Code ID is returned +func (m *Module) HandleMsgStoreCode(index int, tx *juno.Transaction, msg *wasmtypes.MsgStoreCode) error { + // Get store code event + event, err := tx.FindEventByType(index, wasmtypes.EventTypeStoreCode) + if err != nil { + return fmt.Errorf("error while searching for EventTypeInstantiate: %s", err) + } + + // Get code ID from store code event + codeIDKey, err := tx.FindAttributeByKey(event, wasmtypes.AttributeKeyCodeID) + if err != nil { + return fmt.Errorf("error while searching for AttributeKeyContractAddr: %s", err) + } + + codeID, err := strconv.ParseUint(codeIDKey, 10, 64) + if err != nil { + return fmt.Errorf("error while parsing code id to int64: %s", err) + } + + return m.db.SaveWasmCode( + types.NewWasmCode( + msg.Sender, msg.WASMByteCode, msg.InstantiatePermission, codeID, int64(tx.Height), + ), + ) +} + +// HandleMsgInstantiateContract allows to properly handle a MsgInstantiateContract +// Instantiate Contract Event instantiates an executable contract with the code previously stored with Store Code Event +func (m *Module) HandleMsgInstantiateContract(index int, tx *juno.Transaction, msg *wasmtypes.MsgInstantiateContract) error { + // Get instantiate contract event + event, err := tx.FindEventByType(index, wasmtypes.EventTypeInstantiate) + if err != nil { + return fmt.Errorf("error while searching for EventTypeInstantiate: %s", err) + } + + // Get contract address + contractAddress, err := tx.FindAttributeByKey(event, wasmtypes.AttributeKeyContractAddr) + if err != nil { + return fmt.Errorf("error while searching for AttributeKeyContractAddr: %s", err) + } + + err = m.db.UpdateMsgInvolvedAccountsAddresses(contractAddress, tx.TxHash) + if err != nil { + return fmt.Errorf("error while saving contract address inside involved accounts addresses: %s", err) + } + + // Get result data + resultData, err := tx.FindAttributeByKey(event, wasmtypes.AttributeKeyResultDataHex) + if err != nil { + resultData = "" + } + resultDataBz, err := base64.StdEncoding.DecodeString(resultData) + if err != nil { + return fmt.Errorf("error while decoding result data: %s", err) + } + + // Get the contract info + contractInfo, err := m.source.GetContractInfo(int64(tx.Height), contractAddress) + if err != nil { + return fmt.Errorf("error while getting proposal: %s", err) + } + + timestamp, err := time.Parse(time.RFC3339, tx.Timestamp) + if err != nil { + return fmt.Errorf("error while parsing time: %s", err) + } + + // Get contract info extension + var contractInfoExt string + if contractInfo.Extension != nil { + var extentionI wasmtypes.ContractInfoExtension + err = m.cdc.UnpackAny(contractInfo.Extension, &extentionI) + if err != nil { + return fmt.Errorf("error while getting contract info extension: %s", err) + } + contractInfoExt = extentionI.String() + } + + // Get contract states + contractStates, err := m.source.GetContractStates(int64(tx.Height), contractAddress) + if err != nil { + return fmt.Errorf("error while getting genesis contract states: %s", err) + } + + contract := types.NewWasmContract( + msg.Sender, msg.Admin, msg.CodeID, msg.Label, msg.Msg, msg.Funds, + contractAddress, string(resultDataBz), timestamp, + contractInfo.Creator, contractInfoExt, contractStates, int64(tx.Height), + ) + return m.db.SaveWasmContracts( + []types.WasmContract{contract}, + ) +} + +// HandleMsgExecuteContract allows to properly handle a MsgExecuteContract +// Execute Event executes an instantiated contract +func (m *Module) HandleMsgExecuteContract(index int, tx *juno.Transaction, msg *wasmtypes.MsgExecuteContract) error { + // Get Execute Contract event + event, err := tx.FindEventByType(index, wasmtypes.EventTypeExecute) + if err != nil { + return fmt.Errorf("error while searching for EventTypeExecute: %s", err) + } + + // Get result data + resultData, err := tx.FindAttributeByKey(event, wasmtypes.AttributeKeyResultDataHex) + if err != nil { + resultData = "" + } + resultDataBz, err := base64.StdEncoding.DecodeString(resultData) + if err != nil { + return fmt.Errorf("error while decoding result data: %s", err) + } + + timestamp, err := time.Parse(time.RFC3339, tx.Timestamp) + if err != nil { + return fmt.Errorf("error while parsing time: %s", err) + } + + err = m.db.UpdateMsgInvolvedAccountsAddresses(msg.Contract, tx.TxHash) + if err != nil { + return fmt.Errorf("error while saving contract address inside the involved addresses in message table: %s", err) + } + + return m.db.SaveWasmExecuteContract( + types.NewWasmExecuteContract( + msg.Sender, msg.Contract, msg.Msg, msg.Funds, + string(resultDataBz), timestamp, int64(tx.Height), + ), + ) +} + +// HandleMsgMigrateContract allows to properly handle a MsgMigrateContract +// Migrate Contract Event upgrade the contract by updating code ID generated from new Store Code Event +func (m *Module) HandleMsgMigrateContract(index int, tx *juno.Transaction, msg *wasmtypes.MsgMigrateContract) error { + // Get Migrate Contract event + event, err := tx.FindEventByType(index, wasmtypes.EventTypeMigrate) + if err != nil { + return fmt.Errorf("error while searching for EventTypeMigrate: %s", err) + } + + // Get result data + resultData, err := tx.FindAttributeByKey(event, wasmtypes.AttributeKeyResultDataHex) + if err != nil { + resultData = "" + } + resultDataBz, err := base64.StdEncoding.DecodeString(resultData) + if err != nil { + return fmt.Errorf("error while decoding result data: %s", err) + } + + err = m.db.UpdateMsgInvolvedAccountsAddresses(msg.Contract, tx.TxHash) + if err != nil { + return fmt.Errorf("error while saving contract address inside the involved addresses in message table: %s", err) + } + + return m.db.UpdateContractWithMsgMigrateContract(msg.Sender, msg.Contract, msg.CodeID, msg.Msg, string(resultDataBz)) +} + +// HandleMsgUpdateAdmin allows to properly handle a MsgUpdateAdmin +// Update Admin Event updates the contract admin who can migrate the wasm contract +func (m *Module) HandleMsgUpdateAdmin(msg *wasmtypes.MsgUpdateAdmin, tx *juno.Transaction) error { + err := m.db.UpdateMsgInvolvedAccountsAddresses(msg.Contract, tx.TxHash) + if err != nil { + return fmt.Errorf("error while saving contract address inside the involved addresses in message table: %s", err) + } + + return m.db.UpdateContractAdmin(msg.Sender, msg.Contract, msg.NewAdmin) +} + +// HandleMsgClearAdmin allows to properly handle a MsgClearAdmin +// Clear Admin Event clears the admin which make the contract no longer migratable +func (m *Module) HandleMsgClearAdmin(msg *wasmtypes.MsgClearAdmin, tx *juno.Transaction) error { + err := m.db.UpdateMsgInvolvedAccountsAddresses(msg.Contract, tx.TxHash) + if err != nil { + return fmt.Errorf("error while saving contract address inside the involved addresses in message table: %s", err) + } + + return m.db.UpdateContractAdmin(msg.Sender, msg.Contract, "") +} diff --git a/modules/wasm/module.go b/modules/wasm/module.go new file mode 100644 index 000000000..f5eeff0d5 --- /dev/null +++ b/modules/wasm/module.go @@ -0,0 +1,36 @@ +package wasm + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/forbole/callisto/v4/database" + wasmsource "github.com/forbole/callisto/v4/modules/wasm/source" + "github.com/forbole/juno/v6/modules" +) + +var ( + _ modules.Module = &Module{} + _ modules.MessageModule = &Module{} + _ modules.GenesisModule = &Module{} + _ modules.AuthzMessageModule = &Module{} +) + +// Module represent x/wasm module +type Module struct { + cdc codec.Codec + db *database.Db + source wasmsource.Source +} + +// NewModule returns a new Module instance +func NewModule(source wasmsource.Source, cdc codec.Codec, db *database.Db) *Module { + return &Module{ + cdc: cdc, + db: db, + source: source, + } +} + +// Name implements modules.Module +func (m *Module) Name() string { + return "wasm" +} diff --git a/modules/wasm/source/local/source.go b/modules/wasm/source/local/source.go new file mode 100644 index 000000000..9a09eca3c --- /dev/null +++ b/modules/wasm/source/local/source.go @@ -0,0 +1,139 @@ +package local + +import ( + "fmt" + "strings" + + wasmtypes "github.com/ODIN-PROTOCOL/wasmd/x/wasm/types" + "github.com/cosmos/cosmos-sdk/types/query" + wasmsource "github.com/forbole/callisto/v4/modules/wasm/source" + "github.com/forbole/juno/v6/node/local" +) + +var ( + _ wasmsource.Source = &Source{} +) + +// Source implements wasmsource.Source using a local node +type Source struct { + *local.Source + q wasmtypes.QueryServer +} + +// NewSource returns a new Source instance +func NewSource(source *local.Source, querier wasmtypes.QueryServer) *Source { + return &Source{ + Source: source, + q: querier, + } +} + +// GetContractInfo implements wasmsource.Source +func (s Source) GetContractInfo(height int64, contractAddr string) (*wasmtypes.QueryContractInfoResponse, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return nil, fmt.Errorf("error while loading height: %s", err) + } + + res, err := s.q.ContractInfo( + ctx, + &wasmtypes.QueryContractInfoRequest{ + Address: contractAddr, + }, + ) + if err != nil { + return nil, fmt.Errorf("error while getting contract info: %s", err) + } + + return res, nil +} + +// GetContractStates implements wasmsource.Source +func (s Source) GetContractStates(height int64, contractAddr string) ([]wasmtypes.Model, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return nil, fmt.Errorf("error while loading height: %s", err) + } + + var models []wasmtypes.Model + var nextKey []byte + var stop = false + for !stop { + res, err := s.q.AllContractState( + ctx, + &wasmtypes.QueryAllContractStateRequest{ + Address: contractAddr, + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 states at time + }, + }, + ) + if err != nil { + return nil, fmt.Errorf("error while getting contract state: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + models = append(models, res.Models...) + } + + return models, nil +} + +// GetCodes implements wasmsource.Source +func (s Source) GetCodes(height int64) ([]wasmtypes.CodeInfoResponse, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return nil, fmt.Errorf("error while loading height: %s", err) + } + + var codes []wasmtypes.CodeInfoResponse + var nextKey []byte + var stop = false + for !stop { + res, err := s.q.Codes( + ctx, + &wasmtypes.QueryCodesRequest{ + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 states at time + }, + }, + ) + if err != nil { + return nil, fmt.Errorf("error while getting contract codes: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + codes = append(codes, res.CodeInfos...) + } + + return codes, nil +} + +// GetContractsByCode implements wasmsource.Source +func (s Source) GetContractsByCode(height int64, codeID uint64) ([]string, error) { + ctx, err := s.LoadHeight(height) + if err != nil { + return nil, fmt.Errorf("error while loading height: %s", err) + } + + var contracts []string + res, err := s.q.ContractsByCode( + ctx, + &wasmtypes.QueryContractsByCodeRequest{ + CodeId: codeID, + }, + ) + if err != nil { + return nil, fmt.Errorf("error while getting contracts by code info: %s", err) + } + + for _, c := range res.Contracts { + v := strings.Split(c, ",") // Split the values + contracts = append(contracts, v...) + } + return contracts, nil +} diff --git a/modules/wasm/source/remote/source.go b/modules/wasm/source/remote/source.go new file mode 100644 index 000000000..5472d600c --- /dev/null +++ b/modules/wasm/source/remote/source.go @@ -0,0 +1,121 @@ +package remote + +import ( + "fmt" + "strings" + + wasmtypes "github.com/ODIN-PROTOCOL/wasmd/x/wasm/types" + "github.com/cosmos/cosmos-sdk/types/query" + wasmsource "github.com/forbole/callisto/v4/modules/wasm/source" + "github.com/forbole/juno/v6/node/remote" +) + +var ( + _ wasmsource.Source = &Source{} +) + +// Source implements wasmsource.Source using a remote node +type Source struct { + *remote.Source + wasmClient wasmtypes.QueryClient +} + +// NewSource returns a new Source instance +func NewSource(source *remote.Source, wasmClient wasmtypes.QueryClient) *Source { + return &Source{ + Source: source, + wasmClient: wasmClient, + } +} + +// GetContractInfo implements wasmsource.Source +func (s Source) GetContractInfo(height int64, contractAddr string) (*wasmtypes.QueryContractInfoResponse, error) { + res, err := s.wasmClient.ContractInfo( + remote.GetHeightRequestContext(s.Ctx, height), + &wasmtypes.QueryContractInfoRequest{ + Address: contractAddr, + }, + ) + if err != nil { + return nil, fmt.Errorf("error while getting contract info: %s", err) + } + + return res, nil +} + +// GetContractStates implements wasmsource.Source +func (s Source) GetContractStates(height int64, contractAddr string) ([]wasmtypes.Model, error) { + + var models []wasmtypes.Model + var nextKey []byte + var stop = false + for !stop { + res, err := s.wasmClient.AllContractState( + remote.GetHeightRequestContext(s.Ctx, height), + &wasmtypes.QueryAllContractStateRequest{ + Address: contractAddr, + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 states at time + }, + }, + ) + if err != nil { + return nil, fmt.Errorf("error while getting contract state: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + models = append(models, res.Models...) + } + + return models, nil +} + +// GetCodes implements wasmsource.Source +func (s Source) GetCodes(height int64) ([]wasmtypes.CodeInfoResponse, error) { + + var codes []wasmtypes.CodeInfoResponse + var nextKey []byte + var stop = false + for !stop { + res, err := s.wasmClient.Codes( + remote.GetHeightRequestContext(s.Ctx, height), + &wasmtypes.QueryCodesRequest{ + Pagination: &query.PageRequest{ + Key: nextKey, + Limit: 100, // Query 100 states at time + }, + }, + ) + if err != nil { + return nil, fmt.Errorf("error while getting contract codes: %s", err) + } + + nextKey = res.Pagination.NextKey + stop = len(res.Pagination.NextKey) == 0 + codes = append(codes, res.CodeInfos...) + } + + return codes, nil +} + +// GetContractsByCode implements wasmsource.Source +func (s Source) GetContractsByCode(height int64, codeID uint64) ([]string, error) { + var contracts []string + res, err := s.wasmClient.ContractsByCode( + remote.GetHeightRequestContext(s.Ctx, height), + &wasmtypes.QueryContractsByCodeRequest{ + CodeId: codeID, + }, + ) + if err != nil { + return nil, fmt.Errorf("error while getting contracts by code info: %s", err) + } + + for _, c := range res.Contracts { + v := strings.Split(c, ",") // Split the values + contracts = append(contracts, v...) + } + return contracts, nil +} diff --git a/modules/wasm/source/source.go b/modules/wasm/source/source.go new file mode 100644 index 000000000..a83ba2dbb --- /dev/null +++ b/modules/wasm/source/source.go @@ -0,0 +1,12 @@ +package source + +import ( + wasmtypes "github.com/ODIN-PROTOCOL/wasmd/x/wasm/types" +) + +type Source interface { + GetContractInfo(height int64, contractAddr string) (*wasmtypes.QueryContractInfoResponse, error) + GetContractStates(height int64, contractAddress string) ([]wasmtypes.Model, error) + GetCodes(height int64) ([]wasmtypes.CodeInfoResponse, error) + GetContractsByCode(height int64, codeID uint64) ([]string, error) +} diff --git a/modules/wasm/utils_contracts.go b/modules/wasm/utils_contracts.go new file mode 100644 index 000000000..17bafaf8d --- /dev/null +++ b/modules/wasm/utils_contracts.go @@ -0,0 +1,131 @@ +package wasm + +import ( + "fmt" + "strings" + "time" + + "github.com/forbole/callisto/v4/types" + "github.com/rs/zerolog/log" +) + +// StoreContracts gets the available contracts and stores them inside the database +func (m *Module) StoreContracts(height int64) error { + log.Debug().Str("module", "wasm").Int64("height", height). + Msg("storing x/wasm contracts") + + codes, err := m.getWasmCodes(height) + if err != nil { + return fmt.Errorf("error while handling contracts codes: %s", err) + } + + for _, code := range codes { + contracts, err := m.getContractByCode(code.CodeID, height) + if err != nil { + return fmt.Errorf("error while handling contracts codes: %s", err) + } + + for _, contract := range contracts { + contractStates, err := m.source.GetContractStates(height, contract) + if err != nil { + return fmt.Errorf("error while getting contracts states: %s", err) + } + + contractInfo, err := m.source.GetContractInfo(height, contract) + if err != nil { + return fmt.Errorf("error while getting contracts info: %s", err) + } + + err = m.db.SaveWasmContracts([]types.WasmContract{ + types.NewWasmContract("", contractInfo.ContractInfo.Admin, contractInfo.ContractInfo.CodeID, contractInfo.ContractInfo.Label, nil, nil, + contract, "", time.Now(), contractInfo.ContractInfo.Creator, contractInfo.ContractInfo.Extension.GoString(), contractStates, height, + )}) + + if err != nil { + return fmt.Errorf("error while saving wasm contract: %s", err) + } + + } + + } + + return nil +} + +// StoreContractsByCode gets the available contracts by contract code +// and stores them inside the database +func (m *Module) StoreContractsByCode(codeID uint64, height int64) error { + log.Debug().Str("module", "wasm").Int64("height", height). + Msg("storing x/wasm contracts") + + contracts, err := m.getContractByCode(codeID, height) + if err != nil { + return fmt.Errorf("error while handling contracts codes: %s", err) + } + + for _, contract := range contracts { + contractStates, err := m.source.GetContractStates(height, contract) + if err != nil { + return fmt.Errorf("error while getting contracts states: %s", err) + } + + contractInfo, err := m.source.GetContractInfo(height, contract) + if err != nil { + return fmt.Errorf("error while getting contracts info: %s", err) + } + + err = m.db.SaveWasmContracts([]types.WasmContract{ + types.NewWasmContract("", contractInfo.ContractInfo.Admin, contractInfo.ContractInfo.CodeID, contractInfo.ContractInfo.Label, nil, nil, + contract, "", time.Now(), contractInfo.ContractInfo.Creator, contractInfo.ContractInfo.Extension.GoString(), contractStates, height, + )}) + + if err != nil { + return fmt.Errorf("error while saving wasm contract: %s", err) + } + + } + + return nil +} + +func (m *Module) getWasmCodes(height int64) ([]types.WasmCode, error) { + var wasmCodes = []types.WasmCode{} + codes, err := m.source.GetCodes(height) + if err != nil { + return nil, fmt.Errorf("error while getting contracts codes: %s", err) + } + + for _, c := range codes { + instantiatePermission := c.InstantiatePermission + wasmCodes = append(wasmCodes, types.NewWasmCode( + "", c.DataHash, &instantiatePermission, c.CodeID, height, + )) + } + + if len(wasmCodes) == 0 { + return nil, nil + } + + // fmt.Printf("\n wasmcodes %v ", wasmCodes) + err = m.db.SaveWasmCodes(wasmCodes) + if err != nil { + return nil, fmt.Errorf("error while saving wasm codes: %s", err) + } + + return wasmCodes, nil +} + +func (m *Module) getContractByCode(codeID uint64, height int64) ([]string, error) { + var contracts []string + contract, err := m.source.GetContractsByCode(height, codeID) + if err != nil { + return nil, fmt.Errorf("error while getting contracts by code %d: %s", codeID, err) + } + + for _, d := range contract { + values := strings.Split(d, " ") + contracts = append(contracts, values...) + } + + return contracts, nil +} diff --git a/types/wasm.go b/types/wasm.go new file mode 100644 index 000000000..b0a256ab9 --- /dev/null +++ b/types/wasm.go @@ -0,0 +1,151 @@ +package types + +import ( + "encoding/hex" + "encoding/json" + "time" + + wasmtypes "github.com/ODIN-PROTOCOL/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// WasmParams represents the CosmWasm code in x/wasm module +type WasmParams struct { + CodeUploadAccess *wasmtypes.AccessConfig + InstantiateDefaultPermission int32 + Height int64 +} + +// NewWasmParams allows to build a new x/wasm params instance +func NewWasmParams( + codeUploadAccess *wasmtypes.AccessConfig, instantiateDefaultPermission int32, height int64, +) WasmParams { + return WasmParams{ + CodeUploadAccess: codeUploadAccess, + InstantiateDefaultPermission: instantiateDefaultPermission, + Height: height, + } +} + +// WasmCode represents the CosmWasm code in x/wasm module +type WasmCode struct { + Sender string + WasmByteCode []byte + InstantiatePermission *wasmtypes.AccessConfig + CodeID uint64 + Height int64 +} + +// NewWasmCode allows to build a new x/wasm code instance +func NewWasmCode( + sender string, wasmByteCode []byte, initPermission *wasmtypes.AccessConfig, codeID uint64, height int64, +) WasmCode { + return WasmCode{ + Sender: sender, + WasmByteCode: wasmByteCode, + InstantiatePermission: initPermission, + CodeID: codeID, + Height: height, + } +} + +// WasmContract represents the CosmWasm contract in x/wasm module +type WasmContract struct { + Sender string + Admin string + CodeID uint64 + Label string + RawContractMsg wasmtypes.RawContractMessage + Funds sdk.Coins + ContractAddress string + Data string + InstantiatedAt time.Time + Creator string + ContractInfoExtension string + ContractStates []byte + Height int64 +} + +// NewWasmContract allows to build a new x/wasm contract instance +func NewWasmContract( + sender string, admin string, codeID uint64, label string, rawMsg wasmtypes.RawContractMessage, funds sdk.Coins, contractAddress string, data string, + instantiatedAt time.Time, creator string, contractInfoExtension string, states []wasmtypes.Model, height int64, +) WasmContract { + rawContractMsg, _ := rawMsg.MarshalJSON() + contractStateInfo := ConvertContractStates(states) + + return WasmContract{ + Sender: sender, + Admin: admin, + CodeID: codeID, + Label: label, + RawContractMsg: rawContractMsg, + Funds: funds, + ContractAddress: contractAddress, + Data: data, + InstantiatedAt: instantiatedAt, + Creator: creator, + ContractInfoExtension: contractInfoExtension, + ContractStates: contractStateInfo, + Height: height, + } +} + +// ConvertContractStates removes unaccepted hex characters for postgreSQL from the state key +func ConvertContractStates(states []wasmtypes.Model) []byte { + var jsonStates = make(map[string]interface{}) + + hexZero, _ := hex.DecodeString("00") + for _, state := range states { + key := state.Key + // Remove initial 2 hex characters if the first is \x00 + if string(state.Key[:1]) == string(hexZero) { + key = state.Key[2:] + } + + // Remove \x00 hex characters in the middle + for i := 0; i < len(key); i++ { + if string(key[i]) == string(hexZero) { + key = append(key[:i], key[i+1:]...) + i-- + } + } + + // Decode hex value + keyBz, _ := hex.DecodeString(key.String()) + + jsonStates[string(keyBz)] = string(state.Value) + } + jsonStatesBz, _ := json.Marshal(&jsonStates) + + return jsonStatesBz +} + +// WasmExecuteContract represents the CosmWasm execute contract in x/wasm module +type WasmExecuteContract struct { + Sender string + ContractAddress string + RawContractMsg []byte + Funds sdk.Coins + Data string + ExecutedAt time.Time + Height int64 +} + +// NewWasmExecuteContract allows to build a new x/wasm execute contract instance +func NewWasmExecuteContract( + sender string, contractAddress string, rawMsg wasmtypes.RawContractMessage, + funds sdk.Coins, data string, executedAt time.Time, height int64, +) WasmExecuteContract { + rawContractMsg, _ := rawMsg.MarshalJSON() + + return WasmExecuteContract{ + Sender: sender, + ContractAddress: contractAddress, + RawContractMsg: rawContractMsg, + Funds: funds, + Data: data, + ExecutedAt: executedAt, + Height: height, + } +}