From 6357a0a9a89e67fe8b26c3d27e60c446dc36cef8 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 28 Jun 2023 02:04:14 -0400 Subject: [PATCH 01/14] add icq --- app/app.go | 33 ++++++++++++++++++++++++++++++++- app/querier.go | 26 ++++++++++++++++++++++++++ go.mod | 3 ++- go.sum | 7 ++++--- 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 app/querier.go diff --git a/app/app.go b/app/app.go index a9d5c9a..3f679d4 100644 --- a/app/app.go +++ b/app/app.go @@ -33,6 +33,10 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + icq "github.com/strangelove-ventures/async-icq/v7" + icqkeeper "github.com/strangelove-ventures/async-icq/v7/keeper" + icqtypes "github.com/strangelove-ventures/async-icq/v7/types" + "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/ante" @@ -236,6 +240,7 @@ var ( transfer.AppModuleBasic{}, ica.AppModuleBasic{}, ibcfee.AppModuleBasic{}, + icq.AppModuleBasic{}, tokenfactory.AppModuleBasic{}, ) @@ -250,6 +255,7 @@ var ( nft.ModuleName: nil, ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, ibcfeetypes.ModuleName: nil, + icqtypes.ModuleName: nil, icatypes.ModuleName: nil, wasm.ModuleName: {authtypes.Burner}, tokenfactorytypes.ModuleName: {authtypes.Minter, authtypes.Burner}, @@ -296,6 +302,7 @@ type TerpApp struct { IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly IBCFeeKeeper ibcfeekeeper.Keeper + ICQKeeper icqkeeper.Keeper ICAControllerKeeper icacontrollerkeeper.Keeper TokenFactoryKeeper tokenfactorykeeper.Keeper ICAHostKeeper icahostkeeper.Keeper @@ -304,6 +311,7 @@ type TerpApp struct { ScopedIBCKeeper capabilitykeeper.ScopedKeeper ScopedICAHostKeeper capabilitykeeper.ScopedKeeper + ScopedICQKeeper capabilitykeeper.ScopedKeeper ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper ScopedTransferKeeper capabilitykeeper.ScopedKeeper ScopedIBCFeeKeeper capabilitykeeper.ScopedKeeper @@ -365,6 +373,7 @@ func NewTerpApp( ibctransfertypes.StoreKey, ibcfeetypes.StoreKey, wasm.StoreKey, + icqtypes.StoreKey, icahosttypes.StoreKey, icacontrollertypes.StoreKey, tokenfactorytypes.StoreKey, @@ -412,6 +421,7 @@ func NewTerpApp( scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + scopedICQKeeper := app.CapabilityKeeper.ScopeToModule(icqtypes.ModuleName) scopedWasmKeeper := app.CapabilityKeeper.ScopeToModule(wasm.ModuleName) app.CapabilityKeeper.Seal() @@ -609,6 +619,18 @@ func NewTerpApp( &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, ) + // ICQ Keeper + app.ICQKeeper = icqkeeper.NewKeeper( + appCodec, + keys[icqtypes.StoreKey], + app.GetSubspace(icqtypes.ModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + scopedICQKeeper, + NewQuerierWrapper(bApp), + ) + app.ICAHostKeeper = icahostkeeper.NewKeeper( appCodec, keys[icahosttypes.StoreKey], @@ -695,12 +717,16 @@ func NewTerpApp( wasmStack = wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCFeeKeeper) wasmStack = ibcfee.NewIBCMiddleware(wasmStack, app.IBCFeeKeeper) + // create ICQ module + icqModule := icq.NewIBCModule(app.ICQKeeper) + // Create static IBC router, add app routes, then set and seal it ibcRouter := porttypes.NewRouter(). AddRoute(ibctransfertypes.ModuleName, transferStack). AddRoute(wasm.ModuleName, wasmStack). AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). - AddRoute(icahosttypes.SubModuleName, icaHostStack) + AddRoute(icahosttypes.SubModuleName, icaHostStack). + AddRoute(icqtypes.ModuleName, icqModule) app.IBCKeeper.SetRouter(ibcRouter) /**** Module Options ****/ @@ -741,6 +767,7 @@ func NewTerpApp( transfer.NewAppModule(app.TransferKeeper), ibcfee.NewAppModule(app.IBCFeeKeeper), ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + icq.NewAppModule(app.ICQKeeper), crisis.NewAppModule(app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), // always be last to make sure that it checks for all invariants and not only part of them ) @@ -760,6 +787,7 @@ func NewTerpApp( ibcexported.ModuleName, icatypes.ModuleName, ibcfeetypes.ModuleName, + icqtypes.ModuleName, tokenfactorytypes.ModuleName, wasm.ModuleName, ) @@ -776,6 +804,7 @@ func NewTerpApp( ibcexported.ModuleName, icatypes.ModuleName, ibcfeetypes.ModuleName, + icqtypes.ModuleName, tokenfactorytypes.ModuleName, wasm.ModuleName, ) @@ -799,6 +828,7 @@ func NewTerpApp( ibcexported.ModuleName, icatypes.ModuleName, ibcfeetypes.ModuleName, + icqtypes.ModuleName, // wasm after ibc transfer tokenfactorytypes.ModuleName, wasm.ModuleName, @@ -1136,6 +1166,7 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino paramsKeeper.Subspace(tokenfactorytypes.ModuleName) paramsKeeper.Subspace(icahosttypes.SubModuleName) paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + paramsKeeper.Subspace(icqtypes.ModuleName) paramsKeeper.Subspace(wasm.ModuleName) return paramsKeeper diff --git a/app/querier.go b/app/querier.go new file mode 100644 index 0000000..1cda480 --- /dev/null +++ b/app/querier.go @@ -0,0 +1,26 @@ +// this file used from osmosis, ref: https://github.com/CosmosContracts/juno/v16/blob/2ce971f4c6aa85d3ef7ba33d60e0ae74b923ab83/app/keepers/querier.go +// Original Author: https://github.com/nicolaslara + +package app + +import ( + abci "github.com/cometbft/cometbft/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// QuerierWrapper is a local wrapper around BaseApp that exports only the Queryable interface. +// This is used to pass the baseApp to Async ICQ without exposing all methods +type QuerierWrapper struct { + querier sdk.Queryable +} + +var _ sdk.Queryable = QuerierWrapper{} + +func NewQuerierWrapper(querier sdk.Queryable) QuerierWrapper { + return QuerierWrapper{querier: querier} +} + +func (q QuerierWrapper) Query(req abci.RequestQuery) abci.ResponseQuery { + return q.querier.Query(req) +} diff --git a/go.mod b/go.mod index ddd2450..02e19da 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/rakyll/statik v0.1.7 // indirect github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa github.com/spf13/cast v1.5.1 - github.com/spf13/cobra v1.6.1 + github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.3 github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect @@ -40,6 +40,7 @@ require ( cosmossdk.io/math v1.0.1 cosmossdk.io/tools/rosetta v0.2.1 github.com/spf13/viper v1.16.0 + github.com/strangelove-ventures/async-icq/v7 v7.0.0-20230413165143-a3b65ccdc897 ) require ( diff --git a/go.sum b/go.sum index 46920d3..f7577e6 100644 --- a/go.sum +++ b/go.sum @@ -654,7 +654,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -905,8 +904,8 @@ github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -917,6 +916,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/strangelove-ventures/async-icq/v7 v7.0.0-20230413165143-a3b65ccdc897 h1:lCTD5L1v1K1KC6KXjyt4o1X+yzV14RbbrPZaF29n8uI= +github.com/strangelove-ventures/async-icq/v7 v7.0.0-20230413165143-a3b65ccdc897/go.mod h1:ag05Q54Wkr0jVwfe+14sxnuWbw0gBOxtPQv9afBBnr0= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= From f39c818cae6896fd706e583c514052b5431c42fc Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 28 Jun 2023 02:09:20 -0400 Subject: [PATCH 02/14] add fast node-test script --- Makefile | 4 ++ scripts/test_node.sh | 118 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 scripts/test_node.sh diff --git a/Makefile b/Makefile index f6069ef..e2ddba1 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,10 @@ endif install: go.sum go install -mod=readonly $(BUILD_FLAGS) ./cmd/terpd +test-node: + CHAIN_ID="local-1" HOME_DIR="~/.terp1" TIMEOUT_COMMIT="500ms" CLEAN=true sh scripts/test_node.sh + + ######################################## ### Tools & dependencies diff --git a/scripts/test_node.sh b/scripts/test_node.sh new file mode 100644 index 0000000..812a929 --- /dev/null +++ b/scripts/test_node.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# Run this script to quickly install, setup, and run the current version of terp without docker. +# +# Example: +# CHAIN_ID="local-1" HOME_DIR="~/.terp1" TIMEOUT_COMMIT="500ms" CLEAN=true sh scripts/test_node.sh +# CHAIN_ID="local-2" HOME_DIR="~/.terp2" CLEAN=true RPC=36657 REST=2317 PROFF=6061 P2P=36656 GRPC=8090 GRPC_WEB=8091 ROSETTA=8081 TIMEOUT_COMMIT="500ms" sh scripts/test_node.sh +# +# To use unoptomized wasm files up to ~5mb, add: MAX_WASM_SIZE=5000000 + +export KEY="terp1" +export KEY2="terp2" + +export CHAIN_ID=${CHAIN_ID:-"local-1"} +export MONIKER="localterp" +export KEYALGO="secp256k1" +export KEYRING=${KEYRING:-"test"} +export HOME_DIR=$(eval echo "${HOME_DIR:-"~/.terp"}") +export BINARY=${BINARY:-terpd} + +export CLEAN=${CLEAN:-"false"} +export RPC=${RPC:-"26657"} +export REST=${REST:-"1317"} +export PROFF=${PROFF:-"6060"} +export P2P=${P2P:-"26656"} +export GRPC=${GRPC:-"9090"} +export GRPC_WEB=${GRPC_WEB:-"9091"} +export ROSETTA=${ROSETTA:-"8080"} +export TIMEOUT_COMMIT=${TIMEOUT_COMMIT:-"5s"} + +alias BINARY="$BINARY --home=$HOME_DIR" + +command -v $BINARY > /dev/null 2>&1 || { echo >&2 "$BINARY command not found. Ensure this is setup / properly installed in your GOPATH (make install)."; exit 1; } +command -v jq > /dev/null 2>&1 || { echo >&2 "jq not installed. More info: https://stedolan.github.io/jq/download/"; exit 1; } + +$BINARY config keyring-backend $KEYRING +$BINARY config chain-id $CHAIN_ID + +from_scratch () { + # Fresh install on current branch + make install + + # remove existing daemon. + rm -rf $HOME_DIR && echo "Removed $HOME_DIR" + + # terp1efd63aw40lxf3n4mhf7dzhjkr453axurajg60e + echo "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" | BINARY keys add $KEY --keyring-backend $KEYRING --algo $KEYALGO --recover + # terp1hj5fveer5cjtn4wd6wstzugjfdxzl0xppxm7xs + echo "wealth flavor believe regret funny network recall kiss grape useless pepper cram hint member few certain unveil rather brick bargain curious require crowd raise" | BINARY keys add $KEY2 --keyring-backend $KEYRING --algo $KEYALGO --recover + + BINARY init $MONIKER --chain-id $CHAIN_ID --default-denom uterp + + # Function updates the config based on a jq argument as a string + update_test_genesis () { + cat $HOME_DIR/config/genesis.json | jq "$1" > $HOME_DIR/config/tmp_genesis.json && mv $HOME_DIR/config/tmp_genesis.json $HOME_DIR/config/genesis.json + } + + # Block + update_test_genesis '.consensus_params["block"]["max_gas"]="100000000"' + # Gov + update_test_genesis '.app_state["gov"]["params"]["min_deposit"]=[{"denom": "uterp","amount": "1000000"}]' + update_test_genesis '.app_state["gov"]["voting_params"]["voting_period"]="15s"' + # staking + update_test_genesis '.app_state["staking"]["params"]["bond_denom"]="uterp"' + update_test_genesis '.app_state["staking"]["params"]["min_commission_rate"]="0.050000000000000000"' + # mint + update_test_genesis '.app_state["mint"]["params"]["mint_denom"]="uterp"' + # crisis + update_test_genesis '.app_state["crisis"]["constant_fee"]={"denom": "uterp","amount": "1000"}' + + # Custom Modules + + # Allocate genesis accounts + BINARY genesis add-genesis-account $KEY 10000000uterp,1000utest --keyring-backend $KEYRING + BINARY genesis add-genesis-account $KEY2 1000000uterp,1000utest --keyring-backend $KEYRING + + BINARY genesis gentx $KEY 1000000uterp --keyring-backend $KEYRING --chain-id $CHAIN_ID + + # Collect genesis tx + BINARY genesis collect-gentxs + + # Run this to ensure terprything worked and that the genesis file is setup correctly + BINARY genesis validate-genesis +} + +# check if CLEAN is not set to false +if [ "$CLEAN" != "false" ]; then + echo "Starting from a clean state" + from_scratch +fi + +echo "Starting node..." + +# Opens the RPC endpoint to outside connections +sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/c\laddr = "tcp:\/\/0.0.0.0:'$RPC'"/g' $HOME_DIR/config/config.toml +sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["\*"\]/g' $HOME_DIR/config/config.toml + +# REST endpoint +sed -i 's/address = "tcp:\/\/localhost:1317"/address = "tcp:\/\/0.0.0.0:'$REST'"/g' $HOME_DIR/config/app.toml +sed -i 's/enable = false/enable = true/g' $HOME_DIR/config/app.toml + +# replace pprof_laddr = "localhost:6060" binding +sed -i 's/pprof_laddr = "localhost:6060"/pprof_laddr = "localhost:'$PROFF_LADDER'"/g' $HOME_DIR/config/config.toml + +# change p2p addr laddr = "tcp://0.0.0.0:26656" +sed -i 's/laddr = "tcp:\/\/0.0.0.0:26656"/laddr = "tcp:\/\/0.0.0.0:'$P2P'"/g' $HOME_DIR/config/config.toml + +# GRPC +sed -i 's/address = "localhost:9090"/address = "0.0.0.0:'$GRPC'"/g' $HOME_DIR/config/app.toml +sed -i 's/address = "localhost:9091"/address = "0.0.0.0:'$GRPC_WEB'"/g' $HOME_DIR/config/app.toml + +# Rosetta Api +sed -i 's/address = ":8080"/address = "0.0.0.0:'$ROSETTA'"/g' $HOME_DIR/config/app.toml + +# faster blocks +sed -i 's/timeout_commit = "5s"/timeout_commit = "'$TIMEOUT_COMMIT'"/g' $HOME_DIR/config/config.toml + +# Start the node with 0 gas fees +BINARY start --pruning=nothing --minimum-gas-prices=0uterp --rpc.laddr="tcp://0.0.0.0:$RPC" \ No newline at end of file From b18432fd162db4fa82c02ab2a6ecc1e3ebe17023 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 28 Jun 2023 02:09:38 -0400 Subject: [PATCH 03/14] remove tokenfactory --- app/app.go | 28 +- app/apptesting/test_suite.go | 3 +- app/wasm.go | 14 + .../v1beta1/authority_metadata.proto | 17 - .../tokenfactory/v1beta1/genesis.proto | 32 - .../tokenfactory/v1beta1/params.proto | 18 - .../cosmwasm/tokenfactory/v1beta1/query.proto | 71 - proto/cosmwasm/tokenfactory/v1beta1/tx.proto | 120 - x/tokenfactory/README.md | 155 -- x/tokenfactory/bindings/custom_msg_test.go | 277 -- x/tokenfactory/bindings/custom_query_test.go | 75 - x/tokenfactory/bindings/helpers_test.go | 108 - x/tokenfactory/bindings/message_plugin.go | 321 --- x/tokenfactory/bindings/queries.go | 57 - x/tokenfactory/bindings/query_plugin.go | 123 - .../bindings/testdata/download_releases.sh | 16 - .../bindings/testdata/token_reflect.wasm | Bin 285428 -> 0 bytes x/tokenfactory/bindings/testdata/version.txt | 1 - x/tokenfactory/bindings/types/msg.go | 60 - x/tokenfactory/bindings/types/query.go | 59 - x/tokenfactory/bindings/types/types.go | 37 - x/tokenfactory/bindings/validate_msg_test.go | 429 --- .../bindings/validate_queries_test.go | 115 - x/tokenfactory/bindings/wasm.go | 30 - x/tokenfactory/client/cli/query.go | 122 - x/tokenfactory/client/cli/tx.go | 200 -- x/tokenfactory/keeper/admins.go | 49 - x/tokenfactory/keeper/admins_test.go | 404 --- x/tokenfactory/keeper/bankactions.go | 73 - x/tokenfactory/keeper/createdenom.go | 86 - x/tokenfactory/keeper/createdenom_test.go | 163 -- x/tokenfactory/keeper/creators.go | 27 - x/tokenfactory/keeper/genesis.go | 58 - x/tokenfactory/keeper/genesis_test.go | 59 - x/tokenfactory/keeper/grpc_query.go | 35 - x/tokenfactory/keeper/keeper.go | 82 - x/tokenfactory/keeper/keeper_test.go | 64 - x/tokenfactory/keeper/msg_server.go | 191 -- x/tokenfactory/keeper/msg_server_test.go | 247 -- x/tokenfactory/keeper/params.go | 18 - x/tokenfactory/module.go | 220 -- x/tokenfactory/simulation/genesis.go | 26 - x/tokenfactory/simulation/operations.go | 404 --- x/tokenfactory/simulation/params.go | 24 - x/tokenfactory/testhelpers/consts.go | 8 - x/tokenfactory/testhelpers/suite.go | 66 - x/tokenfactory/types/authorityMetadata.go | 15 - x/tokenfactory/types/authority_metadata.pb.go | 365 --- x/tokenfactory/types/authzcodec/codec.go | 24 - x/tokenfactory/types/codec.go | 46 - x/tokenfactory/types/denoms.go | 68 - x/tokenfactory/types/denoms_test.go | 132 - x/tokenfactory/types/errors.go | 22 - x/tokenfactory/types/events.go | 18 - x/tokenfactory/types/expected_keepers.go | 36 - x/tokenfactory/types/genesis.go | 51 - x/tokenfactory/types/genesis.pb.go | 669 ----- x/tokenfactory/types/genesis_test.go | 139 - x/tokenfactory/types/keys.go | 49 - x/tokenfactory/types/msgs.go | 247 -- x/tokenfactory/types/msgs_test.go | 451 ---- x/tokenfactory/types/params.go | 62 - x/tokenfactory/types/params.pb.go | 354 --- x/tokenfactory/types/query.pb.go | 1377 ---------- x/tokenfactory/types/query.pb.gw.go | 343 --- x/tokenfactory/types/tx.pb.go | 2329 ----------------- 66 files changed, 16 insertions(+), 11573 deletions(-) create mode 100644 app/wasm.go delete mode 100644 proto/cosmwasm/tokenfactory/v1beta1/authority_metadata.proto delete mode 100644 proto/cosmwasm/tokenfactory/v1beta1/genesis.proto delete mode 100644 proto/cosmwasm/tokenfactory/v1beta1/params.proto delete mode 100644 proto/cosmwasm/tokenfactory/v1beta1/query.proto delete mode 100644 proto/cosmwasm/tokenfactory/v1beta1/tx.proto delete mode 100644 x/tokenfactory/README.md delete mode 100644 x/tokenfactory/bindings/custom_msg_test.go delete mode 100644 x/tokenfactory/bindings/custom_query_test.go delete mode 100644 x/tokenfactory/bindings/helpers_test.go delete mode 100644 x/tokenfactory/bindings/message_plugin.go delete mode 100644 x/tokenfactory/bindings/queries.go delete mode 100644 x/tokenfactory/bindings/query_plugin.go delete mode 100755 x/tokenfactory/bindings/testdata/download_releases.sh delete mode 100644 x/tokenfactory/bindings/testdata/token_reflect.wasm delete mode 100644 x/tokenfactory/bindings/testdata/version.txt delete mode 100644 x/tokenfactory/bindings/types/msg.go delete mode 100644 x/tokenfactory/bindings/types/query.go delete mode 100644 x/tokenfactory/bindings/types/types.go delete mode 100644 x/tokenfactory/bindings/validate_msg_test.go delete mode 100644 x/tokenfactory/bindings/validate_queries_test.go delete mode 100644 x/tokenfactory/bindings/wasm.go delete mode 100644 x/tokenfactory/client/cli/query.go delete mode 100644 x/tokenfactory/client/cli/tx.go delete mode 100644 x/tokenfactory/keeper/admins.go delete mode 100644 x/tokenfactory/keeper/admins_test.go delete mode 100644 x/tokenfactory/keeper/bankactions.go delete mode 100644 x/tokenfactory/keeper/createdenom.go delete mode 100644 x/tokenfactory/keeper/createdenom_test.go delete mode 100644 x/tokenfactory/keeper/creators.go delete mode 100644 x/tokenfactory/keeper/genesis.go delete mode 100644 x/tokenfactory/keeper/genesis_test.go delete mode 100644 x/tokenfactory/keeper/grpc_query.go delete mode 100644 x/tokenfactory/keeper/keeper.go delete mode 100644 x/tokenfactory/keeper/keeper_test.go delete mode 100644 x/tokenfactory/keeper/msg_server.go delete mode 100644 x/tokenfactory/keeper/msg_server_test.go delete mode 100644 x/tokenfactory/keeper/params.go delete mode 100644 x/tokenfactory/module.go delete mode 100644 x/tokenfactory/simulation/genesis.go delete mode 100644 x/tokenfactory/simulation/operations.go delete mode 100644 x/tokenfactory/simulation/params.go delete mode 100644 x/tokenfactory/testhelpers/consts.go delete mode 100644 x/tokenfactory/testhelpers/suite.go delete mode 100644 x/tokenfactory/types/authorityMetadata.go delete mode 100644 x/tokenfactory/types/authority_metadata.pb.go delete mode 100644 x/tokenfactory/types/authzcodec/codec.go delete mode 100644 x/tokenfactory/types/codec.go delete mode 100644 x/tokenfactory/types/denoms.go delete mode 100644 x/tokenfactory/types/denoms_test.go delete mode 100644 x/tokenfactory/types/errors.go delete mode 100644 x/tokenfactory/types/events.go delete mode 100644 x/tokenfactory/types/expected_keepers.go delete mode 100644 x/tokenfactory/types/genesis.go delete mode 100644 x/tokenfactory/types/genesis.pb.go delete mode 100644 x/tokenfactory/types/genesis_test.go delete mode 100644 x/tokenfactory/types/keys.go delete mode 100644 x/tokenfactory/types/msgs.go delete mode 100644 x/tokenfactory/types/msgs_test.go delete mode 100644 x/tokenfactory/types/params.go delete mode 100644 x/tokenfactory/types/params.pb.go delete mode 100644 x/tokenfactory/types/query.pb.go delete mode 100644 x/tokenfactory/types/query.pb.gw.go delete mode 100644 x/tokenfactory/types/tx.pb.go diff --git a/app/app.go b/app/app.go index 3f679d4..d0dbfc8 100644 --- a/app/app.go +++ b/app/app.go @@ -125,8 +125,6 @@ import ( ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" ibcmock "github.com/cosmos/ibc-go/v7/testing/mock" - "github.com/terpnetwork/terp-core/x/tokenfactory/bindings" - "github.com/spf13/cast" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" @@ -135,11 +133,6 @@ import ( wasmkeeper "github.com/terpnetwork/terp-core/x/wasm/keeper" wasmtypes "github.com/terpnetwork/terp-core/x/wasm/types" - // token factory - "github.com/terpnetwork/terp-core/x/tokenfactory" - tokenfactorykeeper "github.com/terpnetwork/terp-core/x/tokenfactory/keeper" - tokenfactorytypes "github.com/terpnetwork/terp-core/x/tokenfactory/types" - // unnamed import of statik for swagger UI support _ "github.com/cosmos/cosmos-sdk/client/docs/statik" // statik for swagger UI support ) @@ -241,7 +234,6 @@ var ( ica.AppModuleBasic{}, ibcfee.AppModuleBasic{}, icq.AppModuleBasic{}, - tokenfactory.AppModuleBasic{}, ) // module account permissions @@ -258,7 +250,6 @@ var ( icqtypes.ModuleName: nil, icatypes.ModuleName: nil, wasm.ModuleName: {authtypes.Burner}, - tokenfactorytypes.ModuleName: {authtypes.Minter, authtypes.Burner}, } ) @@ -304,7 +295,6 @@ type TerpApp struct { IBCFeeKeeper ibcfeekeeper.Keeper ICQKeeper icqkeeper.Keeper ICAControllerKeeper icacontrollerkeeper.Keeper - TokenFactoryKeeper tokenfactorykeeper.Keeper ICAHostKeeper icahostkeeper.Keeper TransferKeeper ibctransferkeeper.Keeper WasmKeeper wasm.Keeper @@ -376,7 +366,6 @@ func NewTerpApp( icqtypes.StoreKey, icahosttypes.StoreKey, icacontrollertypes.StoreKey, - tokenfactorytypes.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) @@ -481,14 +470,6 @@ func NewTerpApp( authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) - app.TokenFactoryKeeper = tokenfactorykeeper.NewKeeper( - keys[tokenfactorytypes.StoreKey], - app.GetSubspace(tokenfactorytypes.ModuleName), - app.AccountKeeper, - app.BankKeeper, - app.DistrKeeper, - ) - invCheckPeriod := cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)) app.CrisisKeeper = crisiskeeper.NewKeeper( appCodec, @@ -661,9 +642,7 @@ func NewTerpApp( // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks - // See https://github.com/CosmWasm/cosmwasm/blob/main/docs/CAPABILITIES-BUILT-IN.md - availableCapabilities := "iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,token_factory" - wasmOpts = append(bindings.RegisterCustomPlugins(&app.BankKeeper, &app.TokenFactoryKeeper), wasmOpts...) + availableCapabilities := strings.Join(AllCapabilities(), ",") app.WasmKeeper = wasm.NewKeeper( appCodec, keys[wasm.StoreKey], @@ -761,7 +740,6 @@ func NewTerpApp( groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), nftmodule.NewAppModule(appCodec, app.NFTKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), - tokenfactory.NewAppModule(app.TokenFactoryKeeper, app.AccountKeeper, app.BankKeeper), wasm.NewAppModule(appCodec, &app.WasmKeeper, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.MsgServiceRouter(), app.GetSubspace(wasmtypes.ModuleName)), ibc.NewAppModule(app.IBCKeeper), transfer.NewAppModule(app.TransferKeeper), @@ -788,7 +766,6 @@ func NewTerpApp( icatypes.ModuleName, ibcfeetypes.ModuleName, icqtypes.ModuleName, - tokenfactorytypes.ModuleName, wasm.ModuleName, ) @@ -805,7 +782,6 @@ func NewTerpApp( icatypes.ModuleName, ibcfeetypes.ModuleName, icqtypes.ModuleName, - tokenfactorytypes.ModuleName, wasm.ModuleName, ) @@ -830,7 +806,6 @@ func NewTerpApp( ibcfeetypes.ModuleName, icqtypes.ModuleName, // wasm after ibc transfer - tokenfactorytypes.ModuleName, wasm.ModuleName, } app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) @@ -1163,7 +1138,6 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino paramsKeeper.Subspace(crisistypes.ModuleName) paramsKeeper.Subspace(ibctransfertypes.ModuleName) paramsKeeper.Subspace(ibcexported.ModuleName) - paramsKeeper.Subspace(tokenfactorytypes.ModuleName) paramsKeeper.Subspace(icahosttypes.SubModuleName) paramsKeeper.Subspace(icacontrollertypes.SubModuleName) paramsKeeper.Subspace(icqtypes.ModuleName) diff --git a/app/apptesting/test_suite.go b/app/apptesting/test_suite.go index 6974e77..6d13d41 100644 --- a/app/apptesting/test_suite.go +++ b/app/apptesting/test_suite.go @@ -28,9 +28,8 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - authzcodec "github.com/terpnetwork/terp-core/x/tokenfactory/types/authzcodec" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/terpnetwork/terp-core/app" diff --git a/app/wasm.go b/app/wasm.go new file mode 100644 index 0000000..a01074f --- /dev/null +++ b/app/wasm.go @@ -0,0 +1,14 @@ +package app + +// AllCapabilities returns all capabilities available with the current wasmvm +// See https://github.com/CosmWasm/cosmwasm/blob/main/docs/CAPABILITIES-BUILT-IN.md +// This functionality is going to be moved upstream: https://github.com/CosmWasm/wasmvm/issues/425 +func AllCapabilities() []string { + return []string{ + "iterator", + "staking", + "stargate", + "cosmwasm_1_1", + "cosmwasm_1_2", + } +} diff --git a/proto/cosmwasm/tokenfactory/v1beta1/authority_metadata.proto b/proto/cosmwasm/tokenfactory/v1beta1/authority_metadata.proto deleted file mode 100644 index 3d27b3d..0000000 --- a/proto/cosmwasm/tokenfactory/v1beta1/authority_metadata.proto +++ /dev/null @@ -1,17 +0,0 @@ -syntax = "proto3"; -package osmosis.tokenfactory.v1beta1; - -import "cosmos/base/v1beta1/coin.proto"; -import "gogoproto/gogo.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/tokenfactory/types"; - -// DenomAuthorityMetadata specifies metadata for addresses that have specific -// capabilities over a token factory denom. Right now there is only one Admin -// permission, but is planned to be extended to the future. -message DenomAuthorityMetadata { - option (gogoproto.equal) = true; - - // Can be empty for no admin, or a valid terp address - string admin = 1 [ (gogoproto.moretags) = "yaml:\"admin\"" ]; -} \ No newline at end of file diff --git a/proto/cosmwasm/tokenfactory/v1beta1/genesis.proto b/proto/cosmwasm/tokenfactory/v1beta1/genesis.proto deleted file mode 100644 index 8b123c5..0000000 --- a/proto/cosmwasm/tokenfactory/v1beta1/genesis.proto +++ /dev/null @@ -1,32 +0,0 @@ -syntax = "proto3"; -package osmosis.tokenfactory.v1beta1; - -import "gogoproto/gogo.proto"; -import "cosmwasm/tokenfactory/v1beta1/authority_metadata.proto"; -import "cosmwasm/tokenfactory/v1beta1/params.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/tokenfactory/types"; - -// GenesisState defines the tokenfactory module's genesis state. -message GenesisState { - // params defines the paramaters of the module. - Params params = 1 [ (gogoproto.nullable) = false ]; - - repeated GenesisDenom factory_denoms = 2 [ - (gogoproto.moretags) = "yaml:\"factory_denoms\"", - (gogoproto.nullable) = false - ]; -} - -// GenesisDenom defines a tokenfactory denom that is defined within genesis -// state. The structure contains DenomAuthorityMetadata which defines the -// denom's admin. -message GenesisDenom { - option (gogoproto.equal) = true; - - string denom = 1 [ (gogoproto.moretags) = "yaml:\"denom\"" ]; - DenomAuthorityMetadata authority_metadata = 2 [ - (gogoproto.moretags) = "yaml:\"authority_metadata\"", - (gogoproto.nullable) = false - ]; -} \ No newline at end of file diff --git a/proto/cosmwasm/tokenfactory/v1beta1/params.proto b/proto/cosmwasm/tokenfactory/v1beta1/params.proto deleted file mode 100644 index 2871b8b..0000000 --- a/proto/cosmwasm/tokenfactory/v1beta1/params.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; -package osmosis.tokenfactory.v1beta1; - -import "gogoproto/gogo.proto"; -import "cosmwasm/tokenfactory/v1beta1/authority_metadata.proto"; -import "cosmos_proto/cosmos.proto"; -import "cosmos/base/v1beta1/coin.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/tokenfactory/types"; - -// Params defines the parameters for the tokenfactory module. -message Params { - repeated cosmos.base.v1beta1.Coin denom_creation_fee = 1 [ - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", - (gogoproto.moretags) = "yaml:\"denom_creation_fee\"", - (gogoproto.nullable) = false - ]; -} \ No newline at end of file diff --git a/proto/cosmwasm/tokenfactory/v1beta1/query.proto b/proto/cosmwasm/tokenfactory/v1beta1/query.proto deleted file mode 100644 index d7e4c42..0000000 --- a/proto/cosmwasm/tokenfactory/v1beta1/query.proto +++ /dev/null @@ -1,71 +0,0 @@ -syntax = "proto3"; -package osmosis.tokenfactory.v1beta1; - -import "gogoproto/gogo.proto"; -import "google/api/annotations.proto"; -import "cosmos/base/query/v1beta1/pagination.proto"; -import "cosmwasm/tokenfactory/v1beta1/authority_metadata.proto"; -import "cosmwasm/tokenfactory/v1beta1/params.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/tokenfactory/types"; - -// Query defines the gRPC querier service. -service Query { - // Params defines a gRPC query method that returns the tokenfactory module's - // parameters. - rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { - option (google.api.http).get = "/osmosis/tokenfactory/v1beta1/params"; - } - - // DenomAuthorityMetadata defines a gRPC query method for fetching - // DenomAuthorityMetadata for a particular denom. - rpc DenomAuthorityMetadata(QueryDenomAuthorityMetadataRequest) - returns (QueryDenomAuthorityMetadataResponse) { - option (google.api.http).get = - "/osmosis/tokenfactory/v1beta1/denoms/{denom}/authority_metadata"; - } - - // DenomsFromCreator defines a gRPC query method for fetching all - // denominations created by a specific admin/creator. - rpc DenomsFromCreator(QueryDenomsFromCreatorRequest) - returns (QueryDenomsFromCreatorResponse) { - option (google.api.http).get = - "/osmosis/tokenfactory/v1beta1/denoms_from_creator/{creator}"; - } -} - -// QueryParamsRequest is the request type for the Query/Params RPC method. -message QueryParamsRequest {} - -// QueryParamsResponse is the response type for the Query/Params RPC method. -message QueryParamsResponse { - // params defines the parameters of the module. - Params params = 1 [ (gogoproto.nullable) = false ]; -} - -// QueryDenomAuthorityMetadataRequest defines the request structure for the -// DenomAuthorityMetadata gRPC query. -message QueryDenomAuthorityMetadataRequest { - string denom = 1 [ (gogoproto.moretags) = "yaml:\"denom\"" ]; -} - -// QueryDenomAuthorityMetadataResponse defines the response structure for the -// DenomAuthorityMetadata gRPC query. -message QueryDenomAuthorityMetadataResponse { - DenomAuthorityMetadata authority_metadata = 1 [ - (gogoproto.moretags) = "yaml:\"authority_metadata\"", - (gogoproto.nullable) = false - ]; -} - -// QueryDenomsFromCreatorRequest defines the request structure for the -// DenomsFromCreator gRPC query. -message QueryDenomsFromCreatorRequest { - string creator = 1 [ (gogoproto.moretags) = "yaml:\"creator\"" ]; -} - -// QueryDenomsFromCreatorRequest defines the response structure for the -// DenomsFromCreator gRPC query. -message QueryDenomsFromCreatorResponse { - repeated string denoms = 1 [ (gogoproto.moretags) = "yaml:\"denoms\"" ]; -} \ No newline at end of file diff --git a/proto/cosmwasm/tokenfactory/v1beta1/tx.proto b/proto/cosmwasm/tokenfactory/v1beta1/tx.proto deleted file mode 100644 index 365bf5f..0000000 --- a/proto/cosmwasm/tokenfactory/v1beta1/tx.proto +++ /dev/null @@ -1,120 +0,0 @@ -syntax = "proto3"; -package osmosis.tokenfactory.v1beta1; - -import "gogoproto/gogo.proto"; -import "cosmos/base/v1beta1/coin.proto"; -import "cosmos/bank/v1beta1/bank.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/tokenfactory/types"; - -// Msg defines the tokefactory module's gRPC message service. -service Msg { - // CreateDenom Creates a denom of factory/{creator address}/{subdenom} given - // the denom creator address and the subdenom. Subdenoms can contain - // [a-zA-Z0-9./]. - rpc CreateDenom(MsgCreateDenom) returns (MsgCreateDenomResponse); - // Mint is message type that represents a request to mint a new denom. - rpc Mint(MsgMint) returns (MsgMintResponse); - // Burn message type that represents a request to burn (i.e., destroy) a - // denom. - rpc Burn(MsgBurn) returns (MsgBurnResponse); - // A message type that represents a request to change the administrator of the - // denom. - rpc ChangeAdmin(MsgChangeAdmin) returns (MsgChangeAdminResponse); - // A message type that represents a request to set metadata for a - // denomination. - rpc SetDenomMetadata(MsgSetDenomMetadata) - returns (MsgSetDenomMetadataResponse); - - // ForceTransfer is deactivated for now because we need to think through edge - // cases rpc ForceTransfer(MsgForceTransfer) returns - // (MsgForceTransferResponse); -} - -// MsgCreateDenom defines the message structure for the CreateDenom gRPC service -// method. It allows an account to create a new denom. It requires a sender -// address and a sub denomination. The (sender_address, sub_denomination) tuple -// must be unique and cannot be re-used. -// -// The resulting denom created is defined as -// . The resulting denom's admin is -// originally set to be the creator, but this can be changed later. The token -// denom does not indicate the current admin. -message MsgCreateDenom { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - // subdenom can be up to 44 "alphanumeric" characters long. - string subdenom = 2 [ (gogoproto.moretags) = "yaml:\"subdenom\"" ]; -} - -// MsgCreateDenomResponse is the return value of MsgCreateDenom -// It returns the full string of the newly created denom. -message MsgCreateDenomResponse { - string new_token_denom = 1 - [ (gogoproto.moretags) = "yaml:\"new_token_denom\"" ]; -} - -// MsgMint is the sdk.Msg type for allowing an admin account to mint -// more of a token. For now, we only support minting to the sender account -message MsgMint { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - cosmos.base.v1beta1.Coin amount = 2 [ - (gogoproto.moretags) = "yaml:\"amount\"", - (gogoproto.nullable) = false - ]; -} -// MsgMintResponse defines the response structure for an executed -// MsgMint message. -message MsgMintResponse {} - -// MsgBurn is the sdk.Msg type for allowing an admin account to burn -// a token. For now, we only support burning from the sender account. -message MsgBurn { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - cosmos.base.v1beta1.Coin amount = 2 [ - (gogoproto.moretags) = "yaml:\"amount\"", - (gogoproto.nullable) = false - ]; -} -// MsgBurnResponse defines the response structure for an executed -// MsgBurn message. -message MsgBurnResponse {} - -// MsgChangeAdmin is the sdk.Msg type for allowing an admin account to reassign -// adminship of a denom to a new account -message MsgChangeAdmin { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - string denom = 2 [ (gogoproto.moretags) = "yaml:\"denom\"" ]; - string new_admin = 3 [ (gogoproto.moretags) = "yaml:\"new_admin\"" ]; -} - -// MsgChangeAdminResponse defines the response structure for an executed -// MsgChangeAdmin message. -message MsgChangeAdminResponse {} - -// message MsgForceTransfer { -// string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; -// cosmos.base.v1beta1.Coin amount = 2 [ -// (gogoproto.moretags) = "yaml:\"amount\"", -// (gogoproto.nullable) = false -// ]; -// string transferFromAddress = 3 -// [ (gogoproto.moretags) = "yaml:\"transfer_from_address\"" ]; -// string transferToAddress = 4 -// [ (gogoproto.moretags) = "yaml:\"transfer_to_address\"" ]; -// } - -// message MsgForceTransferResponse {} - -// MsgSetDenomMetadata is the sdk.Msg type for allowing an admin account to set -// the denom's bank metadata. -message MsgSetDenomMetadata { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - cosmos.bank.v1beta1.Metadata metadata = 2 [ - (gogoproto.moretags) = "yaml:\"metadata\"", - (gogoproto.nullable) = false - ]; -} - -// MsgSetDenomMetadataResponse defines the response structure for an executed -// MsgSetDenomMetadata message. -message MsgSetDenomMetadataResponse {} \ No newline at end of file diff --git a/x/tokenfactory/README.md b/x/tokenfactory/README.md deleted file mode 100644 index 7d6644e..0000000 --- a/x/tokenfactory/README.md +++ /dev/null @@ -1,155 +0,0 @@ -# Token Factory - -The tokenfactory module allows any account to create a new token with -the name `factory/{creator address}/{subdenom}`. Because tokens are -namespaced by creator address, this allows token minting to be -permissionless, due to not needing to resolve name collisions. A single -account can create multiple denoms, by providing a unique subdenom for each -created denom. Once a denom is created, the original creator is given -"admin" privileges over the asset. This allows them to: - -- Mint their denom to any account -- Burn their denom from any account -- Create a transfer of their denom between any two accounts -- Change the admin. In the future, more admin capabilities may be added. Admins - can choose to share admin privileges with other accounts using the authz - module. The `ChangeAdmin` functionality, allows changing the master admin - account, or even setting it to `""`, meaning no account has admin privileges - of the asset. - -## Messages - -### CreateDenom - -Creates a denom of `factory/{creator address}/{subdenom}` given the denom creator -address and the subdenom. Subdenoms can contain `[a-zA-Z0-9./]`. - -```go -message MsgCreateDenom { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - string subdenom = 2 [ (gogoproto.moretags) = "yaml:\"subdenom\"" ]; -} -``` - -**State Modifications:** - -- Fund community pool with the denom creation fee from the creator address, set - in `Params`. -- Set `DenomMetaData` via bank keeper. -- Set `AuthorityMetadata` for the given denom to store the admin for the created - denom `factory/{creator address}/{subdenom}`. Admin is automatically set as the - Msg sender. -- Add denom to the `CreatorPrefixStore`, where a state of denoms created per - creator is kept. - -### Mint - -Minting of a specific denom is only allowed for the current admin. -Note, the current admin is defaulted to the creator of the denom. - -```go -message MsgMint { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - cosmos.base.v1beta1.Coin amount = 2 [ - (gogoproto.moretags) = "yaml:\"amount\"", - (gogoproto.nullable) = false - ]; -} -``` - -**State Modifications:** - -- Safety check the following - - Check that the denom minting is created via `tokenfactory` module - - Check that the sender of the message is the admin of the denom -- Mint designated amount of tokens for the denom via `bank` module - -### Burn - -Burning of a specific denom is only allowed for the current admin. -Note, the current admin is defaulted to the creator of the denom. - -```go -message MsgBurn { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - cosmos.base.v1beta1.Coin amount = 2 [ - (gogoproto.moretags) = "yaml:\"amount\"", - (gogoproto.nullable) = false - ]; -} -``` - -**State Modifications:** - -- Saftey check the following - - Check that the denom minting is created via `tokenfactory` module - - Check that the sender of the message is the admin of the denom -- Burn designated amount of tokens for the denom via `bank` module - -### ChangeAdmin - -Change the admin of a denom. Note, this is only allowed to be called by the current admin of the denom. - -```go -message MsgChangeAdmin { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - string denom = 2 [ (gogoproto.moretags) = "yaml:\"denom\"" ]; - string newAdmin = 3 [ (gogoproto.moretags) = "yaml:\"new_admin\"" ]; -} -``` - -### SetDenomMetadata - -Setting of metadata for a specific denom is only allowed for the admin of the denom. -It allows the overwriting of the denom metadata in the bank module. - -```go -message MsgChangeAdmin { - string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - cosmos.bank.v1beta1.Metadata metadata = 2 [ (gogoproto.moretags) = "yaml:\"metadata\"", (gogoproto.nullable) = false ]; -} -``` - -**State Modifications:** - -- Check that sender of the message is the admin of denom -- Modify `AuthorityMetadata` state entry to change the admin of the denom - -## Expectations from the chain - -The chain's bech32 prefix for addresses can be at most 16 characters long. - -This comes from denoms having a 128 byte maximum length, enforced from the SDK, -and us setting longest_subdenom to be 44 bytes. - -A token factory token's denom is: `factory/{creator address}/{subdenom}` - -Splitting up into sub-components, this has: - -- `len(factory) = 7` -- `2 * len("/") = 2` -- `len(longest_subdenom)` -- `len(creator_address) = len(bech32(longest_addr_length, chain_addr_prefix))`. - -Longest addr length at the moment is `32 bytes`. Due to SDK error correction -settings, this means `len(bech32(32, chain_addr_prefix)) = len(chain_addr_prefix) + 1 + 58`. -Adding this all, we have a total length constraint of `128 = 7 + 2 + len(longest_subdenom) + len(longest_chain_addr_prefix) + 1 + 58`. -Therefore `len(longest_subdenom) + len(longest_chain_addr_prefix) = 128 - (7 + 2 + 1 + 58) = 60`. - -The choice between how we standardized the split these 60 bytes between maxes -from longest_subdenom and longest_chain_addr_prefix is somewhat arbitrary. -Considerations going into this: - -- Per [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) - the technically longest HRP for a 32 byte address ('data field') is 31 bytes. - (Comes from encode(data) = 59 bytes, and max length = 90 bytes) -- subdenom should be at least 32 bytes so hashes can go into it -- longer subdenoms are very helpful for creating human readable denoms -- chain addresses should prefer being smaller. The longest HRP in cosmos to date is 11 bytes. (`persistence`) - -For explicitness, its currently set to `len(longest_subdenom) = 44` and `len(longest_chain_addr_prefix) = 16`. - -Please note, if the SDK increases the maximum length of a denom from 128 bytes, -these caps should increase. - -So please don't make code rely on these max lengths for parsing. diff --git a/x/tokenfactory/bindings/custom_msg_test.go b/x/tokenfactory/bindings/custom_msg_test.go deleted file mode 100644 index a21bc45..0000000 --- a/x/tokenfactory/bindings/custom_msg_test.go +++ /dev/null @@ -1,277 +0,0 @@ -package bindings_test - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/keeper" - - "github.com/terpnetwork/terp-core/app" - bindings "github.com/terpnetwork/terp-core/x/tokenfactory/bindings/types" - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func TestCreateDenomMsg(t *testing.T) { - creator := RandomAccountAddress() - terp, ctx := SetupCustomApp(t, creator) - - lucky := RandomAccountAddress() - reflect := instantiateReflectContract(t, ctx, terp, lucky) - require.NotEmpty(t, reflect) - - // Fund reflect contract with 100 base denom creation fees - reflectAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) - fundAccount(t, ctx, terp, reflect, reflectAmount) - - msg := bindings.TokenMsg{CreateDenom: &bindings.CreateDenom{ - Subdenom: "SUN", - }} - err := executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) - - // query the denom and see if it matches - query := bindings.TokenQuery{ - FullDenom: &bindings.FullDenom{ - CreatorAddr: reflect.String(), - Subdenom: "SUN", - }, - } - resp := bindings.FullDenomResponse{} - queryCustom(t, ctx, terp, reflect, query, &resp) - - require.Equal(t, resp.Denom, fmt.Sprintf("factory/%s/SUN", reflect.String())) -} - -func TestMintMsg(t *testing.T) { - creator := RandomAccountAddress() - terp, ctx := SetupCustomApp(t, creator) - - lucky := RandomAccountAddress() - reflect := instantiateReflectContract(t, ctx, terp, lucky) - require.NotEmpty(t, reflect) - - // Fund reflect contract with 100 base denom creation fees - reflectAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) - fundAccount(t, ctx, terp, reflect, reflectAmount) - - // lucky was broke - balances := terp.BankKeeper.GetAllBalances(ctx, lucky) - require.Empty(t, balances) - - // Create denom for minting - msg := bindings.TokenMsg{CreateDenom: &bindings.CreateDenom{ - Subdenom: "SUN", - }} - err := executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) - sunDenom := fmt.Sprintf("factory/%s/%s", reflect.String(), msg.CreateDenom.Subdenom) - - amount, ok := sdk.NewIntFromString("808010808") - require.True(t, ok) - msg = bindings.TokenMsg{MintTokens: &bindings.MintTokens{ - Denom: sunDenom, - Amount: amount, - MintToAddress: lucky.String(), - }} - err = executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) - - balances = terp.BankKeeper.GetAllBalances(ctx, lucky) - require.Len(t, balances, 1) - coin := balances[0] - require.Equal(t, amount, coin.Amount) - require.Contains(t, coin.Denom, "factory/") - - // query the denom and see if it matches - query := bindings.TokenQuery{ - FullDenom: &bindings.FullDenom{ - CreatorAddr: reflect.String(), - Subdenom: "SUN", - }, - } - resp := bindings.FullDenomResponse{} - queryCustom(t, ctx, terp, reflect, query, &resp) - - require.Equal(t, resp.Denom, coin.Denom) - - // mint the same denom again - err = executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) - - balances = terp.BankKeeper.GetAllBalances(ctx, lucky) - require.Len(t, balances, 1) - coin = balances[0] - require.Equal(t, amount.MulRaw(2), coin.Amount) - require.Contains(t, coin.Denom, "factory/") - - // query the denom and see if it matches - query = bindings.TokenQuery{ - FullDenom: &bindings.FullDenom{ - CreatorAddr: reflect.String(), - Subdenom: "SUN", - }, - } - resp = bindings.FullDenomResponse{} - queryCustom(t, ctx, terp, reflect, query, &resp) - - require.Equal(t, resp.Denom, coin.Denom) - - // now mint another amount / denom - // create it first - msg = bindings.TokenMsg{CreateDenom: &bindings.CreateDenom{ - Subdenom: "MOON", - }} - err = executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) - moonDenom := fmt.Sprintf("factory/%s/%s", reflect.String(), msg.CreateDenom.Subdenom) - - amount = amount.SubRaw(1) - msg = bindings.TokenMsg{MintTokens: &bindings.MintTokens{ - Denom: moonDenom, - Amount: amount, - MintToAddress: lucky.String(), - }} - err = executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) - - balances = terp.BankKeeper.GetAllBalances(ctx, lucky) - require.Len(t, balances, 2) - coin = balances[0] - require.Equal(t, amount, coin.Amount) - require.Contains(t, coin.Denom, "factory/") - - // query the denom and see if it matches - query = bindings.TokenQuery{ - FullDenom: &bindings.FullDenom{ - CreatorAddr: reflect.String(), - Subdenom: "MOON", - }, - } - resp = bindings.FullDenomResponse{} - queryCustom(t, ctx, terp, reflect, query, &resp) - - require.Equal(t, resp.Denom, coin.Denom) - - // and check the first denom is unchanged - coin = balances[1] - require.Equal(t, amount.AddRaw(1).MulRaw(2), coin.Amount) - require.Contains(t, coin.Denom, "factory/") - - // query the denom and see if it matches - query = bindings.TokenQuery{ - FullDenom: &bindings.FullDenom{ - CreatorAddr: reflect.String(), - Subdenom: "SUN", - }, - } - resp = bindings.FullDenomResponse{} - queryCustom(t, ctx, terp, reflect, query, &resp) - - require.Equal(t, resp.Denom, coin.Denom) -} - -func TestBurnMsg(t *testing.T) { - creator := RandomAccountAddress() - terp, ctx := SetupCustomApp(t, creator) - - lucky := RandomAccountAddress() - reflect := instantiateReflectContract(t, ctx, terp, lucky) - require.NotEmpty(t, reflect) - - // Fund reflect contract with 100 base denom creation fees - reflectAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) - fundAccount(t, ctx, terp, reflect, reflectAmount) - - // lucky was broke - balances := terp.BankKeeper.GetAllBalances(ctx, lucky) - require.Empty(t, balances) - - // Create denom for minting - msg := bindings.TokenMsg{CreateDenom: &bindings.CreateDenom{ - Subdenom: "SUN", - }} - err := executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) - sunDenom := fmt.Sprintf("factory/%s/%s", reflect.String(), msg.CreateDenom.Subdenom) - - amount, ok := sdk.NewIntFromString("808010808") - require.True(t, ok) - - msg = bindings.TokenMsg{MintTokens: &bindings.MintTokens{ - Denom: sunDenom, - Amount: amount, - MintToAddress: lucky.String(), - }} - err = executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) - - // can't burn from different address - msg = bindings.TokenMsg{BurnTokens: &bindings.BurnTokens{ - Denom: sunDenom, - Amount: amount, - BurnFromAddress: lucky.String(), - }} - err = executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.Error(t, err) - - // lucky needs to send balance to reflect contract to burn it - luckyBalance := terp.BankKeeper.GetAllBalances(ctx, lucky) - err = terp.BankKeeper.SendCoins(ctx, lucky, reflect, luckyBalance) - require.NoError(t, err) - - msg = bindings.TokenMsg{BurnTokens: &bindings.BurnTokens{ - Denom: sunDenom, - Amount: amount, - BurnFromAddress: reflect.String(), - }} - err = executeCustom(t, ctx, terp, reflect, lucky, msg, sdk.Coin{}) - require.NoError(t, err) -} - -type ReflectExec struct { - ReflectMsg *ReflectMsgs `json:"reflect_msg,omitempty"` - ReflectSubMsg *ReflectSubMsgs `json:"reflect_sub_msg,omitempty"` -} - -type ReflectMsgs struct { - Msgs []wasmvmtypes.CosmosMsg `json:"msgs"` -} - -type ReflectSubMsgs struct { - Msgs []wasmvmtypes.SubMsg `json:"msgs"` -} - -func executeCustom(t *testing.T, ctx sdk.Context, terp *app.TerpApp, contract sdk.AccAddress, sender sdk.AccAddress, msg bindings.TokenMsg, funds sdk.Coin) error { //nolint:unparam // funds currently always receives nil, but it may receive something else in the future - t.Helper() - wrapped := bindings.TokenFactoryMsg{ - Token: &msg, - } - customBz, err := json.Marshal(wrapped) - require.NoError(t, err) - - reflectMsg := ReflectExec{ - ReflectMsg: &ReflectMsgs{ - Msgs: []wasmvmtypes.CosmosMsg{{ - Custom: customBz, - }}, - }, - } - reflectBz, err := json.Marshal(reflectMsg) - require.NoError(t, err) - - // no funds sent if amount is 0 - var coins sdk.Coins - if !funds.Amount.IsNil() { - coins = sdk.Coins{funds} - } - - contractKeeper := keeper.NewDefaultPermissionKeeper(terp.WasmKeeper) - _, err = contractKeeper.Execute(ctx, contract, sender, reflectBz, coins) - return err -} diff --git a/x/tokenfactory/bindings/custom_query_test.go b/x/tokenfactory/bindings/custom_query_test.go deleted file mode 100644 index 67e3491..0000000 --- a/x/tokenfactory/bindings/custom_query_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package bindings_test - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/app" - bindings "github.com/terpnetwork/terp-core/x/tokenfactory/bindings/types" -) - -func TestQueryFullDenom(t *testing.T) { - actor := RandomAccountAddress() - tokenz, ctx := SetupCustomApp(t, actor) - - reflect := instantiateReflectContract(t, ctx, tokenz, actor) - require.NotEmpty(t, reflect) - - // query full denom - query := bindings.TokenQuery{ - FullDenom: &bindings.FullDenom{ - CreatorAddr: reflect.String(), - Subdenom: "ustart", - }, - } - resp := bindings.FullDenomResponse{} - queryCustom(t, ctx, tokenz, reflect, query, &resp) - - expected := fmt.Sprintf("factory/%s/ustart", reflect.String()) - require.EqualValues(t, expected, resp.Denom) -} - -type ReflectQuery struct { - Chain *ChainRequest `json:"chain,omitempty"` -} - -type ChainRequest struct { - Request wasmvmtypes.QueryRequest `json:"request"` -} - -type ChainResponse struct { - Data []byte `json:"data"` -} - -func queryCustom(t *testing.T, ctx sdk.Context, tokenz *app.TerpApp, contract sdk.AccAddress, request bindings.TokenQuery, response interface{}) { - t.Helper() - wrapped := bindings.TokenFactoryQuery{ - Token: &request, - } - msgBz, err := json.Marshal(wrapped) - require.NoError(t, err) - fmt.Println(string(msgBz)) - - query := ReflectQuery{ - Chain: &ChainRequest{ - Request: wasmvmtypes.QueryRequest{Custom: msgBz}, - }, - } - queryBz, err := json.Marshal(query) - require.NoError(t, err) - fmt.Println(string(queryBz)) - - resBz, err := tokenz.WasmKeeper.QuerySmart(ctx, contract, queryBz) - require.NoError(t, err) - var resp ChainResponse - err = json.Unmarshal(resBz, &resp) - require.NoError(t, err) - err = json.Unmarshal(resp.Data, response) - require.NoError(t, err) -} diff --git a/x/tokenfactory/bindings/helpers_test.go b/x/tokenfactory/bindings/helpers_test.go deleted file mode 100644 index 6857249..0000000 --- a/x/tokenfactory/bindings/helpers_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package bindings_test - -import ( - "os" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" - - "github.com/terpnetwork/terp-core/app" - "github.com/terpnetwork/terp-core/x/wasm" - "github.com/terpnetwork/terp-core/x/wasm/keeper" -) - -func CreateTestInput() (*app.TerpApp, sdk.Context) { - var emptyWasmOpts []wasm.Option - - terp := app.Setup(&testing.T{}, emptyWasmOpts...) - ctx := terp.BaseApp.NewContext(false, tmproto.Header{Height: 1, ChainID: "terp-1", Time: time.Now().UTC()}) - return terp, ctx -} - -func FundAccount(t *testing.T, ctx sdk.Context, terp *app.TerpApp, acct sdk.AccAddress) { - t.Helper() - // TODO: - // err := simapp.FundAccount(terp.BankKeeper, ctx, acct, sdk.NewCoins( - // sdk.NewCoin("uosmo", sdk.NewInt(10000000000)), - // )) - // require.NoError(t, err) -} - -// we need to make this deterministic (same every test run), as content might affect gas costs -func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { - key := ed25519.GenPrivKey() - pub := key.PubKey() - addr := sdk.AccAddress(pub.Address()) - return key, pub, addr -} - -func RandomAccountAddress() sdk.AccAddress { - _, _, addr := keyPubAddr() - return addr -} - -func RandomBech32AccountAddress() string { - return RandomAccountAddress().String() -} - -func storeReflectCode(t *testing.T, ctx sdk.Context, tokenz *app.TerpApp, addr sdk.AccAddress) uint64 { - t.Helper() - wasmCode, err := os.ReadFile("./testdata/token_reflect.wasm") - require.NoError(t, err) - - contractKeeper := keeper.NewDefaultPermissionKeeper(tokenz.WasmKeeper) - codeID, _, err := contractKeeper.Create(ctx, addr, wasmCode, nil) - require.NoError(t, err) - - return codeID -} - -func instantiateReflectContract(t *testing.T, ctx sdk.Context, tokenz *app.TerpApp, funder sdk.AccAddress) sdk.AccAddress { - t.Helper() - initMsgBz := []byte("{}") - contractKeeper := keeper.NewDefaultPermissionKeeper(tokenz.WasmKeeper) - codeID := uint64(1) - addr, _, err := contractKeeper.Instantiate(ctx, codeID, funder, funder, initMsgBz, "demo contract", nil) - require.NoError(t, err) - - return addr -} - -func fundAccount(t *testing.T, ctx sdk.Context, tokenz *app.TerpApp, addr sdk.AccAddress, coins sdk.Coins) { - t.Helper() - // TODO: - // err := simapp.FundAccount( - // tokenz.BankKeeper, - // ctx, - // addr, - // coins, - // ) - - // require.NoError(t, err) - err := tokenz.BankKeeper.MintCoins(ctx, minttypes.ModuleName, coins) - require.NoError(t, err) - err = tokenz.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, coins) - require.NoError(t, err) -} - -func SetupCustomApp(t *testing.T, addr sdk.AccAddress) (*app.TerpApp, sdk.Context) { - t.Helper() - tokenz, ctx := CreateTestInput() - wasmKeeper := tokenz.WasmKeeper - - storeReflectCode(t, ctx, tokenz, addr) - - cInfo := wasmKeeper.GetCodeInfo(ctx, 1) - require.NotNil(t, cInfo) - - return tokenz, ctx -} diff --git a/x/tokenfactory/bindings/message_plugin.go b/x/tokenfactory/bindings/message_plugin.go deleted file mode 100644 index 0e67ca2..0000000 --- a/x/tokenfactory/bindings/message_plugin.go +++ /dev/null @@ -1,321 +0,0 @@ -package bindings - -import ( - "encoding/json" - - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - - wasmkeeper "github.com/terpnetwork/terp-core/x/wasm/keeper" - - bindingstypes "github.com/terpnetwork/terp-core/x/tokenfactory/bindings/types" - tokenfactorykeeper "github.com/terpnetwork/terp-core/x/tokenfactory/keeper" - tokenfactorytypes "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -// CustomMessageDecorator returns decorator for custom CosmWasm bindings messages -func CustomMessageDecorator(bank *bankkeeper.BaseKeeper, tokenFactory *tokenfactorykeeper.Keeper) func(wasmkeeper.Messenger) wasmkeeper.Messenger { - return func(old wasmkeeper.Messenger) wasmkeeper.Messenger { - return &CustomMessenger{ - wrapped: old, - bank: bank, - tokenFactory: tokenFactory, - } - } -} - -type CustomMessenger struct { - wrapped wasmkeeper.Messenger - bank *bankkeeper.BaseKeeper - tokenFactory *tokenfactorykeeper.Keeper -} - -var _ wasmkeeper.Messenger = (*CustomMessenger)(nil) - -// DispatchMsg executes on the contractMsg. -func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { - if msg.Custom != nil { - // only handle the happy path where this is really creating / minting / swapping ... - // leave everything else for the wrapped version - var contractMsg bindingstypes.TokenFactoryMsg - if err := json.Unmarshal(msg.Custom, &contractMsg); err != nil { - return nil, nil, errorsmod.Wrap(err, "token factory msg") - } - if contractMsg.Token == nil { - return nil, nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "nil token field") - } - tokenMsg := contractMsg.Token - - if tokenMsg.CreateDenom != nil { - return m.createDenom(ctx, contractAddr, tokenMsg.CreateDenom) - } - if tokenMsg.MintTokens != nil { - return m.mintTokens(ctx, contractAddr, tokenMsg.MintTokens) - } - if tokenMsg.ChangeAdmin != nil { - return m.changeAdmin(ctx, contractAddr, tokenMsg.ChangeAdmin) - } - if tokenMsg.BurnTokens != nil { - return m.burnTokens(ctx, contractAddr, tokenMsg.BurnTokens) - } - if tokenMsg.SetMetadata != nil { - return m.setMetadata(ctx, contractAddr, tokenMsg.SetMetadata) - } - } - return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) -} - -// createDenom creates a new token denom -func (m *CustomMessenger) createDenom(ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindingstypes.CreateDenom) ([]sdk.Event, [][]byte, error) { - bz, err := PerformCreateDenom(m.tokenFactory, m.bank, ctx, contractAddr, createDenom) - if err != nil { - return nil, nil, errorsmod.Wrap(err, "perform create denom") - } - // TODO: double check how this is all encoded to the contract - return nil, [][]byte{bz}, nil -} - -// PerformCreateDenom is used with createDenom to create a token denom; validates the msgCreateDenom. -func PerformCreateDenom(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindingstypes.CreateDenom) ([]byte, error) { - if createDenom == nil { - return nil, wasmvmtypes.InvalidRequest{Err: "create denom null create denom"} - } - - msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) - - msgCreateDenom := tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.Subdenom) - - if err := msgCreateDenom.ValidateBasic(); err != nil { - return nil, errorsmod.Wrap(err, "failed validating MsgCreateDenom") - } - - // Create denom - resp, err := msgServer.CreateDenom( - sdk.WrapSDKContext(ctx), - msgCreateDenom, - ) - if err != nil { - return nil, errorsmod.Wrap(err, "creating denom") - } - - if createDenom.Metadata != nil { - newDenom := resp.NewTokenDenom - err := PerformSetMetadata(f, b, ctx, contractAddr, newDenom, *createDenom.Metadata) - if err != nil { - return nil, errorsmod.Wrap(err, "setting metadata") - } - } - - return resp.Marshal() -} - -// mintTokens mints tokens of a specified denom to an address. -func (m *CustomMessenger) mintTokens(ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindingstypes.MintTokens) ([]sdk.Event, [][]byte, error) { - err := PerformMint(m.tokenFactory, m.bank, ctx, contractAddr, mint) - if err != nil { - return nil, nil, errorsmod.Wrap(err, "perform mint") - } - return nil, nil, nil -} - -// PerformMint used with mintTokens to validate the mint message and mint through token factory. -func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindingstypes.MintTokens) error { - if mint == nil { - return wasmvmtypes.InvalidRequest{Err: "mint token null mint"} - } - rcpt, err := parseAddress(mint.MintToAddress) - if err != nil { - return err - } - - coin := sdk.Coin{Denom: mint.Denom, Amount: mint.Amount} - sdkMsg := tokenfactorytypes.NewMsgMint(contractAddr.String(), coin) - if err = sdkMsg.ValidateBasic(); err != nil { - return err - } - - // Mint through token factory / message server - msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) - _, err = msgServer.Mint(sdk.WrapSDKContext(ctx), sdkMsg) - if err != nil { - return errorsmod.Wrap(err, "minting coins from message") - } - err = b.SendCoins(ctx, contractAddr, rcpt, sdk.NewCoins(coin)) - if err != nil { - return errorsmod.Wrap(err, "sending newly minted coins from message") - } - return nil -} - -// changeAdmin changes the admin. -func (m *CustomMessenger) changeAdmin(ctx sdk.Context, contractAddr sdk.AccAddress, changeAdmin *bindingstypes.ChangeAdmin) ([]sdk.Event, [][]byte, error) { - err := ChangeAdmin(m.tokenFactory, ctx, contractAddr, changeAdmin) - if err != nil { - return nil, nil, errorsmod.Wrap(err, "failed to change admin") - } - return nil, nil, nil -} - -// ChangeAdmin is used with changeAdmin to validate changeAdmin messages and to dispatch. -func ChangeAdmin(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, changeAdmin *bindingstypes.ChangeAdmin) error { - if changeAdmin == nil { - return wasmvmtypes.InvalidRequest{Err: "changeAdmin is nil"} - } - newAdminAddr, err := parseAddress(changeAdmin.NewAdminAddress) - if err != nil { - return err - } - - changeAdminMsg := tokenfactorytypes.NewMsgChangeAdmin(contractAddr.String(), changeAdmin.Denom, newAdminAddr.String()) - if err := changeAdminMsg.ValidateBasic(); err != nil { - return err - } - - msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) - _, err = msgServer.ChangeAdmin(sdk.WrapSDKContext(ctx), changeAdminMsg) - if err != nil { - return errorsmod.Wrap(err, "failed changing admin from message") - } - return nil -} - -// burnTokens burns tokens. -func (m *CustomMessenger) burnTokens(ctx sdk.Context, contractAddr sdk.AccAddress, burn *bindingstypes.BurnTokens) ([]sdk.Event, [][]byte, error) { - err := PerformBurn(m.tokenFactory, ctx, contractAddr, burn) - if err != nil { - return nil, nil, errorsmod.Wrap(err, "perform burn") - } - return nil, nil, nil -} - -// PerformBurn performs token burning after validating tokenBurn message. -func PerformBurn(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, burn *bindingstypes.BurnTokens) error { - if burn == nil { - return wasmvmtypes.InvalidRequest{Err: "burn token null mint"} - } - if burn.BurnFromAddress != "" && burn.BurnFromAddress != contractAddr.String() { - return wasmvmtypes.InvalidRequest{Err: "BurnFromAddress must be \"\""} - } - - coin := sdk.Coin{Denom: burn.Denom, Amount: burn.Amount} - sdkMsg := tokenfactorytypes.NewMsgBurn(contractAddr.String(), coin) - if err := sdkMsg.ValidateBasic(); err != nil { - return err - } - - // Burn through token factory / message server - msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) - _, err := msgServer.Burn(sdk.WrapSDKContext(ctx), sdkMsg) - if err != nil { - return errorsmod.Wrap(err, "burning coins from message") - } - return nil -} - -// createDenom creates a new token denom -func (m *CustomMessenger) setMetadata(ctx sdk.Context, contractAddr sdk.AccAddress, setMetadata *bindingstypes.SetMetadata) ([]sdk.Event, [][]byte, error) { - err := PerformSetMetadata(m.tokenFactory, m.bank, ctx, contractAddr, setMetadata.Denom, setMetadata.Metadata) - if err != nil { - return nil, nil, errorsmod.Wrap(err, "perform create denom") - } - return nil, nil, nil -} - -// PerformSetMetadata is used with setMetadata to add new metadata -// It also is called inside CreateDenom if optional metadata field is set -func PerformSetMetadata(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, denom string, metadata bindingstypes.Metadata) error { - // ensure contract address is admin of denom - auth, err := f.GetAuthorityMetadata(ctx, denom) - if err != nil { - return err - } - if auth.Admin != contractAddr.String() { - return wasmvmtypes.InvalidRequest{Err: "only admin can set metadata"} - } - - // ensure we are setting proper denom metadata (bank uses Base field, fill it if missing) - if metadata.Base == "" { - metadata.Base = denom - } else if metadata.Base != denom { - // this is the key that we set - return wasmvmtypes.InvalidRequest{Err: "Base must be the same as denom"} - } - - // Create and validate the metadata - bankMetadata := WasmMetadataToSdk(metadata) - if err := bankMetadata.Validate(); err != nil { - return err - } - - b.SetDenomMetaData(ctx, bankMetadata) - return nil -} - -// GetFullDenom is a function, not method, so the message_plugin can use it -func GetFullDenom(contract string, subDenom string) (string, error) { - // Address validation - if _, err := parseAddress(contract); err != nil { - return "", err - } - fullDenom, err := tokenfactorytypes.GetTokenDenom(contract, subDenom) - if err != nil { - return "", errorsmod.Wrap(err, "validate sub-denom") - } - - return fullDenom, nil -} - -// parseAddress parses address from bech32 string and verifies its format. -func parseAddress(addr string) (sdk.AccAddress, error) { - parsed, err := sdk.AccAddressFromBech32(addr) - if err != nil { - return nil, errorsmod.Wrap(err, "address from bech32") - } - err = sdk.VerifyAddressFormat(parsed) - if err != nil { - return nil, errorsmod.Wrap(err, "verify address format") - } - return parsed, nil -} - -func WasmMetadataToSdk(metadata bindingstypes.Metadata) banktypes.Metadata { - denoms := []*banktypes.DenomUnit{} - for _, unit := range metadata.DenomUnits { - denoms = append(denoms, &banktypes.DenomUnit{ - Denom: unit.Denom, - Exponent: unit.Exponent, - Aliases: unit.Aliases, - }) - } - return banktypes.Metadata{ - Description: metadata.Description, - Display: metadata.Display, - Base: metadata.Base, - Name: metadata.Name, - Symbol: metadata.Symbol, - DenomUnits: denoms, - } -} - -func SdkMetadataToWasm(metadata banktypes.Metadata) *bindingstypes.Metadata { - denoms := []bindingstypes.DenomUnit{} - for _, unit := range metadata.DenomUnits { - denoms = append(denoms, bindingstypes.DenomUnit{ - Denom: unit.Denom, - Exponent: unit.Exponent, - Aliases: unit.Aliases, - }) - } - return &bindingstypes.Metadata{ - Description: metadata.Description, - Display: metadata.Display, - Base: metadata.Base, - Name: metadata.Name, - Symbol: metadata.Symbol, - DenomUnits: denoms, - } -} diff --git a/x/tokenfactory/bindings/queries.go b/x/tokenfactory/bindings/queries.go deleted file mode 100644 index 15dd308..0000000 --- a/x/tokenfactory/bindings/queries.go +++ /dev/null @@ -1,57 +0,0 @@ -package bindings - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - - bindingstypes "github.com/terpnetwork/terp-core/x/tokenfactory/bindings/types" - tokenfactorykeeper "github.com/terpnetwork/terp-core/x/tokenfactory/keeper" -) - -type QueryPlugin struct { - bankKeeper *bankkeeper.BaseKeeper - tokenFactoryKeeper *tokenfactorykeeper.Keeper -} - -// NewQueryPlugin returns a reference to a new QueryPlugin. -func NewQueryPlugin(b *bankkeeper.BaseKeeper, tfk *tokenfactorykeeper.Keeper) *QueryPlugin { - return &QueryPlugin{ - bankKeeper: b, - tokenFactoryKeeper: tfk, - } -} - -// GetDenomAdmin is a query to get denom admin. -func (qp QueryPlugin) GetDenomAdmin(ctx sdk.Context, denom string) (*bindingstypes.AdminResponse, error) { - metadata, err := qp.tokenFactoryKeeper.GetAuthorityMetadata(ctx, denom) - if err != nil { - return nil, fmt.Errorf("failed to get admin for denom: %s", denom) - } - return &bindingstypes.AdminResponse{Admin: metadata.Admin}, nil -} - -func (qp QueryPlugin) GetDenomsByCreator(ctx sdk.Context, creator string) (*bindingstypes.DenomsByCreatorResponse, error) { - // TODO: validate creator address - denoms := qp.tokenFactoryKeeper.GetDenomsFromCreator(ctx, creator) - return &bindingstypes.DenomsByCreatorResponse{Denoms: denoms}, nil -} - -func (qp QueryPlugin) GetMetadata(ctx sdk.Context, denom string) (*bindingstypes.MetadataResponse, error) { - metadata, found := qp.bankKeeper.GetDenomMetaData(ctx, denom) - var parsed *bindingstypes.Metadata - if found { - parsed = SdkMetadataToWasm(metadata) - } - return &bindingstypes.MetadataResponse{Metadata: parsed}, nil -} - -func (qp QueryPlugin) GetParams(ctx sdk.Context) (*bindingstypes.ParamsResponse, error) { - params := qp.tokenFactoryKeeper.GetParams(ctx) - return &bindingstypes.ParamsResponse{ - Params: bindingstypes.Params{ - DenomCreationFee: ConvertSdkCoinsToWasmCoins(params.DenomCreationFee), - }, - }, nil -} diff --git a/x/tokenfactory/bindings/query_plugin.go b/x/tokenfactory/bindings/query_plugin.go deleted file mode 100644 index 0dfbfeb..0000000 --- a/x/tokenfactory/bindings/query_plugin.go +++ /dev/null @@ -1,123 +0,0 @@ -package bindings - -import ( - "encoding/json" - "fmt" - - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - bindingstypes "github.com/terpnetwork/terp-core/x/tokenfactory/bindings/types" -) - -// CustomQuerier dispatches custom CosmWasm bindings queries. -func CustomQuerier(qp *QueryPlugin) func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { - return func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { - var contractQuery bindingstypes.TokenFactoryQuery - if err := json.Unmarshal(request, &contractQuery); err != nil { - return nil, errorsmod.Wrap(err, "terp query") - } - if contractQuery.Token == nil { - return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "nil token field") - } - tokenQuery := contractQuery.Token - - switch { - case tokenQuery.FullDenom != nil: - creator := tokenQuery.FullDenom.CreatorAddr - subdenom := tokenQuery.FullDenom.Subdenom - - fullDenom, err := GetFullDenom(creator, subdenom) - if err != nil { - return nil, errorsmod.Wrap(err, "terp full denom query") - } - - res := bindingstypes.FullDenomResponse{ - Denom: fullDenom, - } - - bz, err := json.Marshal(res) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to marshal FullDenomResponse") - } - - return bz, nil - - case tokenQuery.Admin != nil: - res, err := qp.GetDenomAdmin(ctx, tokenQuery.Admin.Denom) - if err != nil { - return nil, err - } - - bz, err := json.Marshal(res) - if err != nil { - return nil, fmt.Errorf("failed to JSON marshal AdminResponse: %w", err) - } - - return bz, nil - - case tokenQuery.Metadata != nil: - res, err := qp.GetMetadata(ctx, tokenQuery.Metadata.Denom) - if err != nil { - return nil, err - } - - bz, err := json.Marshal(res) - if err != nil { - return nil, fmt.Errorf("failed to JSON marshal MetadataResponse: %w", err) - } - - return bz, nil - - case tokenQuery.DenomsByCreator != nil: - res, err := qp.GetDenomsByCreator(ctx, tokenQuery.DenomsByCreator.Creator) - if err != nil { - return nil, err - } - - bz, err := json.Marshal(res) - if err != nil { - return nil, fmt.Errorf("failed to JSON marshal DenomsByCreatorResponse: %w", err) - } - - return bz, nil - - case tokenQuery.Params != nil: - res, err := qp.GetParams(ctx) - if err != nil { - return nil, err - } - - bz, err := json.Marshal(res) - if err != nil { - return nil, fmt.Errorf("failed to JSON marshal ParamsResponse: %w", err) - } - - return bz, nil - - default: - return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown token query variant"} - } - } -} - -// ConvertSdkCoinsToWasmCoins converts sdk type coins to wasm vm type coins -func ConvertSdkCoinsToWasmCoins(coins []sdk.Coin) wasmvmtypes.Coins { - var toSend wasmvmtypes.Coins - for _, coin := range coins { - c := ConvertSdkCoinToWasmCoin(coin) - toSend = append(toSend, c) - } - return toSend -} - -// ConvertSdkCoinToWasmCoin converts a sdk type coin to a wasm vm type coin -func ConvertSdkCoinToWasmCoin(coin sdk.Coin) wasmvmtypes.Coin { - return wasmvmtypes.Coin{ - Denom: coin.Denom, - // Note: gamm tokens have 18 decimal places, so 10^22 is common, no longer in u64 range - Amount: coin.Amount.String(), - } -} diff --git a/x/tokenfactory/bindings/testdata/download_releases.sh b/x/tokenfactory/bindings/testdata/download_releases.sh deleted file mode 100755 index 161a33f..0000000 --- a/x/tokenfactory/bindings/testdata/download_releases.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -set -o errexit -o nounset -o pipefail -command -v shellcheck > /dev/null && shellcheck "$0" - -if [ $# -ne 1 ]; then - echo "Usage: ./download_releases.sh RELEASE_TAG" - exit 1 -fi - -tag="$1" -url="https://github.com/CosmWasm/token-bindings/releases/download/$tag/token_reflect.wasm" -echo "Downloading $url ..." -wget -O "token_reflect.wasm" "$url" - -rm -f version.txt -echo "$tag" >version.txt \ No newline at end of file diff --git a/x/tokenfactory/bindings/testdata/token_reflect.wasm b/x/tokenfactory/bindings/testdata/token_reflect.wasm deleted file mode 100644 index 0d742e10fc20d98e5432e413032502d8ed98b5f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285428 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

90R9hsOfjf>BydfNz7?O=Pv+=5e@NQ?&k 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 O%Q}Cm?Oi3vD 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 0 { - require.Error(t, err) - actualErrMsg := err.Error() - require.Equal(t, spec.expErrMsg, actualErrMsg) - return - } - require.NoError(t, err) - }) - } -} - -func TestMint(t *testing.T) { - creator := RandomAccountAddress() - tokenz, ctx := SetupCustomApp(t, creator) - - // Fund actor with 100 base denom creation fees - tokenCreationFeeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) - fundAccount(t, ctx, tokenz, creator, tokenCreationFeeAmt) - - // Create denoms for valid mint tests - validDenom := bindings.CreateDenom{ - Subdenom: "MOON", - } - _, err := wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &validDenom) - require.NoError(t, err) - - emptyDenom := bindings.CreateDenom{ - Subdenom: "", - } - - require.Panics(t, func() { - _, err := wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &emptyDenom) - require.Error(t, err) - }) - // _, err = wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &emptyDenom) - // require.Error(t, err) - - validDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), validDenom.Subdenom) - emptyDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), emptyDenom.Subdenom) - - lucky := RandomAccountAddress() - - // lucky was broke - balances := tokenz.BankKeeper.GetAllBalances(ctx, lucky) - require.Empty(t, balances) - - amount, ok := sdk.NewIntFromString("8080") - require.True(t, ok) - - specs := map[string]struct { - mint *bindings.MintTokens - expErr bool - }{ - "valid mint": { - mint: &bindings.MintTokens{ - Denom: validDenomStr, - Amount: amount, - MintToAddress: lucky.String(), - }, - }, - "empty sub-denom": { - mint: &bindings.MintTokens{ - Denom: emptyDenomStr, - Amount: amount, - MintToAddress: lucky.String(), - }, - expErr: true, - }, - "nonexistent sub-denom": { - mint: &bindings.MintTokens{ - Denom: fmt.Sprintf("factory/%s/%s", creator.String(), "SUN"), - Amount: amount, - MintToAddress: lucky.String(), - }, - expErr: true, - }, - "invalid sub-denom": { - mint: &bindings.MintTokens{ - Denom: "sub-denom_2", - Amount: amount, - MintToAddress: lucky.String(), - }, - expErr: true, - }, - "zero amount": { - mint: &bindings.MintTokens{ - Denom: validDenomStr, - Amount: sdk.ZeroInt(), - MintToAddress: lucky.String(), - }, - expErr: true, - }, - "negative amount": { - mint: &bindings.MintTokens{ - Denom: validDenomStr, - Amount: amount.Neg(), - MintToAddress: lucky.String(), - }, - expErr: true, - }, - "empty recipient": { - mint: &bindings.MintTokens{ - Denom: validDenomStr, - Amount: amount, - MintToAddress: "", - }, - expErr: true, - }, - "invalid recipient": { - mint: &bindings.MintTokens{ - Denom: validDenomStr, - Amount: amount, - MintToAddress: "invalid", - }, - expErr: true, - }, - "null mint": { - mint: nil, - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // when - gotErr := wasmbinding.PerformMint(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, spec.mint) - // then - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - }) - } -} - -func TestBurn(t *testing.T) { - creator := RandomAccountAddress() - tokenz, ctx := SetupCustomApp(t, creator) - - // Fund actor with 100 base denom creation fees - tokenCreationFeeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) - fundAccount(t, ctx, tokenz, creator, tokenCreationFeeAmt) - - // Create denoms for valid burn tests - validDenom := bindings.CreateDenom{ - Subdenom: "MOON", - } - _, err := wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &validDenom) - require.NoError(t, err) - - emptyDenom := bindings.CreateDenom{ - Subdenom: "", - } - require.Panics(t, func() { - _, err := wasmbinding.PerformCreateDenom(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, &emptyDenom) - require.Error(t, err) - }) - - lucky := RandomAccountAddress() - - // lucky was broke - balances := tokenz.BankKeeper.GetAllBalances(ctx, lucky) - require.Empty(t, balances) - - validDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), validDenom.Subdenom) - emptyDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), emptyDenom.Subdenom) - mintAmount, ok := sdk.NewIntFromString("8080") - require.True(t, ok) - - specs := map[string]struct { - burn *bindings.BurnTokens - expErr bool - }{ - "valid burn": { - burn: &bindings.BurnTokens{ - Denom: validDenomStr, - Amount: mintAmount, - BurnFromAddress: creator.String(), - }, - expErr: false, - }, - "non admin address": { - burn: &bindings.BurnTokens{ - Denom: validDenomStr, - Amount: mintAmount, - BurnFromAddress: lucky.String(), - }, - expErr: true, - }, - "empty sub-denom": { - burn: &bindings.BurnTokens{ - Denom: emptyDenomStr, - Amount: mintAmount, - BurnFromAddress: creator.String(), - }, - expErr: true, - }, - "invalid sub-denom": { - burn: &bindings.BurnTokens{ - Denom: "sub-denom_2", - Amount: mintAmount, - BurnFromAddress: creator.String(), - }, - expErr: true, - }, - "non-minted denom": { - burn: &bindings.BurnTokens{ - Denom: fmt.Sprintf("factory/%s/%s", creator.String(), "SUN"), - Amount: mintAmount, - BurnFromAddress: creator.String(), - }, - expErr: true, - }, - "zero amount": { - burn: &bindings.BurnTokens{ - Denom: validDenomStr, - Amount: sdk.ZeroInt(), - BurnFromAddress: creator.String(), - }, - expErr: true, - }, - "negative amount": { - burn: nil, - expErr: true, - }, - "null burn": { - burn: &bindings.BurnTokens{ - Denom: validDenomStr, - Amount: mintAmount.Neg(), - BurnFromAddress: creator.String(), - }, - expErr: true, - }, - } - - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // Mint valid denom str and empty denom string for burn test - mintBinding := &bindings.MintTokens{ - Denom: validDenomStr, - Amount: mintAmount, - MintToAddress: creator.String(), - } - err := wasmbinding.PerformMint(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, mintBinding) - require.NoError(t, err) - - emptyDenomMintBinding := &bindings.MintTokens{ - Denom: emptyDenomStr, - Amount: mintAmount, - MintToAddress: creator.String(), - } - err = wasmbinding.PerformMint(&tokenz.TokenFactoryKeeper, &tokenz.BankKeeper, ctx, creator, emptyDenomMintBinding) - require.Error(t, err) - - // when - gotErr := wasmbinding.PerformBurn(&tokenz.TokenFactoryKeeper, ctx, creator, spec.burn) - // then - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - }) - } -} diff --git a/x/tokenfactory/bindings/validate_queries_test.go b/x/tokenfactory/bindings/validate_queries_test.go deleted file mode 100644 index c800103..0000000 --- a/x/tokenfactory/bindings/validate_queries_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package bindings_test - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" - - wasmbinding "github.com/terpnetwork/terp-core/x/tokenfactory/bindings" -) - -func TestFullDenom(t *testing.T) { - actor := RandomAccountAddress() - - specs := map[string]struct { - addr string - subdenom string - expFullDenom string - expErr bool - }{ - "valid address": { - addr: actor.String(), - subdenom: "subDenom1", - expFullDenom: fmt.Sprintf("factory/%s/subDenom1", actor.String()), - }, - "empty address": { - addr: "", - subdenom: "subDenom1", - expErr: true, - }, - "invalid address": { - addr: "invalid", - subdenom: "subDenom1", - expErr: true, - }, - "empty sub-denom": { - addr: actor.String(), - subdenom: "", - expFullDenom: fmt.Sprintf("factory/%s/", actor.String()), - }, - "invalid sub-denom (contains @)": { - addr: actor.String(), - subdenom: "sub@denom", - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // when - gotFullDenom, gotErr := wasmbinding.GetFullDenom(spec.addr, spec.subdenom) - // then - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expFullDenom, gotFullDenom, "exp %s but got %s", spec.expFullDenom, gotFullDenom) - }) - } -} - -func TestDenomAdmin(t *testing.T) { - addr := RandomAccountAddress() - app, ctx := SetupCustomApp(t, addr) - - // set token creation fee to zero to make testing easier - tfParams := app.TokenFactoryKeeper.GetParams(ctx) - tfParams.DenomCreationFee = sdk.NewCoins() - app.TokenFactoryKeeper.SetParams(ctx, tfParams) - - // create a subdenom via the token factory - admin := sdk.AccAddress([]byte("addr1_______________")) - tfDenom, err := app.TokenFactoryKeeper.CreateDenom(ctx, admin.String(), "subdenom") - require.NoError(t, err) - require.NotEmpty(t, tfDenom) - - queryPlugin := wasmbinding.NewQueryPlugin(&app.BankKeeper, &app.TokenFactoryKeeper) - - testCases := []struct { - name string - denom string - expectErr bool - expectAdmin string - }{ - { - name: "valid token factory denom", - denom: tfDenom, - expectAdmin: admin.String(), - }, - { - name: "invalid token factory denom", - denom: "uosmo", - expectErr: false, - expectAdmin: "", - }, - } - - for _, tc := range testCases { - tc := tc - - t.Run(tc.name, func(t *testing.T) { - resp, err := queryPlugin.GetDenomAdmin(ctx, tc.denom) - if tc.expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.NotNil(t, resp) - require.Equal(t, tc.expectAdmin, resp.Admin) - } - }) - } -} diff --git a/x/tokenfactory/bindings/wasm.go b/x/tokenfactory/bindings/wasm.go deleted file mode 100644 index 33ea401..0000000 --- a/x/tokenfactory/bindings/wasm.go +++ /dev/null @@ -1,30 +0,0 @@ -package bindings - -import ( - "github.com/terpnetwork/terp-core/x/wasm" - - wasmkeeper "github.com/terpnetwork/terp-core/x/wasm/keeper" - - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - - tokenfactorykeeper "github.com/terpnetwork/terp-core/x/tokenfactory/keeper" -) - -func RegisterCustomPlugins( - bank *bankkeeper.BaseKeeper, - tokenFactory *tokenfactorykeeper.Keeper, -) []wasmkeeper.Option { - wasmQueryPlugin := NewQueryPlugin(bank, tokenFactory) - - queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ - Custom: CustomQuerier(wasmQueryPlugin), - }) - messengerDecoratorOpt := wasmkeeper.WithMessageHandlerDecorator( - CustomMessageDecorator(bank, tokenFactory), - ) - - return []wasm.Option{ - queryPluginOpt, - messengerDecoratorOpt, - } -} diff --git a/x/tokenfactory/client/cli/query.go b/x/tokenfactory/client/cli/query.go deleted file mode 100644 index b23a7e2..0000000 --- a/x/tokenfactory/client/cli/query.go +++ /dev/null @@ -1,122 +0,0 @@ -package cli - -import ( - "fmt" - - // "strings" - - "github.com/spf13/cobra" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - - // "github.com/cosmos/cosmos-sdk/client/flags" - // sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -// GetQueryCmd returns the cli query commands for this module -func GetQueryCmd() *cobra.Command { - // Group tokenfactory queries under a subcommand - cmd := &cobra.Command{ - Use: types.ModuleName, - Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), - DisableFlagParsing: true, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, - } - - cmd.AddCommand( - GetParams(), - GetCmdDenomAuthorityMetadata(), - GetCmdDenomsFromCreator(), - ) - - return cmd -} - -// GetParams returns the params for the module -func GetParams() *cobra.Command { - cmd := &cobra.Command{ - Use: "params [flags]", - Short: "Get the params for the x/tokenfactory module", - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - - res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) - if err != nil { - return err - } - - return clientCtx.PrintProto(res) - }, - } - - flags.AddQueryFlagsToCmd(cmd) - - return cmd -} - -// GetCmdDenomAuthorityMetadata returns the authority metadata for a queried denom -func GetCmdDenomAuthorityMetadata() *cobra.Command { - cmd := &cobra.Command{ - Use: "denom-authority-metadata [denom] [flags]", - Short: "Get the authority metadata for a specific denom", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - - res, err := queryClient.DenomAuthorityMetadata(cmd.Context(), &types.QueryDenomAuthorityMetadataRequest{ - Denom: args[0], - }) - if err != nil { - return err - } - - return clientCtx.PrintProto(res) - }, - } - - flags.AddQueryFlagsToCmd(cmd) - - return cmd -} - -// GetCmdDenomsFromCreator a command to get a list of all tokens created by a specific creator address -func GetCmdDenomsFromCreator() *cobra.Command { - cmd := &cobra.Command{ - Use: "denoms-from-creator [creator address] [flags]", - Short: "Returns a list of all tokens created by a specific creator address", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - - res, err := queryClient.DenomsFromCreator(cmd.Context(), &types.QueryDenomsFromCreatorRequest{ - Creator: args[0], - }) - if err != nil { - return err - } - - return clientCtx.PrintProto(res) - }, - } - - flags.AddQueryFlagsToCmd(cmd) - - return cmd -} diff --git a/x/tokenfactory/client/cli/tx.go b/x/tokenfactory/client/cli/tx.go deleted file mode 100644 index 9b0aeff..0000000 --- a/x/tokenfactory/client/cli/tx.go +++ /dev/null @@ -1,200 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/tx" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -// GetTxCmd returns the transaction commands for this module -func GetTxCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: types.ModuleName, - Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), - DisableFlagParsing: true, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, - } - - cmd.AddCommand( - NewCreateDenomCmd(), - NewMintCmd(), - NewBurnCmd(), - // NewForceTransferCmd(), - NewChangeAdminCmd(), - ) - - return cmd -} - -// NewCreateDenomCmd broadcast MsgCreateDenom -func NewCreateDenomCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "create-denom [subdenom] [flags]", - Short: "create a new denom from an account", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) - if err != nil { - return err - } - - msg := types.NewMsgCreateDenom( - clientCtx.GetFromAddress().String(), - args[0], - ) - - return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -// NewMintCmd broadcast MsgMint -func NewMintCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "mint [amount] [flags]", - Short: "Mint a denom to an address. Must have admin authority to do so.", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) - if err != nil { - return err - } - - amount, err := sdk.ParseCoinNormalized(args[0]) - if err != nil { - return err - } - - msg := types.NewMsgMint( - clientCtx.GetFromAddress().String(), - amount, - ) - - return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -// NewBurnCmd broadcast MsgBurn -func NewBurnCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "burn [amount] [flags]", - Short: "Burn tokens from an address. Must have admin authority to do so.", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) - if err != nil { - return err - } - - amount, err := sdk.ParseCoinNormalized(args[0]) - if err != nil { - return err - } - - msg := types.NewMsgBurn( - clientCtx.GetFromAddress().String(), - amount, - ) - - return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -// // NewForceTransferCmd broadcast MsgForceTransfer -// func NewForceTransferCmd() *cobra.Command { -// cmd := &cobra.Command{ -// Use: "force-transfer [amount] [transfer-from-address] [transfer-to-address] [flags]", -// Short: "Force transfer tokens from one address to another address. Must have admin authority to do so.", -// Args: cobra.ExactArgs(3), -// RunE: func(cmd *cobra.Command, args []string) error { -// clientCtx, err := client.GetClientTxContext(cmd) -// if err != nil { -// return err -// } - -// txf := tx.NewFactoryCLI(clientCtx, cmd.Flags()).WithTxConfig(clientCtx.TxConfig).WithAccountRetriever(clientCtx.AccountRetriever) - -// amount, err := sdk.ParseCoinNormalized(args[0]) -// if err != nil { -// return err -// } - -// msg := types.NewMsgForceTransfer( -// clientCtx.GetFromAddress().String(), -// amount, -// args[1], -// args[2], -// ) - -// return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) -// }, -// } - -// flags.AddTxFlagsToCmd(cmd) -// return cmd -// } - -// NewChangeAdminCmd broadcast MsgChangeAdmin -func NewChangeAdminCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "change-admin [denom] [new-admin-address] [flags]", - Short: "Changes the admin address for a factory-created denom. Must have admin authority to do so.", - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) - if err != nil { - return err - } - - msg := types.NewMsgChangeAdmin( - clientCtx.GetFromAddress().String(), - args[0], - args[1], - ) - - return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) - }, - } - - flags.AddTxFlagsToCmd(cmd) - return cmd -} diff --git a/x/tokenfactory/keeper/admins.go b/x/tokenfactory/keeper/admins.go deleted file mode 100644 index 665895b..0000000 --- a/x/tokenfactory/keeper/admins.go +++ /dev/null @@ -1,49 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/gogoproto/proto" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -// GetAuthorityMetadata returns the authority metadata for a specific denom -func (k Keeper) GetAuthorityMetadata(ctx sdk.Context, denom string) (types.DenomAuthorityMetadata, error) { - bz := k.GetDenomPrefixStore(ctx, denom).Get([]byte(types.DenomAuthorityMetadataKey)) - - metadata := types.DenomAuthorityMetadata{} - err := proto.Unmarshal(bz, &metadata) - if err != nil { - return types.DenomAuthorityMetadata{}, err - } - return metadata, nil -} - -// setAuthorityMetadata stores authority metadata for a specific denom -func (k Keeper) setAuthorityMetadata(ctx sdk.Context, denom string, metadata types.DenomAuthorityMetadata) error { - err := metadata.Validate() - if err != nil { - return err - } - - store := k.GetDenomPrefixStore(ctx, denom) - - bz, err := proto.Marshal(&metadata) - if err != nil { - return err - } - - store.Set([]byte(types.DenomAuthorityMetadataKey), bz) - return nil -} - -func (k Keeper) setAdmin(ctx sdk.Context, denom string, admin string) error { - metadata, err := k.GetAuthorityMetadata(ctx, denom) - if err != nil { - return err - } - - metadata.Admin = admin - - return k.setAuthorityMetadata(ctx, denom, metadata) -} diff --git a/x/tokenfactory/keeper/admins_test.go b/x/tokenfactory/keeper/admins_test.go deleted file mode 100644 index 335320f..0000000 --- a/x/tokenfactory/keeper/admins_test.go +++ /dev/null @@ -1,404 +0,0 @@ -package keeper_test - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func (suite *KeeperTestSuite) TestAdminMsgs() { - addr0bal := int64(0) - addr1bal := int64(0) - - bankKeeper := suite.App.BankKeeper - - suite.CreateDefaultDenom() - // Make sure that the admin is set correctly - queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ - Denom: suite.defaultDenom, - }) - suite.Require().NoError(err) - suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) - - // Test minting to admins own account - _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) - addr0bal += 10 - suite.Require().NoError(err) - suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) - - // // Test force transferring - // _, err = suite.msgServer.ForceTransfer(sdk.WrapSDKContext(suite.Ctx), types.NewMsgForceTransfer(suite.TestAccs[0].String(), sdk.NewInt64Coin(denom, 5), suite.TestAccs[1].String(), suite.TestAccs[0].String())) - // suite.Require().NoError(err) - // suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], denom).IsEqual(sdk.NewInt64Coin(denom, 15))) - // suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], denom).IsEqual(sdk.NewInt64Coin(denom, 5))) - - // Test burning from own account - _, err = suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) - suite.Require().NoError(err) - suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal) - - // Test Change Admin - _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), types.NewMsgChangeAdmin(suite.TestAccs[0].String(), suite.defaultDenom, suite.TestAccs[1].String())) - suite.Require().NoError(err) - queryRes, err = suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ - Denom: suite.defaultDenom, - }) - suite.Require().NoError(err) - suite.Require().Equal(suite.TestAccs[1].String(), queryRes.AuthorityMetadata.Admin) - - // Make sure old admin can no longer do actions - _, err = suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) - suite.Require().Error(err) - - // Make sure the new admin works - _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[1].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) - addr1bal += 5 - suite.Require().NoError(err) - suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal) - - // Try setting admin to empty - _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), types.NewMsgChangeAdmin(suite.TestAccs[1].String(), suite.defaultDenom, "")) - suite.Require().NoError(err) - queryRes, err = suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ - Denom: suite.defaultDenom, - }) - suite.Require().NoError(err) - suite.Require().Equal("", queryRes.AuthorityMetadata.Admin) -} - -// TestMintDenom ensures the following properties of the MintMessage: -// * Noone can mint tokens for a denom that doesn't exist -// * Only the admin of a denom can mint tokens for it -// * The admin of a denom can mint tokens for it -func (suite *KeeperTestSuite) TestMintDenom() { - var addr0bal int64 - - // Create a denom - suite.CreateDefaultDenom() - - for _, tc := range []struct { - desc string - amount int64 - mintDenom string - admin string - valid bool - }{ - { - desc: "denom does not exist", - amount: 10, - mintDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", - admin: suite.TestAccs[0].String(), - valid: false, - }, - { - desc: "mint is not by the admin", - amount: 10, - mintDenom: suite.defaultDenom, - admin: suite.TestAccs[1].String(), - valid: false, - }, - { - desc: "success case", - amount: 10, - mintDenom: suite.defaultDenom, - admin: suite.TestAccs[0].String(), - valid: true, - }, - } { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - // Test minting to admins own account - bankKeeper := suite.App.BankKeeper - _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) - if tc.valid { - addr0bal += 10 - suite.Require().NoError(err) - suite.Require().Equal(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64(), addr0bal, bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) - } else { - suite.Require().Error(err) - } - }) - } -} - -func (suite *KeeperTestSuite) TestBurnDenom() { - var addr0bal int64 - - // Create a denom. - suite.CreateDefaultDenom() - - // mint 10 default token for testAcc[0] - _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) - suite.Require().NoError(err) - addr0bal += 10 - - for _, tc := range []struct { - desc string - amount int64 - burnDenom string - admin string - valid bool - }{ - { - desc: "denom does not exist", - amount: 10, - burnDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", - admin: suite.TestAccs[0].String(), - valid: false, - }, - { - desc: "burn is not by the admin", - amount: 10, - burnDenom: suite.defaultDenom, - admin: suite.TestAccs[1].String(), - valid: false, - }, - { - desc: "burn amount is bigger than minted amount", - amount: 1000, - burnDenom: suite.defaultDenom, - admin: suite.TestAccs[1].String(), - valid: false, - }, - { - desc: "success case", - amount: 10, - burnDenom: suite.defaultDenom, - admin: suite.TestAccs[0].String(), - valid: true, - }, - } { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - // Test minting to admins own account - bankKeeper := suite.App.BankKeeper - _, err := suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, 10))) - if tc.valid { - addr0bal -= 10 - suite.Require().NoError(err) - suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) - } else { - suite.Require().Error(err) - suite.Require().True(bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) - } - }) - } -} - -func (suite *KeeperTestSuite) TestChangeAdminDenom() { - for _, tc := range []struct { - desc string - msgChangeAdmin func(denom string) *types.MsgChangeAdmin - expectedChangeAdminPass bool - expectedAdminIndex int - msgMint func(denom string) *types.MsgMint - expectedMintPass bool - }{ - { - desc: "creator admin can't mint after setting to '' ", - msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { - return types.NewMsgChangeAdmin(suite.TestAccs[0].String(), denom, "") - }, - expectedChangeAdminPass: true, - expectedAdminIndex: -1, - msgMint: func(denom string) *types.MsgMint { - return types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(denom, 5)) - }, - expectedMintPass: false, - }, - { - desc: "non-admins can't change the existing admin", - msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { - return types.NewMsgChangeAdmin(suite.TestAccs[1].String(), denom, suite.TestAccs[2].String()) - }, - expectedChangeAdminPass: false, - expectedAdminIndex: 0, - }, - { - desc: "success change admin", - msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { - return types.NewMsgChangeAdmin(suite.TestAccs[0].String(), denom, suite.TestAccs[1].String()) - }, - expectedAdminIndex: 1, - expectedChangeAdminPass: true, - msgMint: func(denom string) *types.MsgMint { - return types.NewMsgMint(suite.TestAccs[1].String(), sdk.NewInt64Coin(denom, 5)) - }, - expectedMintPass: true, - }, - } { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - // setup test - suite.SetupTest() - - // Create a denom and mint - res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) - suite.Require().NoError(err) - - testDenom := res.GetNewTokenDenom() - - _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(testDenom, 10))) - suite.Require().NoError(err) - - _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), tc.msgChangeAdmin(testDenom)) - if tc.expectedChangeAdminPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - - queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ - Denom: testDenom, - }) - suite.Require().NoError(err) - - // expectedAdminIndex with negative value is assumed as admin with value of "" - const emptyStringAdminIndexFlag = -1 - if tc.expectedAdminIndex == emptyStringAdminIndexFlag { - suite.Require().Equal("", queryRes.AuthorityMetadata.Admin) - } else { - suite.Require().Equal(suite.TestAccs[tc.expectedAdminIndex].String(), queryRes.AuthorityMetadata.Admin) - } - - // we test mint to test if admin authority is performed properly after admin change. - if tc.msgMint != nil { - _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), tc.msgMint(testDenom)) - if tc.expectedMintPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - } - }) - } -} - -func (suite *KeeperTestSuite) TestSetDenomMetaData() { - // setup test - suite.SetupTest() - suite.CreateDefaultDenom() - - for _, tc := range []struct { - desc string - msgSetDenomMetadata types.MsgSetDenomMetadata - expectedPass bool - }{ - { - desc: "successful set denom metadata", - msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ - Description: "yeehaw", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: suite.defaultDenom, - Exponent: 0, - }, - { - Denom: "uosmo", - Exponent: 6, - }, - }, - Base: suite.defaultDenom, - Display: "uosmo", - Name: "OSMO", - Symbol: "OSMO", - }), - expectedPass: true, - }, - { - desc: "non existent factory denom name", - msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ - Description: "yeehaw", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: fmt.Sprintf("factory/%s/litecoin", suite.TestAccs[0].String()), - Exponent: 0, - }, - { - Denom: "uosmo", - Exponent: 6, - }, - }, - Base: fmt.Sprintf("factory/%s/litecoin", suite.TestAccs[0].String()), - Display: "uosmo", - Name: "OSMO", - Symbol: "OSMO", - }), - expectedPass: false, - }, - { - desc: "non-factory denom", - msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ - Description: "yeehaw", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: "uosmo", - Exponent: 0, - }, - { - Denom: "uosmoo", - Exponent: 6, - }, - }, - Base: "uosmo", - Display: "uosmoo", - Name: "OSMO", - Symbol: "OSMO", - }), - expectedPass: false, - }, - { - desc: "wrong admin", - msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[1].String(), banktypes.Metadata{ - Description: "yeehaw", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: suite.defaultDenom, - Exponent: 0, - }, - { - Denom: "uosmo", - Exponent: 6, - }, - }, - Base: suite.defaultDenom, - Display: "uosmo", - Name: "OSMO", - Symbol: "OSMO", - }), - expectedPass: false, - }, - { - desc: "invalid metadata (missing display denom unit)", - msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ - Description: "yeehaw", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: suite.defaultDenom, - Exponent: 0, - }, - }, - Base: suite.defaultDenom, - Display: "uosmo", - Name: "OSMO", - Symbol: "OSMO", - }), - expectedPass: false, - }, - } { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - bankKeeper := suite.App.BankKeeper - res, err := suite.msgServer.SetDenomMetadata(sdk.WrapSDKContext(suite.Ctx), &tc.msgSetDenomMetadata) - if tc.expectedPass { - suite.Require().NoError(err) - suite.Require().NotNil(res) - - md, found := bankKeeper.GetDenomMetaData(suite.Ctx, suite.defaultDenom) - suite.Require().True(found) - suite.Require().Equal(tc.msgSetDenomMetadata.Metadata.Name, md.Name) - } else { - suite.Require().Error(err) - } - }) - } -} diff --git a/x/tokenfactory/keeper/bankactions.go b/x/tokenfactory/keeper/bankactions.go deleted file mode 100644 index 803c4c3..0000000 --- a/x/tokenfactory/keeper/bankactions.go +++ /dev/null @@ -1,73 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error { - // verify that denom is an x/tokenfactory denom - _, _, err := types.DeconstructDenom(amount.Denom) - if err != nil { - return err - } - - err = k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) - if err != nil { - return err - } - - addr, err := sdk.AccAddressFromBech32(mintTo) - if err != nil { - return err - } - - return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, - addr, - sdk.NewCoins(amount)) -} - -func (k Keeper) burnFrom(ctx sdk.Context, amount sdk.Coin, burnFrom string) error { - // verify that denom is an x/tokenfactory denom - _, _, err := types.DeconstructDenom(amount.Denom) - if err != nil { - return err - } - - addr, err := sdk.AccAddressFromBech32(burnFrom) - if err != nil { - return err - } - - err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, - addr, - types.ModuleName, - sdk.NewCoins(amount)) - if err != nil { - return err - } - - return k.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) -} - -// forceTransfer is a helper function to transfer coins from one account to another -func (k Keeper) forceTransfer(ctx sdk.Context, amount sdk.Coin, fromAddr string, toAddr string) error { //nolint:unused - // verify that denom is an x/tokenfactory denom - _, _, err := types.DeconstructDenom(amount.Denom) - if err != nil { - return err - } - - fromSdkAddr, err := sdk.AccAddressFromBech32(fromAddr) - if err != nil { - return err - } - - toSdkAddr, err := sdk.AccAddressFromBech32(toAddr) - if err != nil { - return err - } - - return k.bankKeeper.SendCoins(ctx, fromSdkAddr, toSdkAddr, sdk.NewCoins(amount)) -} diff --git a/x/tokenfactory/keeper/createdenom.go b/x/tokenfactory/keeper/createdenom.go deleted file mode 100644 index 4c5fe3e..0000000 --- a/x/tokenfactory/keeper/createdenom.go +++ /dev/null @@ -1,86 +0,0 @@ -package keeper - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -// ConvertToBaseToken converts a fee amount in a whitelisted fee token to the base fee token amount -func (k Keeper) CreateDenom(ctx sdk.Context, creatorAddr string, subdenom string) (newTokenDenom string, err error) { - denom, err := k.validateCreateDenom(ctx, creatorAddr, subdenom) - if err != nil { - return "", err - } - - err = k.chargeForCreateDenom(ctx, creatorAddr, subdenom) - if err != nil { - return "", err - } - - err = k.createDenomAfterValidation(ctx, creatorAddr, denom) - return denom, err -} - -// Runs CreateDenom logic after the charge and all denom validation has been handled. -// Made into a second function for genesis initialization. -func (k Keeper) createDenomAfterValidation(ctx sdk.Context, creatorAddr string, denom string) (err error) { - denomMetaData := banktypes.Metadata{ - DenomUnits: []*banktypes.DenomUnit{{ - Denom: denom, - Exponent: 0, - }}, - Base: denom, - } - - k.bankKeeper.SetDenomMetaData(ctx, denomMetaData) - - authorityMetadata := types.DenomAuthorityMetadata{ - Admin: creatorAddr, - } - err = k.setAuthorityMetadata(ctx, denom, authorityMetadata) - if err != nil { - return err - } - - k.addDenomFromCreator(ctx, creatorAddr, denom) - return nil -} - -func (k Keeper) validateCreateDenom(ctx sdk.Context, creatorAddr string, subdenom string) (newTokenDenom string, err error) { - // Temporary check until IBC bug is sorted out - if k.bankKeeper.HasSupply(ctx, subdenom) { - return "", fmt.Errorf("temporary error until IBC bug is sorted out, " + - "can't create subdenoms that are the same as a native denom") - } - - denom, err := types.GetTokenDenom(creatorAddr, subdenom) - if err != nil { - return "", err - } - - _, found := k.bankKeeper.GetDenomMetaData(ctx, denom) - if found { - return "", types.ErrDenomExists - } - - return denom, nil -} - -func (k Keeper) chargeForCreateDenom(ctx sdk.Context, creatorAddr string, subdenom string) (err error) { //nolint:unparam - // Send creation fee to community pool - creationFee := k.GetParams(ctx).DenomCreationFee - accAddr, err := sdk.AccAddressFromBech32(creatorAddr) - if err != nil { - return err - } - if creationFee != nil { - if err := k.communityPoolKeeper.FundCommunityPool(ctx, creationFee, accAddr); err != nil { - return err - } - } - return nil -} diff --git a/x/tokenfactory/keeper/createdenom_test.go b/x/tokenfactory/keeper/createdenom_test.go deleted file mode 100644 index 1900bbf..0000000 --- a/x/tokenfactory/keeper/createdenom_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package keeper_test - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/testhelpers" - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func (suite *KeeperTestSuite) TestMsgCreateDenom() { - var ( - tokenFactoryKeeper = suite.App.TokenFactoryKeeper - bankKeeper = suite.App.BankKeeper - denomCreationFee = tokenFactoryKeeper.GetParams(suite.Ctx).DenomCreationFee - ) - - // Get balance of acc 0 before creating a denom - preCreateBalance := bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], denomCreationFee[0].Denom) - - // Creating a denom should work - res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) - suite.Require().NoError(err) - suite.Require().NotEmpty(res.GetNewTokenDenom()) - - // Make sure that the admin is set correctly - queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ - Denom: res.GetNewTokenDenom(), - }) - suite.Require().NoError(err) - suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) - - // Make sure that creation fee was deducted - postCreateBalance := bankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], tokenFactoryKeeper.GetParams(suite.Ctx).DenomCreationFee[0].Denom) - suite.Require().True(preCreateBalance.Sub(postCreateBalance).IsEqual(denomCreationFee[0])) - - // Make sure that a second version of the same denom can't be recreated - _, err = suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) - suite.Require().Error(err) - - // Creating a second denom should work - res, err = suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "litecoin")) - suite.Require().NoError(err) - suite.Require().NotEmpty(res.GetNewTokenDenom()) - - // Try querying all the denoms created by suite.TestAccs[0] - queryRes2, err := suite.queryClient.DenomsFromCreator(suite.Ctx.Context(), &types.QueryDenomsFromCreatorRequest{ - Creator: suite.TestAccs[0].String(), - }) - suite.Require().NoError(err) - suite.Require().Len(queryRes2.Denoms, 2) - - // Make sure that a second account can create a denom with the same subdenom - res, err = suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[1].String(), "bitcoin")) - suite.Require().NoError(err) - suite.Require().NotEmpty(res.GetNewTokenDenom()) - - // Make sure that an address with a "/" in it can't create denoms - _, err = suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom("osmosis.eth/creator", "bitcoin")) - suite.Require().Error(err) -} - -func (suite *KeeperTestSuite) TestCreateDenom() { - var ( - primaryDenom = types.DefaultParams().DenomCreationFee[0].Denom - secondaryDenom = testhelpers.SecondaryDenom - defaultDenomCreationFee = types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(primaryDenom, sdk.NewInt(50000000)))} - twoDenomCreationFee = types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(primaryDenom, sdk.NewInt(50000000)), sdk.NewCoin(secondaryDenom, sdk.NewInt(50000000)))} - nilCreationFee = types.Params{DenomCreationFee: nil} - largeCreationFee = types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(primaryDenom, sdk.NewInt(5000000000)))} - ) - - for _, tc := range []struct { - desc string - denomCreationFee types.Params - setup func() - subdenom string - valid bool - }{ - { - desc: "subdenom too long", - denomCreationFee: defaultDenomCreationFee, - subdenom: "assadsadsadasdasdsadsadsadsadsadsadsklkadaskkkdasdasedskhanhassyeunganassfnlksdflksafjlkasd", - valid: false, - }, - { - desc: "subdenom and creator pair already exists", - denomCreationFee: defaultDenomCreationFee, - setup: func() { - _, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) - suite.Require().NoError(err) - }, - subdenom: "bitcoin", - valid: false, - }, - { - desc: "success case: defaultDenomCreationFee", - denomCreationFee: defaultDenomCreationFee, - subdenom: "evmos", - valid: true, - }, - { - desc: "success case: twoDenomCreationFee", - denomCreationFee: twoDenomCreationFee, - subdenom: "catcoin", - valid: true, - }, - { - desc: "success case: nilCreationFee", - denomCreationFee: nilCreationFee, - subdenom: "czcoin", - valid: true, - }, - { - desc: "account doesn't have enough to pay for denom creation fee", - denomCreationFee: largeCreationFee, - subdenom: "tooexpensive", - valid: false, - }, - { - desc: "subdenom having invalid characters", - denomCreationFee: defaultDenomCreationFee, - subdenom: "bit/***///&&&/coin", - valid: false, - }, - } { - suite.SetupTest() - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - if tc.setup != nil { - tc.setup() - } - tokenFactoryKeeper := suite.App.TokenFactoryKeeper - bankKeeper := suite.App.BankKeeper - // Set denom creation fee in params - tokenFactoryKeeper.SetParams(suite.Ctx, tc.denomCreationFee) - denomCreationFee := tokenFactoryKeeper.GetParams(suite.Ctx).DenomCreationFee - suite.Require().Equal(tc.denomCreationFee.DenomCreationFee, denomCreationFee) - - // note balance, create a tokenfactory denom, then note balance again - preCreateBalance := bankKeeper.GetAllBalances(suite.Ctx, suite.TestAccs[0]) - res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), tc.subdenom)) - postCreateBalance := bankKeeper.GetAllBalances(suite.Ctx, suite.TestAccs[0]) - if tc.valid { - suite.Require().NoError(err) - suite.Require().True(preCreateBalance.Sub(postCreateBalance...).IsEqual(denomCreationFee)) - - // Make sure that the admin is set correctly - queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ - Denom: res.GetNewTokenDenom(), - }) - - suite.Require().NoError(err) - suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) - - } else { - suite.Require().Error(err) - // Ensure we don't charge if we expect an error - suite.Require().True(preCreateBalance.IsEqual(postCreateBalance)) - } - }) - } -} diff --git a/x/tokenfactory/keeper/creators.go b/x/tokenfactory/keeper/creators.go deleted file mode 100644 index 570d54b..0000000 --- a/x/tokenfactory/keeper/creators.go +++ /dev/null @@ -1,27 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func (k Keeper) addDenomFromCreator(ctx sdk.Context, creator, denom string) { - store := k.GetCreatorPrefixStore(ctx, creator) - store.Set([]byte(denom), []byte(denom)) -} - -func (k Keeper) GetDenomsFromCreator(ctx sdk.Context, creator string) []string { - store := k.GetCreatorPrefixStore(ctx, creator) - - iterator := store.Iterator(nil, nil) - defer iterator.Close() - - denoms := []string{} - for ; iterator.Valid(); iterator.Next() { - denoms = append(denoms, string(iterator.Key())) - } - return denoms -} - -func (k Keeper) GetAllDenomsIterator(ctx sdk.Context) sdk.Iterator { - return k.GetCreatorsPrefixStore(ctx).Iterator(nil, nil) -} diff --git a/x/tokenfactory/keeper/genesis.go b/x/tokenfactory/keeper/genesis.go deleted file mode 100644 index 52577e8..0000000 --- a/x/tokenfactory/keeper/genesis.go +++ /dev/null @@ -1,58 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -// InitGenesis initializes the tokenfactory module's state from a provided genesis -// state. -func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { - k.CreateModuleAccount(ctx) - - if genState.Params.DenomCreationFee == nil { - genState.Params.DenomCreationFee = sdk.NewCoins() - } - k.SetParams(ctx, genState.Params) - - for _, genDenom := range genState.GetFactoryDenoms() { - creator, _, err := types.DeconstructDenom(genDenom.GetDenom()) - if err != nil { - panic(err) - } - err = k.createDenomAfterValidation(ctx, creator, genDenom.GetDenom()) - if err != nil { - panic(err) - } - err = k.setAuthorityMetadata(ctx, genDenom.GetDenom(), genDenom.GetAuthorityMetadata()) - if err != nil { - panic(err) - } - } -} - -// ExportGenesis returns the tokenfactory module's exported genesis. -func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { - genDenoms := []types.GenesisDenom{} - iterator := k.GetAllDenomsIterator(ctx) - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - denom := string(iterator.Value()) - - authorityMetadata, err := k.GetAuthorityMetadata(ctx, denom) - if err != nil { - panic(err) - } - - genDenoms = append(genDenoms, types.GenesisDenom{ - Denom: denom, - AuthorityMetadata: authorityMetadata, - }) - } - - return &types.GenesisState{ - FactoryDenoms: genDenoms, - Params: k.GetParams(ctx), - } -} diff --git a/x/tokenfactory/keeper/genesis_test.go b/x/tokenfactory/keeper/genesis_test.go deleted file mode 100644 index a04ef9b..0000000 --- a/x/tokenfactory/keeper/genesis_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package keeper_test - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func (suite *KeeperTestSuite) TestGenesis() { - genesisState := types.GenesisState{ - FactoryDenoms: []types.GenesisDenom{ - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - }, - }, - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/diff-admin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "cosmos15czt5nhlnvayqq37xun9s9yus0d6y26dx74r5p", - }, - }, - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/litecoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - }, - }, - }, - } - - suite.SetupTestForInitGenesis() - app := suite.App - - // Test both with bank denom metadata set, and not set. - for i, denom := range genesisState.FactoryDenoms { - // hacky, sets bank metadata to exist if i != 0, to cover both cases. - if i != 0 { - app.BankKeeper.SetDenomMetaData(suite.Ctx, banktypes.Metadata{Base: denom.GetDenom()}) - } - } - - // check before initGenesis that the module account is nil - tokenfactoryModuleAccount := app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) - suite.Require().Nil(tokenfactoryModuleAccount) - - app.TokenFactoryKeeper.SetParams(suite.Ctx, types.Params{DenomCreationFee: sdk.Coins{sdk.NewInt64Coin("uosmo", 100)}}) - app.TokenFactoryKeeper.InitGenesis(suite.Ctx, genesisState) - - // check that the module account is now initialized - tokenfactoryModuleAccount = app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) - suite.Require().NotNil(tokenfactoryModuleAccount) - - exportedGenesis := app.TokenFactoryKeeper.ExportGenesis(suite.Ctx) - suite.Require().NotNil(exportedGenesis) - suite.Require().Equal(genesisState, *exportedGenesis) -} diff --git a/x/tokenfactory/keeper/grpc_query.go b/x/tokenfactory/keeper/grpc_query.go deleted file mode 100644 index 2ee7191..0000000 --- a/x/tokenfactory/keeper/grpc_query.go +++ /dev/null @@ -1,35 +0,0 @@ -package keeper - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -var _ types.QueryServer = Keeper{} - -func (k Keeper) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - params := k.GetParams(sdkCtx) - - return &types.QueryParamsResponse{Params: params}, nil -} - -func (k Keeper) DenomAuthorityMetadata(ctx context.Context, req *types.QueryDenomAuthorityMetadataRequest) (*types.QueryDenomAuthorityMetadataResponse, error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - - authorityMetadata, err := k.GetAuthorityMetadata(sdkCtx, req.GetDenom()) - if err != nil { - return nil, err - } - - return &types.QueryDenomAuthorityMetadataResponse{AuthorityMetadata: authorityMetadata}, nil -} - -func (k Keeper) DenomsFromCreator(ctx context.Context, req *types.QueryDenomsFromCreatorRequest) (*types.QueryDenomsFromCreatorResponse, error) { - sdkCtx := sdk.UnwrapSDKContext(ctx) - denoms := k.GetDenomsFromCreator(sdkCtx, req.GetCreator()) - return &types.QueryDenomsFromCreatorResponse{Denoms: denoms}, nil -} diff --git a/x/tokenfactory/keeper/keeper.go b/x/tokenfactory/keeper/keeper.go deleted file mode 100644 index 2ce23cd..0000000 --- a/x/tokenfactory/keeper/keeper.go +++ /dev/null @@ -1,82 +0,0 @@ -package keeper - -import ( - "fmt" - - "github.com/cometbft/cometbft/libs/log" - - "github.com/cosmos/cosmos-sdk/store/prefix" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" - - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" -) - -type ( - Keeper struct { - storeKey storetypes.StoreKey - - paramSpace paramtypes.Subspace - - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper - communityPoolKeeper types.CommunityPoolKeeper - } -) - -// NewKeeper returns a new instance of the x/tokenfactory keeper -func NewKeeper( - storeKey storetypes.StoreKey, - paramSpace paramtypes.Subspace, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, - communityPoolKeeper types.CommunityPoolKeeper, -) Keeper { - if !paramSpace.HasKeyTable() { - paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) - } - - return Keeper{ - storeKey: storeKey, - paramSpace: paramSpace, - - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, - communityPoolKeeper: communityPoolKeeper, - } -} - -// Logger returns a logger for the x/tokenfactory module -func (k Keeper) Logger(ctx sdk.Context) log.Logger { - return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) -} - -// GetDenomPrefixStore returns the substore for a specific denom -func (k Keeper) GetDenomPrefixStore(ctx sdk.Context, denom string) sdk.KVStore { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.GetDenomPrefixStore(denom)) -} - -// GetCreatorPrefixStore returns the substore for a specific creator address -func (k Keeper) GetCreatorPrefixStore(ctx sdk.Context, creator string) sdk.KVStore { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.GetCreatorPrefix(creator)) -} - -// GetCreatorsPrefixStore returns the substore that contains a list of creators -func (k Keeper) GetCreatorsPrefixStore(ctx sdk.Context) sdk.KVStore { - store := ctx.KVStore(k.storeKey) - return prefix.NewStore(store, types.GetCreatorsPrefix()) -} - -// CreateModuleAccount creates a module account with minting and burning capabilities -// This account isn't intended to store any coins, -// it purely mints and burns them on behalf of the admin of respective denoms, -// and sends to the relevant address. -func (k Keeper) CreateModuleAccount(ctx sdk.Context) { - moduleAcc := authtypes.NewEmptyModuleAccount(types.ModuleName, authtypes.Minter, authtypes.Burner) - k.accountKeeper.SetModuleAccount(ctx, moduleAcc) -} diff --git a/x/tokenfactory/keeper/keeper_test.go b/x/tokenfactory/keeper/keeper_test.go deleted file mode 100644 index 75581e3..0000000 --- a/x/tokenfactory/keeper/keeper_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package keeper_test - -import ( - "testing" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/suite" - - "github.com/terpnetwork/terp-core/app/apptesting" - "github.com/terpnetwork/terp-core/x/tokenfactory/keeper" - "github.com/terpnetwork/terp-core/x/tokenfactory/testhelpers" - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -type KeeperTestSuite struct { - apptesting.KeeperTestHelper - - queryClient types.QueryClient - msgServer types.MsgServer - // defaultDenom is on the suite, as it depends on the creator test address. - defaultDenom string -} - -func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(KeeperTestSuite)) -} - -func (suite *KeeperTestSuite) SetupTest() { - suite.Setup() - // Fund every TestAcc with two denoms, one of which is the denom creation fee - fundAccsAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100)), sdk.NewCoin(testhelpers.SecondaryDenom, testhelpers.SecondaryAmount)) - for _, acc := range suite.TestAccs { - suite.FundAcc(acc, fundAccsAmount) - } - - suite.queryClient = types.NewQueryClient(suite.QueryHelper) - suite.msgServer = keeper.NewMsgServerImpl(suite.App.TokenFactoryKeeper) -} - -func (suite *KeeperTestSuite) CreateDefaultDenom() { - res, _ := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) - suite.defaultDenom = res.GetNewTokenDenom() -} - -func (suite *KeeperTestSuite) TestCreateModuleAccount() { - app := suite.App - - // remove module account - tokenfactoryModuleAccount := app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) - app.AccountKeeper.RemoveAccount(suite.Ctx, tokenfactoryModuleAccount) - - // ensure module account was removed - suite.Ctx = app.BaseApp.NewContext(false, tmproto.Header{}) - tokenfactoryModuleAccount = app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) - suite.Require().Nil(tokenfactoryModuleAccount) - - // create module account - app.TokenFactoryKeeper.CreateModuleAccount(suite.Ctx) - - // check that the module account is now initialized - tokenfactoryModuleAccount = app.AccountKeeper.GetAccount(suite.Ctx, app.AccountKeeper.GetModuleAddress(types.ModuleName)) - suite.Require().NotNil(tokenfactoryModuleAccount) -} diff --git a/x/tokenfactory/keeper/msg_server.go b/x/tokenfactory/keeper/msg_server.go deleted file mode 100644 index 9bd98ed..0000000 --- a/x/tokenfactory/keeper/msg_server.go +++ /dev/null @@ -1,191 +0,0 @@ -package keeper - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -type msgServer struct { - Keeper -} - -// NewMsgServerImpl returns an implementation of the MsgServer interface -// for the provided Keeper. -func NewMsgServerImpl(keeper Keeper) types.MsgServer { - return &msgServer{Keeper: keeper} -} - -var _ types.MsgServer = msgServer{} - -func (server msgServer) CreateDenom(goCtx context.Context, msg *types.MsgCreateDenom) (*types.MsgCreateDenomResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - denom, err := server.Keeper.CreateDenom(ctx, msg.Sender, msg.Subdenom) - if err != nil { - return nil, err - } - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.TypeMsgCreateDenom, - sdk.NewAttribute(types.AttributeCreator, msg.Sender), - sdk.NewAttribute(types.AttributeNewTokenDenom, denom), - ), - }) - - return &types.MsgCreateDenomResponse{ - NewTokenDenom: denom, - }, nil -} - -func (server msgServer) Mint(goCtx context.Context, msg *types.MsgMint) (*types.MsgMintResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - // pay some extra gas cost to give a better error here. - _, denomExists := server.bankKeeper.GetDenomMetaData(ctx, msg.Amount.Denom) - if !denomExists { - return nil, types.ErrDenomDoesNotExist.Wrapf("denom: %s", msg.Amount.Denom) - } - - authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) - if err != nil { - return nil, err - } - - if msg.Sender != authorityMetadata.GetAdmin() { - return nil, types.ErrUnauthorized - } - - err = server.Keeper.mintTo(ctx, msg.Amount, msg.Sender) - if err != nil { - return nil, err - } - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.TypeMsgMint, - sdk.NewAttribute(types.AttributeMintToAddress, msg.Sender), - sdk.NewAttribute(types.AttributeAmount, msg.Amount.String()), - ), - }) - - return &types.MsgMintResponse{}, nil -} - -func (server msgServer) Burn(goCtx context.Context, msg *types.MsgBurn) (*types.MsgBurnResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) - if err != nil { - return nil, err - } - - if msg.Sender != authorityMetadata.GetAdmin() { - return nil, types.ErrUnauthorized - } - - err = server.Keeper.burnFrom(ctx, msg.Amount, msg.Sender) - if err != nil { - return nil, err - } - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.TypeMsgBurn, - sdk.NewAttribute(types.AttributeBurnFromAddress, msg.Sender), - sdk.NewAttribute(types.AttributeAmount, msg.Amount.String()), - ), - }) - - return &types.MsgBurnResponse{}, nil -} - -// func (server msgServer) ForceTransfer(goCtx context.Context, msg *types.MsgForceTransfer) (*types.MsgForceTransferResponse, error) { -// ctx := sdk.UnwrapSDKContext(goCtx) - -// authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) -// if err != nil { -// return nil, err -// } - -// if msg.Sender != authorityMetadata.GetAdmin() { -// return nil, types.ErrUnauthorized -// } - -// err = server.Keeper.forceTransfer(ctx, msg.Amount, msg.TransferFromAddress, msg.TransferToAddress) -// if err != nil { -// return nil, err -// } - -// ctx.EventManager().EmitEvents(sdk.Events{ -// sdk.NewEvent( -// types.TypeMsgForceTransfer, -// sdk.NewAttribute(types.AttributeTransferFromAddress, msg.TransferFromAddress), -// sdk.NewAttribute(types.AttributeTransferToAddress, msg.TransferToAddress), -// sdk.NewAttribute(types.AttributeAmount, msg.Amount.String()), -// ), -// }) - -// return &types.MsgForceTransferResponse{}, nil -// } - -func (server msgServer) ChangeAdmin(goCtx context.Context, msg *types.MsgChangeAdmin) (*types.MsgChangeAdminResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Denom) - if err != nil { - return nil, err - } - - if msg.Sender != authorityMetadata.GetAdmin() { - return nil, types.ErrUnauthorized - } - - err = server.Keeper.setAdmin(ctx, msg.Denom, msg.NewAdmin) - if err != nil { - return nil, err - } - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.TypeMsgChangeAdmin, - sdk.NewAttribute(types.AttributeDenom, msg.GetDenom()), - sdk.NewAttribute(types.AttributeNewAdmin, msg.NewAdmin), - ), - }) - - return &types.MsgChangeAdminResponse{}, nil -} - -func (server msgServer) SetDenomMetadata(goCtx context.Context, msg *types.MsgSetDenomMetadata) (*types.MsgSetDenomMetadataResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - - // Defense in depth validation of metadata - err := msg.Metadata.Validate() - if err != nil { - return nil, err - } - - authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Metadata.Base) - if err != nil { - return nil, err - } - - if msg.Sender != authorityMetadata.GetAdmin() { - return nil, types.ErrUnauthorized - } - - server.Keeper.bankKeeper.SetDenomMetaData(ctx, msg.Metadata) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.TypeMsgSetDenomMetadata, - sdk.NewAttribute(types.AttributeDenom, msg.Metadata.Base), - sdk.NewAttribute(types.AttributeDenomMetadata, msg.Metadata.String()), - ), - }) - - return &types.MsgSetDenomMetadataResponse{}, nil -} diff --git a/x/tokenfactory/keeper/msg_server_test.go b/x/tokenfactory/keeper/msg_server_test.go deleted file mode 100644 index 83f99a5..0000000 --- a/x/tokenfactory/keeper/msg_server_test.go +++ /dev/null @@ -1,247 +0,0 @@ -package keeper_test - -import ( - "fmt" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -// TestMintDenomMsg tests TypeMsgMint message is emitted on a successful mint -func (suite *KeeperTestSuite) TestMintDenomMsg() { - // Create a denom - suite.CreateDefaultDenom() - - for _, tc := range []struct { - desc string - amount int64 - mintDenom string - admin string - valid bool - expectedMessageEvents int - }{ - { - desc: "denom does not exist", - amount: 10, - mintDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", - admin: suite.TestAccs[0].String(), - valid: false, - }, - { - desc: "success case", - amount: 10, - mintDenom: suite.defaultDenom, - admin: suite.TestAccs[0].String(), - valid: true, - expectedMessageEvents: 1, - }, - } { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) - suite.Require().Equal(0, len(ctx.EventManager().Events())) - // Test mint message - suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) //nolint:errcheck - // Ensure current number and type of event is emitted - suite.AssertEventEmitted(ctx, types.TypeMsgMint, tc.expectedMessageEvents) - }) - } -} - -// TestBurnDenomMsg tests TypeMsgBurn message is emitted on a successful burn -func (suite *KeeperTestSuite) TestBurnDenomMsg() { - // Create a denom. - suite.CreateDefaultDenom() - // mint 10 default token for testAcc[0] - suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) //nolint:errcheck - - for _, tc := range []struct { - desc string - amount int64 - burnDenom string - admin string - valid bool - expectedMessageEvents int - }{ - { - desc: "denom does not exist", - burnDenom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/evmos", - admin: suite.TestAccs[0].String(), - valid: false, - }, - { - desc: "success case", - burnDenom: suite.defaultDenom, - admin: suite.TestAccs[0].String(), - valid: true, - expectedMessageEvents: 1, - }, - } { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) - suite.Require().Equal(0, len(ctx.EventManager().Events())) - // Test burn message - suite.msgServer.Burn(sdk.WrapSDKContext(ctx), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, 10))) //nolint:errcheck - // Ensure current number and type of event is emitted - suite.AssertEventEmitted(ctx, types.TypeMsgBurn, tc.expectedMessageEvents) - }) - } -} - -// TestCreateDenomMsg tests TypeMsgCreateDenom message is emitted on a successful denom creation -func (suite *KeeperTestSuite) TestCreateDenomMsg() { - defaultDenomCreationFee := types.Params{DenomCreationFee: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(50000000)))} - for _, tc := range []struct { - desc string - denomCreationFee types.Params - subdenom string - valid bool - expectedMessageEvents int - }{ - { - desc: "subdenom too long", - denomCreationFee: defaultDenomCreationFee, - subdenom: "assadsadsadasdasdsadsadsadsadsadsadsklkadaskkkdasdasedskhanhassyeunganassfnlksdflksafjlkasd", - valid: false, - }, - { - desc: "success case: defaultDenomCreationFee", - denomCreationFee: defaultDenomCreationFee, - subdenom: "evmos", - valid: true, - expectedMessageEvents: 1, - }, - } { - suite.SetupTest() - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - tokenFactoryKeeper := suite.App.TokenFactoryKeeper - ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) - suite.Require().Equal(0, len(ctx.EventManager().Events())) - // Set denom creation fee in params - tokenFactoryKeeper.SetParams(suite.Ctx, tc.denomCreationFee) - // Test create denom message - suite.msgServer.CreateDenom(sdk.WrapSDKContext(ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), tc.subdenom)) //nolint:errcheck - // Ensure current number and type of event is emitted - suite.AssertEventEmitted(ctx, types.TypeMsgCreateDenom, tc.expectedMessageEvents) - }) - } -} - -// TestChangeAdminDenomMsg tests TypeMsgChangeAdmin message is emitted on a successful admin change -func (suite *KeeperTestSuite) TestChangeAdminDenomMsg() { - for _, tc := range []struct { - desc string - msgChangeAdmin func(denom string) *types.MsgChangeAdmin - expectedChangeAdminPass bool - expectedAdminIndex int - msgMint func(denom string) *types.MsgMint - expectedMintPass bool - expectedMessageEvents int - }{ - { - desc: "non-admins can't change the existing admin", - msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { - return types.NewMsgChangeAdmin(suite.TestAccs[1].String(), denom, suite.TestAccs[2].String()) - }, - expectedChangeAdminPass: false, - expectedAdminIndex: 0, - }, - { - desc: "success change admin", - msgChangeAdmin: func(denom string) *types.MsgChangeAdmin { - return types.NewMsgChangeAdmin(suite.TestAccs[0].String(), denom, suite.TestAccs[1].String()) - }, - expectedAdminIndex: 1, - expectedChangeAdminPass: true, - expectedMessageEvents: 1, - msgMint: func(denom string) *types.MsgMint { - return types.NewMsgMint(suite.TestAccs[1].String(), sdk.NewInt64Coin(denom, 5)) - }, - expectedMintPass: true, - }, - } { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - // setup test - suite.SetupTest() - ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) - suite.Require().Equal(0, len(ctx.EventManager().Events())) - // Create a denom and mint - res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) - suite.Require().NoError(err) - testDenom := res.GetNewTokenDenom() - suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(testDenom, 10))) //nolint:errcheck - // Test change admin message - suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(ctx), tc.msgChangeAdmin(testDenom)) //nolint:errcheck - // Ensure current number and type of event is emitted - suite.AssertEventEmitted(ctx, types.TypeMsgChangeAdmin, tc.expectedMessageEvents) - }) - } -} - -// TestSetDenomMetaDataMsg tests TypeMsgSetDenomMetadata message is emitted on a successful denom metadata change -func (suite *KeeperTestSuite) TestSetDenomMetaDataMsg() { - // setup test - suite.SetupTest() - suite.CreateDefaultDenom() - - for _, tc := range []struct { - desc string - msgSetDenomMetadata types.MsgSetDenomMetadata - expectedPass bool - expectedMessageEvents int - }{ - { - desc: "successful set denom metadata", - msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ - Description: "yeehaw", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: suite.defaultDenom, - Exponent: 0, - }, - { - Denom: "uosmo", - Exponent: 6, - }, - }, - Base: suite.defaultDenom, - Display: "uosmo", - Name: "OSMO", - Symbol: "OSMO", - }), - expectedPass: true, - expectedMessageEvents: 1, - }, - { - desc: "non existent factory denom name", - msgSetDenomMetadata: *types.NewMsgSetDenomMetadata(suite.TestAccs[0].String(), banktypes.Metadata{ - Description: "yeehaw", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: fmt.Sprintf("factory/%s/litecoin", suite.TestAccs[0].String()), - Exponent: 0, - }, - { - Denom: "uosmo", - Exponent: 6, - }, - }, - Base: fmt.Sprintf("factory/%s/litecoin", suite.TestAccs[0].String()), - Display: "uosmo", - Name: "OSMO", - Symbol: "OSMO", - }), - expectedPass: false, - }, - } { - suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { - ctx := suite.Ctx.WithEventManager(sdk.NewEventManager()) - suite.Require().Equal(0, len(ctx.EventManager().Events())) - // Test set denom metadata message - suite.msgServer.SetDenomMetadata(sdk.WrapSDKContext(ctx), &tc.msgSetDenomMetadata) //nolint:errcheck - // Ensure current number and type of event is emitted - suite.AssertEventEmitted(ctx, types.TypeMsgSetDenomMetadata, tc.expectedMessageEvents) - }) - } -} diff --git a/x/tokenfactory/keeper/params.go b/x/tokenfactory/keeper/params.go deleted file mode 100644 index 9c9caf4..0000000 --- a/x/tokenfactory/keeper/params.go +++ /dev/null @@ -1,18 +0,0 @@ -package keeper - -import ( - "github.com/terpnetwork/terp-core/x/tokenfactory/types" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// GetParams returns the total set params. -func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramSpace.GetParamSet(ctx, ¶ms) - return params -} - -// SetParams sets the total set of params. -func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.paramSpace.SetParamSet(ctx, ¶ms) -} diff --git a/x/tokenfactory/module.go b/x/tokenfactory/module.go deleted file mode 100644 index fdac9f1..0000000 --- a/x/tokenfactory/module.go +++ /dev/null @@ -1,220 +0,0 @@ -/* -The tokenfactory module allows any account to create a new token with -the name `factory/{creator address}/{subdenom}`. - -- Mint and burn user denom to and form any account -- Create a transfer of their denom between any two accounts -- Change the admin. In the future, more admin capabilities may be added. -*/ -package tokenfactory - -import ( - "context" - "encoding/json" - "fmt" - "math/rand" - - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/gorilla/mux" - "github.com/grpc-ecosystem/grpc-gateway/runtime" - "github.com/spf13/cobra" - - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - - simulation "github.com/terpnetwork/terp-core/x/tokenfactory/simulation" - - "github.com/terpnetwork/terp-core/x/tokenfactory/client/cli" - "github.com/terpnetwork/terp-core/x/tokenfactory/keeper" - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} -) - -// ---------------------------------------------------------------------------- -// AppModuleBasic -// ---------------------------------------------------------------------------- - -// AppModuleBasic implements the AppModuleBasic interface for the capability module. -type AppModuleBasic struct{} - -func NewAppModuleBasic() AppModuleBasic { - return AppModuleBasic{} -} - -// Name returns the x/tokenfactory module's name. -func (AppModuleBasic) Name() string { - return types.ModuleName -} - -func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - types.RegisterCodec(cdc) -} - -// RegisterInterfaces registers the module's interface types -func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { - types.RegisterInterfaces(reg) -} - -// DefaultGenesis returns the x/tokenfactory module's default genesis state. -func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { - return cdc.MustMarshalJSON(types.DefaultGenesis()) -} - -// ValidateGenesis performs genesis state validation for the x/tokenfactory module. -func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { - var genState types.GenesisState - if err := cdc.UnmarshalJSON(bz, &genState); err != nil { - return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) - } - - return genState.Validate() -} - -// RegisterRESTRoutes registers the capability module's REST service handlers. -func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) { -} - -// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. -func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { - types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) //nolint:errcheck -} - -// GetTxCmd returns the x/tokenfactory module's root tx command. -func (a AppModuleBasic) GetTxCmd() *cobra.Command { - return cli.GetTxCmd() -} - -// GetQueryCmd returns the x/tokenfactory module's root query command. -func (AppModuleBasic) GetQueryCmd() *cobra.Command { - return cli.GetQueryCmd() -} - -// ---------------------------------------------------------------------------- -// AppModule -// ---------------------------------------------------------------------------- - -// AppModule implements the AppModule interface for the capability module. -type AppModule struct { - AppModuleBasic - - keeper keeper.Keeper - accountKeeper types.AccountKeeper - bankKeeper types.BankKeeper -} - -func NewAppModule( - keeper keeper.Keeper, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, -) AppModule { - return AppModule{ - AppModuleBasic: NewAppModuleBasic(), - keeper: keeper, - accountKeeper: accountKeeper, - bankKeeper: bankKeeper, - } -} - -// Name returns the x/tokenfactory module's name. -func (am AppModule) Name() string { - return am.AppModuleBasic.Name() -} - -// QuerierRoute returns the x/tokenfactory module's query routing key. -func (AppModule) QuerierRoute() string { return types.QuerierRoute } - -// RegisterServices registers a GRPC query service to respond to the -// module-specific GRPC queries. -func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) - types.RegisterQueryServer(cfg.QueryServer(), am.keeper) -} - -// RegisterInvariants registers the x/tokenfactory module's invariants. -func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} - -// InitGenesis performs the x/tokenfactory module's genesis initialization. It -// returns no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { - var genState types.GenesisState - cdc.MustUnmarshalJSON(gs, &genState) - - am.keeper.InitGenesis(ctx, genState) - - return []abci.ValidatorUpdate{} -} - -// ExportGenesis returns the x/tokenfactory module's exported genesis state as raw -// JSON bytes. -func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { - genState := am.keeper.ExportGenesis(ctx) - return cdc.MustMarshalJSON(genState) -} - -// ConsensusVersion implements ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 1 } - -// BeginBlock executes all ABCI BeginBlock logic respective to the tokenfactory module. -func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} - -// EndBlock executes all ABCI EndBlock logic respective to the tokenfactory module. It -// returns no validator updates. -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} -} - -// ___________________________________________________________________________ - -// AppModuleSimulationV2 functions - -// // GenerateGenesisState creates a randomized GenState of the tokenfactory module. -// func (am AppModule) SimulatorGenesisState(simState *module.SimulationState, s *simtypes.SimCtx) { -// tfDefaultGen := types.DefaultGenesis() -// tfDefaultGen.Params.DenomCreationFee = sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(10000000))) -// tfDefaultGenJson := simState.Cdc.MustMarshalJSON(tfDefaultGen) -// simState.GenState[types.ModuleName] = tfDefaultGenJson -// } - -// // WeightedOperations returns the all the lockup module operations with their respective weights. -// func (am AppModule) Actions() []simtypes.Action { -// return []simtypes.Action{ -// simtypes.NewMsgBasedAction("create token factory token", am.keeper, simulation.RandomMsgCreateDenom), -// simtypes.NewMsgBasedAction("mint token factory token", am.keeper, simulation.RandomMsgMintDenom), -// simtypes.NewMsgBasedAction("burn token factory token", am.keeper, simulation.RandomMsgBurnDenom), -// simtypes.NewMsgBasedAction("change admin token factory token", am.keeper, simulation.RandomMsgChangeAdmin), -// } -// } - -// ____________________________________________________________________________ - -// AppModuleSimulation functions -func (AppModule) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// GenerateGenesisState creates a randomized GenState of the bank module. -func (am AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { - return nil -} - -// RandomizedParams creates randomized bank param changes for the simulator. -func (am AppModule) RandomizedParams(r *rand.Rand) []simtypes.LegacyParamChange { - return simulation.ParamChanges(r) -} - -// RegisterStoreDecoder registers a decoder for supply module's types -func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { -} - -// WeightedOperations returns the all the gov module operations with their respective weights. -func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { - return simulation.WeightedOperations(&simState, am.keeper, am.accountKeeper, am.bankKeeper) -} diff --git a/x/tokenfactory/simulation/genesis.go b/x/tokenfactory/simulation/genesis.go deleted file mode 100644 index f3e091b..0000000 --- a/x/tokenfactory/simulation/genesis.go +++ /dev/null @@ -1,26 +0,0 @@ -package simulation - -import ( - "math/rand" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func RandDenomCreationFeeParam(r *rand.Rand) sdk.Coins { - amount := r.Int63n(10_000_000) - return sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(amount))) -} - -func RandomizedGenState(simstate *module.SimulationState) { - tfGenesis := types.DefaultGenesis() - - _, err := simstate.Cdc.MarshalJSON(tfGenesis) - if err != nil { - panic(err) - } - - simstate.GenState[types.ModuleName] = simstate.Cdc.MustMarshalJSON(tfGenesis) -} diff --git a/x/tokenfactory/simulation/operations.go b/x/tokenfactory/simulation/operations.go deleted file mode 100644 index 46f9a16..0000000 --- a/x/tokenfactory/simulation/operations.go +++ /dev/null @@ -1,404 +0,0 @@ -package simulation - -import ( - "math/rand" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/cosmos/cosmos-sdk/x/simulation" - - appparams "github.com/terpnetwork/terp-core/app/params" - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -// Simulation operation weights constants -// -//nolint:gosec -const ( - OpWeightMsgCreateDenom = "op_weight_msg_create_denom" - OpWeightMsgMint = "op_weight_msg_mint" - OpWeightMsgBurn = "op_weight_msg_burn" - OpWeightMsgChangeAdmin = "op_weight_msg_change_admin" - OpWeightMsgSetDenomMetadata = "op_weight_msg_set_denom_metadata" -) - -type TokenfactoryKeeper interface { - GetParams(ctx sdk.Context) (params types.Params) - GetAuthorityMetadata(ctx sdk.Context, denom string) (types.DenomAuthorityMetadata, error) - GetAllDenomsIterator(ctx sdk.Context) sdk.Iterator - GetDenomsFromCreator(ctx sdk.Context, creator string) []string -} - -type BankKeeper interface { - simulation.BankKeeper - GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin -} - -func WeightedOperations( - simstate *module.SimulationState, - tfKeeper TokenfactoryKeeper, - ak types.AccountKeeper, - bk BankKeeper, -) simulation.WeightedOperations { - var ( - weightMsgCreateDenom int - weightMsgMint int - weightMsgBurn int - weightMsgChangeAdmin int - weightMsgSetDenomMetadata int - ) - - simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgCreateDenom, &weightMsgCreateDenom, nil, - func(_ *rand.Rand) { - weightMsgCreateDenom = appparams.DefaultWeightMsgCreateDenom - }, - ) - simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgMint, &weightMsgMint, nil, - func(_ *rand.Rand) { - weightMsgMint = appparams.DefaultWeightMsgMint - }, - ) - simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgBurn, &weightMsgBurn, nil, - func(_ *rand.Rand) { - weightMsgBurn = appparams.DefaultWeightMsgBurn - }, - ) - simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgChangeAdmin, &weightMsgChangeAdmin, nil, - func(_ *rand.Rand) { - weightMsgChangeAdmin = appparams.DefaultWeightMsgChangeAdmin - }, - ) - simstate.AppParams.GetOrGenerate(simstate.Cdc, OpWeightMsgSetDenomMetadata, &weightMsgSetDenomMetadata, nil, - func(_ *rand.Rand) { - weightMsgSetDenomMetadata = appparams.DefaultWeightMsgSetDenomMetadata - }, - ) - - return simulation.WeightedOperations{ - simulation.NewWeightedOperation( - weightMsgCreateDenom, - SimulateMsgCreateDenom( - tfKeeper, - ak, - bk, - ), - ), - simulation.NewWeightedOperation( - weightMsgMint, - SimulateMsgMint( - tfKeeper, - ak, - bk, - DefaultSimulationDenomSelector, - ), - ), - simulation.NewWeightedOperation( - weightMsgBurn, - SimulateMsgBurn( - tfKeeper, - ak, - bk, - DefaultSimulationDenomSelector, - ), - ), - simulation.NewWeightedOperation( - weightMsgChangeAdmin, - SimulateMsgChangeAdmin( - tfKeeper, - ak, - bk, - DefaultSimulationDenomSelector, - ), - ), - simulation.NewWeightedOperation( - weightMsgSetDenomMetadata, - SimulateMsgSetDenomMetadata( - tfKeeper, - ak, - bk, - DefaultSimulationDenomSelector, - ), - ), - } -} - -type DenomSelector = func(*rand.Rand, sdk.Context, TokenfactoryKeeper, string) (string, bool) - -func DefaultSimulationDenomSelector(r *rand.Rand, ctx sdk.Context, tfKeeper TokenfactoryKeeper, creator string) (string, bool) { - denoms := tfKeeper.GetDenomsFromCreator(ctx, creator) - if len(denoms) == 0 { - return "", false - } - randPos := r.Intn(len(denoms)) - - return denoms[randPos], true -} - -func SimulateMsgSetDenomMetadata( - tfKeeper TokenfactoryKeeper, - ak types.AccountKeeper, - bk BankKeeper, - denomSelector DenomSelector, -) simtypes.Operation { - return func( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - accs []simtypes.Account, - chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // Get create denom account - createdDenomAccount, _ := simtypes.RandomAcc(r, accs) - - // Get demon - denom, hasDenom := denomSelector(r, ctx, tfKeeper, createdDenomAccount.Address.String()) - if !hasDenom { - return simtypes.NoOpMsg(types.ModuleName, types.MsgSetDenomMetadata{}.Type(), "sim account have no denom created"), nil, nil - } - - // Get admin of the denom - authData, err := tfKeeper.GetAuthorityMetadata(ctx, denom) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.MsgSetDenomMetadata{}.Type(), "err authority metadata"), nil, err - } - adminAccount, found := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(authData.Admin)) - if !found { - return simtypes.NoOpMsg(types.ModuleName, types.MsgSetDenomMetadata{}.Type(), "admin account not found"), nil, nil - } - - metadata := banktypes.Metadata{ - Description: simtypes.RandStringOfLength(r, 10), - DenomUnits: []*banktypes.DenomUnit{{ - Denom: denom, - Exponent: 0, - }}, - Base: denom, - Display: denom, - Name: simtypes.RandStringOfLength(r, 10), - Symbol: simtypes.RandStringOfLength(r, 10), - } - - msg := types.MsgSetDenomMetadata{ - Sender: adminAccount.Address.String(), - Metadata: metadata, - } - - txCtx := BuildOperationInput(r, app, ctx, &msg, adminAccount, ak, bk, nil) - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -func SimulateMsgChangeAdmin( - tfKeeper TokenfactoryKeeper, - ak types.AccountKeeper, - bk BankKeeper, - denomSelector DenomSelector, -) simtypes.Operation { - return func( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - accs []simtypes.Account, - chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // Get create denom account - createdDenomAccount, _ := simtypes.RandomAcc(r, accs) - - // Get demon - denom, hasDenom := denomSelector(r, ctx, tfKeeper, createdDenomAccount.Address.String()) - if !hasDenom { - return simtypes.NoOpMsg(types.ModuleName, types.MsgChangeAdmin{}.Type(), "sim account have no denom created"), nil, nil - } - - // Get admin of the denom - authData, err := tfKeeper.GetAuthorityMetadata(ctx, denom) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.MsgChangeAdmin{}.Type(), "err authority metadata"), nil, err - } - curAdminAccount, found := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(authData.Admin)) - if !found { - return simtypes.NoOpMsg(types.ModuleName, types.MsgChangeAdmin{}.Type(), "admin account not found"), nil, nil - } - - // Rand new admin account - newAdmin, _ := simtypes.RandomAcc(r, accs) - if newAdmin.Address.String() == curAdminAccount.Address.String() { - return simtypes.NoOpMsg(types.ModuleName, types.MsgChangeAdmin{}.Type(), "new admin cannot be the same as current admin"), nil, nil - } - - // Create msg - msg := types.MsgChangeAdmin{ - Sender: curAdminAccount.Address.String(), - Denom: denom, - NewAdmin: newAdmin.Address.String(), - } - - txCtx := BuildOperationInput(r, app, ctx, &msg, curAdminAccount, ak, bk, nil) - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -func SimulateMsgBurn( - tfKeeper TokenfactoryKeeper, - ak types.AccountKeeper, - bk BankKeeper, - denomSelector DenomSelector, -) simtypes.Operation { - return func( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - accs []simtypes.Account, - chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // Get create denom account - createdDenomAccount, _ := simtypes.RandomAcc(r, accs) - - // Get demon - denom, hasDenom := denomSelector(r, ctx, tfKeeper, createdDenomAccount.Address.String()) - if !hasDenom { - return simtypes.NoOpMsg(types.ModuleName, types.MsgBurn{}.Type(), "sim account have no denom created"), nil, nil - } - - // Get admin of the denom - authData, err := tfKeeper.GetAuthorityMetadata(ctx, denom) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.MsgBurn{}.Type(), "err authority metadata"), nil, err - } - adminAccount, found := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(authData.Admin)) - if !found { - return simtypes.NoOpMsg(types.ModuleName, types.MsgBurn{}.Type(), "admin account not found"), nil, nil - } - - // Check if admin account balance = 0 - accountBalance := bk.GetBalance(ctx, adminAccount.Address, denom) - if accountBalance.Amount.LTE(sdk.ZeroInt()) { - return simtypes.NoOpMsg(types.ModuleName, types.MsgBurn{}.Type(), "sim account have no balance"), nil, nil - } - - // Rand burn amount - amount, _ := simtypes.RandPositiveInt(r, accountBalance.Amount) - burnAmount := sdk.NewCoin(denom, amount) - - // Create msg - msg := types.MsgBurn{ - Sender: adminAccount.Address.String(), - Amount: burnAmount, - } - - txCtx := BuildOperationInput(r, app, ctx, &msg, adminAccount, ak, bk, sdk.NewCoins(burnAmount)) - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// Simulate msg mint denom -func SimulateMsgMint( - tfKeeper TokenfactoryKeeper, - ak types.AccountKeeper, - bk BankKeeper, - denomSelector DenomSelector, -) simtypes.Operation { - return func( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - accs []simtypes.Account, - chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // Get create denom account - createdDenomAccount, _ := simtypes.RandomAcc(r, accs) - - // Get demon - denom, hasDenom := denomSelector(r, ctx, tfKeeper, createdDenomAccount.Address.String()) - if !hasDenom { - return simtypes.NoOpMsg(types.ModuleName, types.MsgMint{}.Type(), "sim account have no denom created"), nil, nil - } - - // Get admin of the denom - authData, err := tfKeeper.GetAuthorityMetadata(ctx, denom) - if err != nil { - return simtypes.NoOpMsg(types.ModuleName, types.MsgMint{}.Type(), "err authority metadata"), nil, err - } - adminAccount, found := simtypes.FindAccount(accs, sdk.MustAccAddressFromBech32(authData.Admin)) - if !found { - return simtypes.NoOpMsg(types.ModuleName, types.MsgMint{}.Type(), "admin account not found"), nil, nil - } - - // Rand mint amount - mintAmount, _ := simtypes.RandPositiveInt(r, sdk.NewIntFromUint64(100_000_000)) - - // Create msg mint - msg := types.MsgMint{ - Sender: adminAccount.Address.String(), - Amount: sdk.NewCoin(denom, mintAmount), - } - - txCtx := BuildOperationInput(r, app, ctx, &msg, adminAccount, ak, bk, nil) - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// Simulate msg create denom -func SimulateMsgCreateDenom(tfKeeper TokenfactoryKeeper, ak types.AccountKeeper, bk BankKeeper) simtypes.Operation { - return func( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - accs []simtypes.Account, - chainID string, - ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { - // Get sims account - simAccount, _ := simtypes.RandomAcc(r, accs) - - // Check if sims account enough create fee - createFee := tfKeeper.GetParams(ctx).DenomCreationFee - balances := bk.GetAllBalances(ctx, simAccount.Address) - _, hasNeg := balances.SafeSub(createFee...) - if hasNeg { - return simtypes.NoOpMsg(types.ModuleName, types.MsgCreateDenom{}.Type(), "Creator not enough creation fee"), nil, nil - } - - // Create msg create denom - msg := types.MsgCreateDenom{ - Sender: simAccount.Address.String(), - Subdenom: simtypes.RandStringOfLength(r, 10), - } - - txCtx := BuildOperationInput(r, app, ctx, &msg, simAccount, ak, bk, createFee) - return simulation.GenAndDeliverTxWithRandFees(txCtx) - } -} - -// BuildOperationInput helper to build object -func BuildOperationInput( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - msg interface { - sdk.Msg - Type() string - }, - simAccount simtypes.Account, - ak types.AccountKeeper, - bk BankKeeper, - deposit sdk.Coins, -) simulation.OperationInput { - return simulation.OperationInput{ - R: r, - App: app, - TxGen: appparams.MakeEncodingConfig().TxConfig, - Cdc: nil, - Msg: msg, - MsgType: msg.Type(), - Context: ctx, - SimAccount: simAccount, - AccountKeeper: ak, - Bankkeeper: bk, - ModuleName: types.ModuleName, - CoinsSpentInMsg: deposit, - } -} diff --git a/x/tokenfactory/simulation/params.go b/x/tokenfactory/simulation/params.go deleted file mode 100644 index 886bf06..0000000 --- a/x/tokenfactory/simulation/params.go +++ /dev/null @@ -1,24 +0,0 @@ -package simulation - -import ( - "fmt" - "math/rand" - - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func ParamChanges(r *rand.Rand) []simtypes.LegacyParamChange { - return []simtypes.LegacyParamChange{ - simulation.NewSimLegacyParamChange( - types.ModuleName, - string(types.KeyDenomCreationFee), - func(r *rand.Rand) string { - amount := RandDenomCreationFeeParam(r) - return fmt.Sprintf("[{\"denom\":\"%v\",\"amount\":\"%v\"}]", amount[0].Denom, amount[0].Amount) - }, - ), - } -} diff --git a/x/tokenfactory/testhelpers/consts.go b/x/tokenfactory/testhelpers/consts.go deleted file mode 100644 index d7804a3..0000000 --- a/x/tokenfactory/testhelpers/consts.go +++ /dev/null @@ -1,8 +0,0 @@ -package testhelpers - -import sdk "github.com/cosmos/cosmos-sdk/types" - -var ( - SecondaryDenom = "uion" - SecondaryAmount = sdk.NewInt(100000000) -) diff --git a/x/tokenfactory/testhelpers/suite.go b/x/tokenfactory/testhelpers/suite.go deleted file mode 100644 index b509a52..0000000 --- a/x/tokenfactory/testhelpers/suite.go +++ /dev/null @@ -1,66 +0,0 @@ -package testhelpers - -import ( - "encoding/json" - "testing" - "time" - - "github.com/cosmos/cosmos-sdk/x/authz" - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -var ( - Amino = codec.NewLegacyAmino() - AuthzModuleCdc = codec.NewAminoCodec(Amino) -) - -func init() { - cryptocodec.RegisterCrypto(Amino) - codec.RegisterEvidences(Amino) - sdk.RegisterLegacyAminoCodec(Amino) -} - -func TestMessageAuthzSerialization(t *testing.T, msg sdk.Msg) { - someDate := time.Date(1, 1, 1, 1, 1, 1, 1, time.UTC) - const ( - mockGranter string = "cosmos1abc" - mockGrantee string = "cosmos1xyz" - ) - - var ( - mockMsgGrant authz.MsgGrant - mockMsgRevoke authz.MsgRevoke - mockMsgExec authz.MsgExec - ) - - // Authz: Grant Msg - typeURL := sdk.MsgTypeURL(msg) - later := someDate.Add(time.Hour) - grant, err := authz.NewGrant(someDate, authz.NewGenericAuthorization(typeURL), &later) - require.NoError(t, err) - - msgGrant := authz.MsgGrant{Granter: mockGranter, Grantee: mockGrantee, Grant: grant} - msgGrantBytes := json.RawMessage(sdk.MustSortJSON(AuthzModuleCdc.MustMarshalJSON(&msgGrant))) - err = AuthzModuleCdc.UnmarshalJSON(msgGrantBytes, &mockMsgGrant) - require.NoError(t, err) - - // Authz: Revoke Msg - msgRevoke := authz.MsgRevoke{Granter: mockGranter, Grantee: mockGrantee, MsgTypeUrl: typeURL} - msgRevokeByte := json.RawMessage(sdk.MustSortJSON(AuthzModuleCdc.MustMarshalJSON(&msgRevoke))) - err = AuthzModuleCdc.UnmarshalJSON(msgRevokeByte, &mockMsgRevoke) - require.NoError(t, err) - - // Authz: Exec Msg - msgAny, err := cdctypes.NewAnyWithValue(msg) - require.NoError(t, err) - msgExec := authz.MsgExec{Grantee: mockGrantee, Msgs: []*cdctypes.Any{msgAny}} - execMsgByte := json.RawMessage(sdk.MustSortJSON(AuthzModuleCdc.MustMarshalJSON(&msgExec))) - err = AuthzModuleCdc.UnmarshalJSON(execMsgByte, &mockMsgExec) - require.NoError(t, err) - require.Equal(t, msgExec.Msgs[0].Value, mockMsgExec.Msgs[0].Value) -} diff --git a/x/tokenfactory/types/authorityMetadata.go b/x/tokenfactory/types/authorityMetadata.go deleted file mode 100644 index b45bffc..0000000 --- a/x/tokenfactory/types/authorityMetadata.go +++ /dev/null @@ -1,15 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func (metadata DenomAuthorityMetadata) Validate() error { - if metadata.Admin != "" { - _, err := sdk.AccAddressFromBech32(metadata.Admin) - if err != nil { - return err - } - } - return nil -} diff --git a/x/tokenfactory/types/authority_metadata.pb.go b/x/tokenfactory/types/authority_metadata.pb.go deleted file mode 100644 index 7e07827..0000000 --- a/x/tokenfactory/types/authority_metadata.pb.go +++ /dev/null @@ -1,365 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: cosmwasm/tokenfactory/v1beta1/authority_metadata.proto - -package types - -import ( - fmt "fmt" - io "io" - math "math" - math_bits "math/bits" - - _ "github.com/cosmos/cosmos-sdk/types" - _ "github.com/cosmos/gogoproto/gogoproto" - proto "github.com/cosmos/gogoproto/proto" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = proto.Marshal - _ = fmt.Errorf - _ = math.Inf -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// DenomAuthorityMetadata specifies metadata for addresses that have specific -// capabilities over a token factory denom. Right now there is only one Admin -// permission, but is planned to be extended to the future. -type DenomAuthorityMetadata struct { - // Can be empty for no admin, or a valid terp address - Admin string `protobuf:"bytes,1,opt,name=admin,proto3" json:"admin,omitempty" yaml:"admin"` -} - -func (m *DenomAuthorityMetadata) Reset() { *m = DenomAuthorityMetadata{} } -func (m *DenomAuthorityMetadata) String() string { return proto.CompactTextString(m) } -func (*DenomAuthorityMetadata) ProtoMessage() {} -func (*DenomAuthorityMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_5e9c086b3b345014, []int{0} -} - -func (m *DenomAuthorityMetadata) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *DenomAuthorityMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_DenomAuthorityMetadata.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *DenomAuthorityMetadata) XXX_Merge(src proto.Message) { - xxx_messageInfo_DenomAuthorityMetadata.Merge(m, src) -} - -func (m *DenomAuthorityMetadata) XXX_Size() int { - return m.Size() -} - -func (m *DenomAuthorityMetadata) XXX_DiscardUnknown() { - xxx_messageInfo_DenomAuthorityMetadata.DiscardUnknown(m) -} - -var xxx_messageInfo_DenomAuthorityMetadata proto.InternalMessageInfo - -func (m *DenomAuthorityMetadata) GetAdmin() string { - if m != nil { - return m.Admin - } - return "" -} - -func init() { - proto.RegisterType((*DenomAuthorityMetadata)(nil), "osmosis.tokenfactory.v1beta1.DenomAuthorityMetadata") -} - -func init() { - proto.RegisterFile("cosmwasm/tokenfactory/v1beta1/authority_metadata.proto", fileDescriptor_5e9c086b3b345014) -} - -var fileDescriptor_5e9c086b3b345014 = []byte{ - // 253 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x4b, 0xce, 0x2f, 0xce, - 0x2d, 0x4f, 0x2c, 0xce, 0xd5, 0x2f, 0xc9, 0xcf, 0x4e, 0xcd, 0x4b, 0x4b, 0x4c, 0x2e, 0xc9, 0x2f, - 0xaa, 0xd4, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x4f, 0x2c, 0x2d, 0xc9, 0xc8, 0x2f, - 0xca, 0x2c, 0xa9, 0x8c, 0xcf, 0x4d, 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0xd4, 0x2b, 0x28, 0xca, - 0x2f, 0xc9, 0x17, 0x92, 0xc9, 0x2f, 0xce, 0xcd, 0x2f, 0xce, 0x2c, 0xd6, 0x43, 0xd6, 0xa6, 0x07, - 0xd5, 0x26, 0x25, 0x97, 0x0c, 0x96, 0xd6, 0x4f, 0x4a, 0x2c, 0x4e, 0x85, 0x9b, 0x95, 0x9c, 0x9f, - 0x99, 0x07, 0xd1, 0x2d, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x66, 0xea, 0x83, 0x58, 0x10, 0x51, - 0x25, 0x37, 0x2e, 0x31, 0x97, 0xd4, 0xbc, 0xfc, 0x5c, 0x47, 0x98, 0xa5, 0xbe, 0x50, 0x3b, 0x85, - 0xd4, 0xb8, 0x58, 0x13, 0x53, 0x72, 0x33, 0xf3, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x9d, 0x04, - 0x3e, 0xdd, 0x93, 0xe7, 0xa9, 0x4c, 0xcc, 0xcd, 0xb1, 0x52, 0x02, 0x0b, 0x2b, 0x05, 0x41, 0xa4, - 0xad, 0x58, 0x5e, 0x2c, 0x90, 0x67, 0x74, 0xf2, 0x3f, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, - 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, - 0x39, 0x86, 0x28, 0xd3, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd, 0x92, - 0xd4, 0xa2, 0x82, 0xbc, 0xd4, 0x92, 0xf2, 0xfc, 0xa2, 0x6c, 0x30, 0x5b, 0x37, 0x39, 0xbf, 0x28, - 0x55, 0xbf, 0x02, 0x35, 0x1c, 0x4a, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0xee, 0x33, 0x06, - 0x04, 0x00, 0x00, 0xff, 0xff, 0x3c, 0x03, 0xdd, 0x97, 0x2d, 0x01, 0x00, 0x00, -} - -func (this *DenomAuthorityMetadata) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*DenomAuthorityMetadata) - if !ok { - that2, ok := that.(DenomAuthorityMetadata) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.Admin != that1.Admin { - return false - } - return true -} - -func (m *DenomAuthorityMetadata) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *DenomAuthorityMetadata) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *DenomAuthorityMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Admin) > 0 { - i -= len(m.Admin) - copy(dAtA[i:], m.Admin) - i = encodeVarintAuthorityMetadata(dAtA, i, uint64(len(m.Admin))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func encodeVarintAuthorityMetadata(dAtA []byte, offset int, v uint64) int { - offset -= sovAuthorityMetadata(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} - -func (m *DenomAuthorityMetadata) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Admin) - if l > 0 { - n += 1 + l + sovAuthorityMetadata(uint64(l)) - } - return n -} - -func sovAuthorityMetadata(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} - -func sozAuthorityMetadata(x uint64) (n int) { - return sovAuthorityMetadata(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} - -func (m *DenomAuthorityMetadata) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthorityMetadata - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DenomAuthorityMetadata: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DenomAuthorityMetadata: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Admin", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthorityMetadata - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthAuthorityMetadata - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthAuthorityMetadata - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Admin = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipAuthorityMetadata(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthAuthorityMetadata - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func skipAuthorityMetadata(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowAuthorityMetadata - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowAuthorityMetadata - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowAuthorityMetadata - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthAuthorityMetadata - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupAuthorityMetadata - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthAuthorityMetadata - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthAuthorityMetadata = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowAuthorityMetadata = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupAuthorityMetadata = fmt.Errorf("proto: unexpected end of group") -) diff --git a/x/tokenfactory/types/authzcodec/codec.go b/x/tokenfactory/types/authzcodec/codec.go deleted file mode 100644 index 366e337..0000000 --- a/x/tokenfactory/types/authzcodec/codec.go +++ /dev/null @@ -1,24 +0,0 @@ -package authzcodec - -// Note: this file is a copy from authz/codec in 0.46 so we can be compatible with 0.45 - -import ( - "github.com/cosmos/cosmos-sdk/codec" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -var ( - Amino = codec.NewLegacyAmino() - ModuleCdc = codec.NewAminoCodec(Amino) -) - -func init() { - // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be - // used to properly serialize MsgGrant and MsgExec instances - sdk.RegisterLegacyAminoCodec(Amino) - cryptocodec.RegisterCrypto(Amino) - codec.RegisterEvidences(Amino) - - Amino.Seal() -} diff --git a/x/tokenfactory/types/codec.go b/x/tokenfactory/types/codec.go deleted file mode 100644 index b086a51..0000000 --- a/x/tokenfactory/types/codec.go +++ /dev/null @@ -1,46 +0,0 @@ -package types - -import ( - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" - - // this line is used by starport scaffolding # 1 - "github.com/cosmos/cosmos-sdk/types/msgservice" -) - -func RegisterCodec(cdc *codec.LegacyAmino) { - cdc.RegisterConcrete(&MsgCreateDenom{}, "osmosis/tokenfactory/create-denom", nil) - cdc.RegisterConcrete(&MsgMint{}, "osmosis/tokenfactory/mint", nil) - cdc.RegisterConcrete(&MsgBurn{}, "osmosis/tokenfactory/burn", nil) - // cdc.RegisterConcrete(&MsgForceTransfer{}, "osmosis/tokenfactory/force-transfer", nil) - cdc.RegisterConcrete(&MsgChangeAdmin{}, "osmosis/tokenfactory/change-admin", nil) -} - -func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { - registry.RegisterImplementations( - (*sdk.Msg)(nil), - &MsgCreateDenom{}, - &MsgMint{}, - &MsgBurn{}, - // &MsgForceTransfer{}, - &MsgChangeAdmin{}, - ) - msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) -} - -var ( - amino = codec.NewLegacyAmino() - ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) -) - -func init() { - RegisterCodec(amino) - // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be - // used to properly serialize MsgGrant and MsgExec instances - sdk.RegisterLegacyAminoCodec(amino) - RegisterCodec(authzcodec.Amino) - - amino.Seal() -} diff --git a/x/tokenfactory/types/denoms.go b/x/tokenfactory/types/denoms.go deleted file mode 100644 index 6583998..0000000 --- a/x/tokenfactory/types/denoms.go +++ /dev/null @@ -1,68 +0,0 @@ -package types - -import ( - "strings" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -const ( - ModuleDenomPrefix = "factory" - // See the TokenFactory readme for a derivation of these. - // TL;DR, MaxSubdenomLength + MaxHrpLength = 60 comes from SDK max denom length = 128 - // and the structure of tokenfactory denoms. - MaxSubdenomLength = 44 - MaxHrpLength = 16 - // MaxCreatorLength = 59 + MaxHrpLength - MaxCreatorLength = 59 + MaxHrpLength -) - -// GetTokenDenom constructs a denom string for tokens created by tokenfactory -// based on an input creator address and a subdenom -// The denom constructed is factory/{creator}/{subdenom} -func GetTokenDenom(creator, subdenom string) (string, error) { - if len(subdenom) > MaxSubdenomLength { - return "", ErrSubdenomTooLong - } - if len(creator) > MaxCreatorLength { - return "", ErrCreatorTooLong - } - if strings.Contains(creator, "/") { - return "", ErrInvalidCreator - } - denom := strings.Join([]string{ModuleDenomPrefix, creator, subdenom}, "/") - return denom, sdk.ValidateDenom(denom) -} - -// DeconstructDenom takes a token denom string and verifies that it is a valid -// denom of the tokenfactory module, and is of the form `factory/{creator}/{subdenom}` -// If valid, it returns the creator address and subdenom -func DeconstructDenom(denom string) (creator string, subdenom string, err error) { - err = sdk.ValidateDenom(denom) - if err != nil { - return "", "", err - } - - strParts := strings.Split(denom, "/") - if len(strParts) < 3 { - return "", "", errorsmod.Wrapf(ErrInvalidDenom, "not enough parts of denom %s", denom) - } - - if strParts[0] != ModuleDenomPrefix { - return "", "", errorsmod.Wrapf(ErrInvalidDenom, "denom prefix is incorrect. Is: %s. Should be: %s", strParts[0], ModuleDenomPrefix) - } - - creator = strParts[1] - creatorAddr, err := sdk.AccAddressFromBech32(creator) - if err != nil { - return "", "", errorsmod.Wrapf(ErrInvalidDenom, "Invalid creator address (%s)", err) - } - - // Handle the case where a denom has a slash in its subdenom. For example, - // when we did the split, we'd turn factory/accaddr/atomderivative/sikka into ["factory", "accaddr", "atomderivative", "sikka"] - // So we have to join [2:] with a "/" as the delimiter to get back the correct subdenom which should be "atomderivative/sikka" - subdenom = strings.Join(strParts[2:], "/") - - return creatorAddr.String(), subdenom, nil -} diff --git a/x/tokenfactory/types/denoms_test.go b/x/tokenfactory/types/denoms_test.go deleted file mode 100644 index 12f4637..0000000 --- a/x/tokenfactory/types/denoms_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package types_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func TestDeconstructDenom(t *testing.T) { - // Note: this seems to be used in terp to add some more checks (only 20 or 32 byte addresses), - // which is good, but not required for these tests as they make code less reuable - // appparams.SetAddressPrefixes() - - for _, tc := range []struct { - desc string - denom string - expectedSubdenom string - err error - }{ - { - desc: "empty is invalid", - denom: "", - err: types.ErrInvalidDenom, - }, - { - desc: "normal", - denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - expectedSubdenom: "bitcoin", - }, - { - desc: "multiple slashes in subdenom", - denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin/1", - expectedSubdenom: "bitcoin/1", - }, - { - desc: "no subdenom", - denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/", - expectedSubdenom: "", - }, - { - desc: "incorrect prefix", - denom: "ibc/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - err: types.ErrInvalidDenom, - }, - { - desc: "subdenom of only slashes", - denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/////", - expectedSubdenom: "////", - }, - { - desc: "too long name", - denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/adsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsf", - err: types.ErrInvalidDenom, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - expectedCreator := "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8" - creator, subdenom, err := types.DeconstructDenom(tc.denom) - if tc.err != nil { - require.ErrorContains(t, err, tc.err.Error()) - } else { - require.NoError(t, err) - require.Equal(t, expectedCreator, creator) - require.Equal(t, tc.expectedSubdenom, subdenom) - } - }) - } -} - -func TestGetTokenDenom(t *testing.T) { - // appparams.SetAddressPrefixes() - for _, tc := range []struct { - desc string - creator string - subdenom string - valid bool - }{ - { - desc: "normal", - creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - subdenom: "bitcoin", - valid: true, - }, - { - desc: "multiple slashes in subdenom", - creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - subdenom: "bitcoin/1", - valid: true, - }, - { - desc: "no subdenom", - creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - subdenom: "", - valid: true, - }, - { - desc: "subdenom of only slashes", - creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - subdenom: "/////", - valid: true, - }, - { - desc: "too long name", - creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - subdenom: "adsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsfadsf", - valid: false, - }, - { - desc: "subdenom is exactly max length", - creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - subdenom: "bitcoinfsadfsdfeadfsafwefsefsefsdfsdafasefsf", - valid: true, - }, - { - desc: "creator is exactly max length", - creator: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8jhgjhgkhjklhkjhkjhgjhgjgjghelu", - subdenom: "bitcoin", - valid: true, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - _, err := types.GetTokenDenom(tc.creator, tc.subdenom) - if tc.valid { - require.NoError(t, err) - } else { - require.Error(t, err) - } - }) - } -} diff --git a/x/tokenfactory/types/errors.go b/x/tokenfactory/types/errors.go deleted file mode 100644 index 7742014..0000000 --- a/x/tokenfactory/types/errors.go +++ /dev/null @@ -1,22 +0,0 @@ -package types - -// DONTCOVER - -import ( - fmt "fmt" - - errorsmod "cosmossdk.io/errors" -) - -// x/tokenfactory module sentinel errors -var ( - ErrDenomExists = errorsmod.Register(ModuleName, 2, "attempting to create a denom that already exists (has bank metadata)") - ErrUnauthorized = errorsmod.Register(ModuleName, 3, "unauthorized account") - ErrInvalidDenom = errorsmod.Register(ModuleName, 4, "invalid denom") - ErrInvalidCreator = errorsmod.Register(ModuleName, 5, "invalid creator") - ErrInvalidAuthorityMetadata = errorsmod.Register(ModuleName, 6, "invalid authority metadata") - ErrInvalidGenesis = errorsmod.Register(ModuleName, 7, "invalid genesis") - ErrSubdenomTooLong = errorsmod.Register(ModuleName, 8, fmt.Sprintf("subdenom too long, max length is %d bytes", MaxSubdenomLength)) - ErrCreatorTooLong = errorsmod.Register(ModuleName, 9, fmt.Sprintf("creator too long, max length is %d bytes", MaxCreatorLength)) - ErrDenomDoesNotExist = errorsmod.Register(ModuleName, 10, "denom does not exist") -) diff --git a/x/tokenfactory/types/events.go b/x/tokenfactory/types/events.go deleted file mode 100644 index 48038e0..0000000 --- a/x/tokenfactory/types/events.go +++ /dev/null @@ -1,18 +0,0 @@ -package types - -// event types -// -//nolint:gosec // these are not hard-coded credentials -const ( - AttributeAmount = "amount" - AttributeCreator = "creator" - AttributeSubdenom = "subdenom" - AttributeNewTokenDenom = "new_token_denom" - AttributeMintToAddress = "mint_to_address" - AttributeBurnFromAddress = "burn_from_address" - AttributeTransferFromAddress = "transfer_from_address" - AttributeTransferToAddress = "transfer_to_address" - AttributeDenom = "denom" - AttributeNewAdmin = "new_admin" - AttributeDenomMetadata = "denom_metadata" -) diff --git a/x/tokenfactory/types/expected_keepers.go b/x/tokenfactory/types/expected_keepers.go deleted file mode 100644 index 15f377e..0000000 --- a/x/tokenfactory/types/expected_keepers.go +++ /dev/null @@ -1,36 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -type BankKeeper interface { - // Methods imported from bank should be defined here - GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) - SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata) - - HasSupply(ctx sdk.Context, denom string) bool - - SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error - SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error - MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error - BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error - - SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error - HasBalance(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coin) bool - GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin -} - -type AccountKeeper interface { - SetModuleAccount(ctx sdk.Context, macc authtypes.ModuleAccountI) - GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI -} - -// CommunityPoolKeeper defines the contract needed to be fulfilled for community pool interactions. -type CommunityPoolKeeper interface { - FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error -} diff --git a/x/tokenfactory/types/genesis.go b/x/tokenfactory/types/genesis.go deleted file mode 100644 index aef9370..0000000 --- a/x/tokenfactory/types/genesis.go +++ /dev/null @@ -1,51 +0,0 @@ -package types - -import ( - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// this line is used by starport scaffolding # genesis/types/import - -// DefaultIndex is the default capability global index -const DefaultIndex uint64 = 1 - -// DefaultGenesis returns the default Capability genesis state -func DefaultGenesis() *GenesisState { - return &GenesisState{ - Params: DefaultParams(), - FactoryDenoms: []GenesisDenom{}, - } -} - -// Validate performs basic genesis state validation returning an error upon any -// failure. -func (gs GenesisState) Validate() error { - err := gs.Params.Validate() - if err != nil { - return err - } - - seenDenoms := map[string]bool{} - - for _, denom := range gs.GetFactoryDenoms() { - if seenDenoms[denom.GetDenom()] { - return errorsmod.Wrapf(ErrInvalidGenesis, "duplicate denom: %s", denom.GetDenom()) - } - seenDenoms[denom.GetDenom()] = true - - _, _, err := DeconstructDenom(denom.GetDenom()) - if err != nil { - return err - } - - if denom.AuthorityMetadata.Admin != "" { - _, err = sdk.AccAddressFromBech32(denom.AuthorityMetadata.Admin) - if err != nil { - return errorsmod.Wrapf(ErrInvalidAuthorityMetadata, "Invalid admin address (%s)", err) - } - } - } - - return nil -} diff --git a/x/tokenfactory/types/genesis.pb.go b/x/tokenfactory/types/genesis.pb.go deleted file mode 100644 index f2c5849..0000000 --- a/x/tokenfactory/types/genesis.pb.go +++ /dev/null @@ -1,669 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: cosmwasm/tokenfactory/v1beta1/genesis.proto - -package types - -import ( - fmt "fmt" - io "io" - math "math" - math_bits "math/bits" - - _ "github.com/cosmos/gogoproto/gogoproto" - proto "github.com/cosmos/gogoproto/proto" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = proto.Marshal - _ = fmt.Errorf - _ = math.Inf -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// GenesisState defines the tokenfactory module's genesis state. -type GenesisState struct { - // params defines the paramaters of the module. - Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` - FactoryDenoms []GenesisDenom `protobuf:"bytes,2,rep,name=factory_denoms,json=factoryDenoms,proto3" json:"factory_denoms" yaml:"factory_denoms"` -} - -func (m *GenesisState) Reset() { *m = GenesisState{} } -func (m *GenesisState) String() string { return proto.CompactTextString(m) } -func (*GenesisState) ProtoMessage() {} -func (*GenesisState) Descriptor() ([]byte, []int) { - return fileDescriptor_b333539769138b3e, []int{0} -} - -func (m *GenesisState) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *GenesisState) XXX_Merge(src proto.Message) { - xxx_messageInfo_GenesisState.Merge(m, src) -} - -func (m *GenesisState) XXX_Size() int { - return m.Size() -} - -func (m *GenesisState) XXX_DiscardUnknown() { - xxx_messageInfo_GenesisState.DiscardUnknown(m) -} - -var xxx_messageInfo_GenesisState proto.InternalMessageInfo - -func (m *GenesisState) GetParams() Params { - if m != nil { - return m.Params - } - return Params{} -} - -func (m *GenesisState) GetFactoryDenoms() []GenesisDenom { - if m != nil { - return m.FactoryDenoms - } - return nil -} - -// GenesisDenom defines a tokenfactory denom that is defined within genesis -// state. The structure contains DenomAuthorityMetadata which defines the -// denom's admin. -type GenesisDenom struct { - Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty" yaml:"denom"` - AuthorityMetadata DenomAuthorityMetadata `protobuf:"bytes,2,opt,name=authority_metadata,json=authorityMetadata,proto3" json:"authority_metadata" yaml:"authority_metadata"` -} - -func (m *GenesisDenom) Reset() { *m = GenesisDenom{} } -func (m *GenesisDenom) String() string { return proto.CompactTextString(m) } -func (*GenesisDenom) ProtoMessage() {} -func (*GenesisDenom) Descriptor() ([]byte, []int) { - return fileDescriptor_b333539769138b3e, []int{1} -} - -func (m *GenesisDenom) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *GenesisDenom) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_GenesisDenom.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *GenesisDenom) XXX_Merge(src proto.Message) { - xxx_messageInfo_GenesisDenom.Merge(m, src) -} - -func (m *GenesisDenom) XXX_Size() int { - return m.Size() -} - -func (m *GenesisDenom) XXX_DiscardUnknown() { - xxx_messageInfo_GenesisDenom.DiscardUnknown(m) -} - -var xxx_messageInfo_GenesisDenom proto.InternalMessageInfo - -func (m *GenesisDenom) GetDenom() string { - if m != nil { - return m.Denom - } - return "" -} - -func (m *GenesisDenom) GetAuthorityMetadata() DenomAuthorityMetadata { - if m != nil { - return m.AuthorityMetadata - } - return DenomAuthorityMetadata{} -} - -func init() { - proto.RegisterType((*GenesisState)(nil), "osmosis.tokenfactory.v1beta1.GenesisState") - proto.RegisterType((*GenesisDenom)(nil), "osmosis.tokenfactory.v1beta1.GenesisDenom") -} - -func init() { - proto.RegisterFile("cosmwasm/tokenfactory/v1beta1/genesis.proto", fileDescriptor_b333539769138b3e) -} - -var fileDescriptor_b333539769138b3e = []byte{ - // 374 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4e, 0xce, 0x2f, 0xce, - 0x2d, 0x4f, 0x2c, 0xce, 0xd5, 0x2f, 0xc9, 0xcf, 0x4e, 0xcd, 0x4b, 0x4b, 0x4c, 0x2e, 0xc9, 0x2f, - 0xaa, 0xd4, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, - 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0xc9, 0x2f, 0xce, 0xcd, 0x07, 0x71, 0x91, - 0xd5, 0xea, 0x41, 0xd5, 0x4a, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0x15, 0xea, 0x83, 0x58, 0x10, - 0x3d, 0x52, 0x66, 0xf8, 0x2d, 0x48, 0x2c, 0x2d, 0xc9, 0xc8, 0x2f, 0xca, 0x2c, 0xa9, 0x8c, 0xcf, - 0x4d, 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x84, 0xea, 0xd3, 0xc2, 0xaf, 0xaf, 0x20, 0xb1, 0x28, - 0x31, 0x17, 0xea, 0x2e, 0xa5, 0x23, 0x8c, 0x5c, 0x3c, 0xee, 0x10, 0x97, 0x06, 0x97, 0x24, 0x96, - 0xa4, 0x0a, 0x39, 0x71, 0xb1, 0x41, 0x14, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0xa9, 0xe8, - 0xe1, 0x73, 0xb9, 0x5e, 0x00, 0x58, 0xad, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x50, 0x9d, - 0x42, 0x05, 0x5c, 0x7c, 0x50, 0x75, 0xf1, 0x29, 0xa9, 0x79, 0xf9, 0xb9, 0xc5, 0x12, 0x4c, 0x0a, - 0xcc, 0x1a, 0xdc, 0x46, 0x5a, 0xf8, 0xcd, 0x82, 0xba, 0xc3, 0x05, 0xa4, 0xc5, 0x49, 0x16, 0x64, - 0xe2, 0xa7, 0x7b, 0xf2, 0xa2, 0x95, 0x89, 0xb9, 0x39, 0x56, 0x4a, 0xa8, 0xe6, 0x29, 0x05, 0xf1, - 0x42, 0x05, 0x5c, 0x20, 0xfc, 0xa3, 0x08, 0x6f, 0x80, 0x45, 0x84, 0xd4, 0xb8, 0x58, 0xc1, 0x4a, - 0xc1, 0xbe, 0xe0, 0x74, 0x12, 0xf8, 0x74, 0x4f, 0x9e, 0x07, 0x62, 0x12, 0x58, 0x58, 0x29, 0x08, - 0x22, 0x2d, 0xd4, 0xc6, 0xc8, 0x25, 0x84, 0x19, 0x90, 0x12, 0x4c, 0x60, 0xbf, 0x9b, 0xe0, 0x77, - 0x2f, 0xd8, 0x26, 0x47, 0x98, 0x66, 0x5f, 0xa8, 0x5e, 0x27, 0x45, 0xa8, 0xcb, 0x25, 0x21, 0xf6, - 0x61, 0x9a, 0xae, 0x14, 0x24, 0x98, 0x88, 0xae, 0xcb, 0x8a, 0xe5, 0xc5, 0x02, 0x79, 0x46, 0x27, - 0xff, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, - 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0x32, 0x4d, 0xcf, 0x2c, 0xc9, - 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x2f, 0x49, 0x2d, 0x2a, 0xc8, 0x4b, 0x2d, 0x29, 0xcf, - 0x2f, 0xca, 0x06, 0xb3, 0x75, 0x93, 0xf3, 0x8b, 0x52, 0xf5, 0x2b, 0x50, 0xa3, 0xbb, 0xa4, 0xb2, - 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x1c, 0xcd, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe4, 0x72, - 0x8b, 0x18, 0xad, 0x02, 0x00, 0x00, -} - -func (this *GenesisDenom) Equal(that interface{}) bool { - if that == nil { - return this == nil - } - - that1, ok := that.(*GenesisDenom) - if !ok { - that2, ok := that.(GenesisDenom) - if ok { - that1 = &that2 - } else { - return false - } - } - if that1 == nil { - return this == nil - } else if this == nil { - return false - } - if this.Denom != that1.Denom { - return false - } - if !this.AuthorityMetadata.Equal(&that1.AuthorityMetadata) { - return false - } - return true -} - -func (m *GenesisState) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.FactoryDenoms) > 0 { - for iNdEx := len(m.FactoryDenoms) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.FactoryDenoms[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenesis(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - } - } - { - size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenesis(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *GenesisDenom) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GenesisDenom) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *GenesisDenom) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.AuthorityMetadata.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintGenesis(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - if len(m.Denom) > 0 { - i -= len(m.Denom) - copy(dAtA[i:], m.Denom) - i = encodeVarintGenesis(dAtA, i, uint64(len(m.Denom))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { - offset -= sovGenesis(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} - -func (m *GenesisState) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Params.Size() - n += 1 + l + sovGenesis(uint64(l)) - if len(m.FactoryDenoms) > 0 { - for _, e := range m.FactoryDenoms { - l = e.Size() - n += 1 + l + sovGenesis(uint64(l)) - } - } - return n -} - -func (m *GenesisDenom) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Denom) - if l > 0 { - n += 1 + l + sovGenesis(uint64(l)) - } - l = m.AuthorityMetadata.Size() - n += 1 + l + sovGenesis(uint64(l)) - return n -} - -func sovGenesis(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} - -func sozGenesis(x uint64) (n int) { - return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} - -func (m *GenesisState) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenesis - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenesis - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FactoryDenoms", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenesis - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenesis - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.FactoryDenoms = append(m.FactoryDenoms, GenesisDenom{}) - if err := m.FactoryDenoms[len(m.FactoryDenoms)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenesis(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenesis - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *GenesisDenom) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GenesisDenom: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GenesisDenom: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthGenesis - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthGenesis - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Denom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AuthorityMetadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenesis - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGenesis - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthGenesis - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.AuthorityMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenesis(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenesis - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func skipGenesis(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenesis - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenesis - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowGenesis - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthGenesis - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupGenesis - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthGenesis - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") -) diff --git a/x/tokenfactory/types/genesis_test.go b/x/tokenfactory/types/genesis_test.go deleted file mode 100644 index c93adb4..0000000 --- a/x/tokenfactory/types/genesis_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package types_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/tokenfactory/types" -) - -func TestGenesisState_Validate(t *testing.T) { - for _, tc := range []struct { - desc string - genState *types.GenesisState - valid bool - }{ - { - desc: "default is valid", - genState: types.DefaultGenesis(), - valid: true, - }, - { - desc: "valid genesis state", - genState: &types.GenesisState{ - FactoryDenoms: []types.GenesisDenom{ - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8", - }, - }, - }, - }, - valid: true, - }, - { - desc: "different admin from creator", - genState: &types.GenesisState{ - FactoryDenoms: []types.GenesisDenom{ - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "cosmos1ft6e5esdtdegnvcr3djd3ftk4kwpcr6jta8eyh", - }, - }, - }, - }, - valid: true, - }, - { - desc: "empty admin", - genState: &types.GenesisState{ - FactoryDenoms: []types.GenesisDenom{ - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "", - }, - }, - }, - }, - valid: true, - }, - { - desc: "no admin", - genState: &types.GenesisState{ - FactoryDenoms: []types.GenesisDenom{ - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - }, - }, - }, - valid: true, - }, - { - desc: "invalid admin", - genState: &types.GenesisState{ - FactoryDenoms: []types.GenesisDenom{ - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "moose", - }, - }, - }, - }, - valid: false, - }, - { - desc: "multiple denoms", - genState: &types.GenesisState{ - FactoryDenoms: []types.GenesisDenom{ - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "", - }, - }, - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/litecoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "", - }, - }, - }, - }, - valid: true, - }, - { - desc: "duplicate denoms", - genState: &types.GenesisState{ - FactoryDenoms: []types.GenesisDenom{ - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "", - }, - }, - { - Denom: "factory/cosmos1t7egva48prqmzl59x5ngv4zx0dtrwewcdqdjr8/bitcoin", - AuthorityMetadata: types.DenomAuthorityMetadata{ - Admin: "", - }, - }, - }, - }, - valid: false, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - err := tc.genState.Validate() - if tc.valid { - require.NoError(t, err) - } else { - require.Error(t, err) - } - }) - } -} diff --git a/x/tokenfactory/types/keys.go b/x/tokenfactory/types/keys.go deleted file mode 100644 index fac4a6e..0000000 --- a/x/tokenfactory/types/keys.go +++ /dev/null @@ -1,49 +0,0 @@ -package types - -import ( - "strings" -) - -const ( - // ModuleName defines the module name - ModuleName = "tokenfactory" - - // StoreKey defines the primary module store key - StoreKey = ModuleName - - // RouterKey is the message route for slashing - RouterKey = ModuleName - - // QuerierRoute defines the module's query routing key - QuerierRoute = ModuleName - - // MemStoreKey defines the in-memory store key - MemStoreKey = "mem_tokenfactory" -) - -// KeySeparator is used to combine parts of the keys in the store -const KeySeparator = "|" - -var ( - DenomAuthorityMetadataKey = "authoritymetadata" - DenomsPrefixKey = "denoms" - CreatorPrefixKey = "creator" - AdminPrefixKey = "admin" -) - -// GetDenomPrefixStore returns the store prefix where all the data associated with a specific denom -// is stored -func GetDenomPrefixStore(denom string) []byte { - return []byte(strings.Join([]string{DenomsPrefixKey, denom, ""}, KeySeparator)) -} - -// GetCreatorsPrefix returns the store prefix where the list of the denoms created by a specific -// creator are stored -func GetCreatorPrefix(creator string) []byte { - return []byte(strings.Join([]string{CreatorPrefixKey, creator, ""}, KeySeparator)) -} - -// GetCreatorsPrefix returns the store prefix where a list of all creator addresses are stored -func GetCreatorsPrefix() []byte { - return []byte(strings.Join([]string{CreatorPrefixKey, ""}, KeySeparator)) -} diff --git a/x/tokenfactory/types/msgs.go b/x/tokenfactory/types/msgs.go deleted file mode 100644 index e9fb237..0000000 --- a/x/tokenfactory/types/msgs.go +++ /dev/null @@ -1,247 +0,0 @@ -package types - -import ( - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -// constants -const ( - TypeMsgCreateDenom = "create_denom" - TypeMsgMint = "tf_mint" - TypeMsgBurn = "tf_burn" - TypeMsgForceTransfer = "force_transfer" - TypeMsgChangeAdmin = "change_admin" - TypeMsgSetDenomMetadata = "set_denom_metadata" -) - -var _ sdk.Msg = &MsgCreateDenom{} - -// NewMsgCreateDenom creates a msg to create a new denom -func NewMsgCreateDenom(sender, subdenom string) *MsgCreateDenom { - return &MsgCreateDenom{ - Sender: sender, - Subdenom: subdenom, - } -} - -func (m MsgCreateDenom) Route() string { return RouterKey } -func (m MsgCreateDenom) Type() string { return TypeMsgCreateDenom } -func (m MsgCreateDenom) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(m.Sender) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) - } - - _, err = GetTokenDenom(m.Sender, m.Subdenom) - if err != nil { - return errorsmod.Wrap(ErrInvalidDenom, err.Error()) - } - - return nil -} - -func (m MsgCreateDenom) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) -} - -func (m MsgCreateDenom) GetSigners() []sdk.AccAddress { - sender, _ := sdk.AccAddressFromBech32(m.Sender) - return []sdk.AccAddress{sender} -} - -var _ sdk.Msg = &MsgMint{} - -// NewMsgMint creates a message to mint tokens -func NewMsgMint(sender string, amount sdk.Coin) *MsgMint { - return &MsgMint{ - Sender: sender, - Amount: amount, - } -} - -func (m MsgMint) Route() string { return RouterKey } -func (m MsgMint) Type() string { return TypeMsgMint } -func (m MsgMint) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(m.Sender) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) - } - - if !m.Amount.IsValid() || m.Amount.Amount.Equal(sdk.ZeroInt()) { - return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) - } - - return nil -} - -func (m MsgMint) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) -} - -func (m MsgMint) GetSigners() []sdk.AccAddress { - sender, _ := sdk.AccAddressFromBech32(m.Sender) - return []sdk.AccAddress{sender} -} - -var _ sdk.Msg = &MsgBurn{} - -// NewMsgBurn creates a message to burn tokens -func NewMsgBurn(sender string, amount sdk.Coin) *MsgBurn { - return &MsgBurn{ - Sender: sender, - Amount: amount, - } -} - -func (m MsgBurn) Route() string { return RouterKey } -func (m MsgBurn) Type() string { return TypeMsgBurn } -func (m MsgBurn) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(m.Sender) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) - } - - if !m.Amount.IsValid() || m.Amount.Amount.Equal(sdk.ZeroInt()) { - return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) - } - - return nil -} - -func (m MsgBurn) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) -} - -func (m MsgBurn) GetSigners() []sdk.AccAddress { - sender, _ := sdk.AccAddressFromBech32(m.Sender) - return []sdk.AccAddress{sender} -} - -// var _ sdk.Msg = &MsgForceTransfer{} - -// // NewMsgForceTransfer creates a transfer funds from one account to another -// func NewMsgForceTransfer(sender string, amount sdk.Coin, fromAddr, toAddr string) *MsgForceTransfer { -// return &MsgForceTransfer{ -// Sender: sender, -// Amount: amount, -// TransferFromAddress: fromAddr, -// TransferToAddress: toAddr, -// } -// } - -// func (m MsgForceTransfer) Route() string { return RouterKey } -// func (m MsgForceTransfer) Type() string { return TypeMsgForceTransfer } -// func (m MsgForceTransfer) ValidateBasic() error { -// _, err := sdk.AccAddressFromBech32(m.Sender) -// if err != nil { -// return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) -// } - -// _, err = sdk.AccAddressFromBech32(m.TransferFromAddress) -// if err != nil { -// return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid address (%s)", err) -// } -// _, err = sdk.AccAddressFromBech32(m.TransferToAddress) -// if err != nil { -// return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid address (%s)", err) -// } - -// if !m.Amount.IsValid() { -// return errorsmod.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) -// } - -// return nil -// } - -// func (m MsgForceTransfer) GetSignBytes() []byte { -// return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) -// } - -// func (m MsgForceTransfer) GetSigners() []sdk.AccAddress { -// sender, _ := sdk.AccAddressFromBech32(m.Sender) -// return []sdk.AccAddress{sender} -// } - -var _ sdk.Msg = &MsgChangeAdmin{} - -// NewMsgChangeAdmin creates a message to burn tokens -func NewMsgChangeAdmin(sender, denom, newAdmin string) *MsgChangeAdmin { - return &MsgChangeAdmin{ - Sender: sender, - Denom: denom, - NewAdmin: newAdmin, - } -} - -func (m MsgChangeAdmin) Route() string { return RouterKey } -func (m MsgChangeAdmin) Type() string { return TypeMsgChangeAdmin } -func (m MsgChangeAdmin) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(m.Sender) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) - } - - _, err = sdk.AccAddressFromBech32(m.NewAdmin) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid address (%s)", err) - } - - _, _, err = DeconstructDenom(m.Denom) - if err != nil { - return err - } - - return nil -} - -func (m MsgChangeAdmin) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) -} - -func (m MsgChangeAdmin) GetSigners() []sdk.AccAddress { - sender, _ := sdk.AccAddressFromBech32(m.Sender) - return []sdk.AccAddress{sender} -} - -var _ sdk.Msg = &MsgSetDenomMetadata{} - -// NewMsgChangeAdmin creates a message to burn tokens -func NewMsgSetDenomMetadata(sender string, metadata banktypes.Metadata) *MsgSetDenomMetadata { - return &MsgSetDenomMetadata{ - Sender: sender, - Metadata: metadata, - } -} - -func (m MsgSetDenomMetadata) Route() string { return RouterKey } -func (m MsgSetDenomMetadata) Type() string { return TypeMsgSetDenomMetadata } -func (m MsgSetDenomMetadata) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(m.Sender) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) - } - - err = m.Metadata.Validate() - if err != nil { - return err - } - - _, _, err = DeconstructDenom(m.Metadata.Base) - if err != nil { - return err - } - - return nil -} - -func (m MsgSetDenomMetadata) GetSignBytes() []byte { - return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&m)) -} - -func (m MsgSetDenomMetadata) GetSigners() []sdk.AccAddress { - sender, _ := sdk.AccAddressFromBech32(m.Sender) - return []sdk.AccAddress{sender} -} diff --git a/x/tokenfactory/types/msgs_test.go b/x/tokenfactory/types/msgs_test.go deleted file mode 100644 index 2b71ce1..0000000 --- a/x/tokenfactory/types/msgs_test.go +++ /dev/null @@ -1,451 +0,0 @@ -package types_test - -import ( - fmt "fmt" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/tokenfactory/testhelpers" - "github.com/terpnetwork/terp-core/x/tokenfactory/types" - - "github.com/cometbft/cometbft/crypto/ed25519" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -// // Test authz serialize and de-serializes for tokenfactory msg. -func TestAuthzMsg(t *testing.T) { - t.Skip("TODO: figure out how to register authz interfaces for tests") - pk1 := ed25519.GenPrivKey().PubKey() - addr1 := sdk.AccAddress(pk1.Address()).String() - coin := sdk.NewCoin("denom", sdk.NewInt(1)) - - testCases := []struct { - name string - msg sdk.Msg - }{ - { - name: "MsgCreateDenom", - msg: &types.MsgCreateDenom{ - Sender: addr1, - Subdenom: "valoper1xyz", - }, - }, - { - name: "MsgBurn", - msg: &types.MsgBurn{ - Sender: addr1, - Amount: coin, - }, - }, - { - name: "MsgMint", - msg: &types.MsgMint{ - Sender: addr1, - Amount: coin, - }, - }, - { - name: "MsgChangeAdmin", - msg: &types.MsgChangeAdmin{ - Sender: addr1, - Denom: "denom", - NewAdmin: "osmo1q8tq5qhrhw6t970egemuuwywhlhpnmdmts6xnu", - }, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - testhelpers.TestMessageAuthzSerialization(t, tc.msg) - }) - } -} - -// TestMsgCreateDenom tests if valid/invalid create denom messages are properly validated/invalidated -func TestMsgCreateDenom(t *testing.T) { - // generate a private/public key pair and get the respective address - pk1 := ed25519.GenPrivKey().PubKey() - addr1 := sdk.AccAddress(pk1.Address()) - - // make a proper createDenom message - createMsg := func(after func(msg types.MsgCreateDenom) types.MsgCreateDenom) types.MsgCreateDenom { - properMsg := *types.NewMsgCreateDenom( - addr1.String(), - "bitcoin", - ) - - return after(properMsg) - } - - // validate createDenom message was created as intended - msg := createMsg(func(msg types.MsgCreateDenom) types.MsgCreateDenom { - return msg - }) - require.Equal(t, msg.Route(), types.RouterKey) - require.Equal(t, msg.Type(), "create_denom") - signers := msg.GetSigners() - require.Equal(t, len(signers), 1) - require.Equal(t, signers[0].String(), addr1.String()) - - tests := []struct { - name string - msg types.MsgCreateDenom - expectPass bool - }{ - { - name: "proper msg", - msg: createMsg(func(msg types.MsgCreateDenom) types.MsgCreateDenom { - return msg - }), - expectPass: true, - }, - { - name: "empty sender", - msg: createMsg(func(msg types.MsgCreateDenom) types.MsgCreateDenom { - msg.Sender = "" - return msg - }), - expectPass: false, - }, - { - name: "invalid subdenom", - msg: createMsg(func(msg types.MsgCreateDenom) types.MsgCreateDenom { - msg.Subdenom = "thissubdenomismuchtoolongasdkfjaasdfdsafsdlkfnmlksadmflksmdlfmlsakmfdsafasdfasdf" - return msg - }), - expectPass: false, - }, - } - - for _, test := range tests { - if test.expectPass { - require.NoError(t, test.msg.ValidateBasic(), "test: %v", test.name) - } else { - require.Error(t, test.msg.ValidateBasic(), "test: %v", test.name) - } - } -} - -// TestMsgMint tests if valid/invalid create denom messages are properly validated/invalidated -func TestMsgMint(t *testing.T) { - // generate a private/public key pair and get the respective address - pk1 := ed25519.GenPrivKey().PubKey() - addr1 := sdk.AccAddress(pk1.Address()) - - // make a proper mint message - createMsg := func(after func(msg types.MsgMint) types.MsgMint) types.MsgMint { - properMsg := *types.NewMsgMint( - addr1.String(), - sdk.NewCoin("bitcoin", sdk.NewInt(500000000)), - ) - - return after(properMsg) - } - - // validate mint message was created as intended - msg := createMsg(func(msg types.MsgMint) types.MsgMint { - return msg - }) - require.Equal(t, msg.Route(), types.RouterKey) - require.Equal(t, msg.Type(), "tf_mint") - signers := msg.GetSigners() - require.Equal(t, len(signers), 1) - require.Equal(t, signers[0].String(), addr1.String()) - - tests := []struct { - name string - msg types.MsgMint - expectPass bool - }{ - { - name: "proper msg", - msg: createMsg(func(msg types.MsgMint) types.MsgMint { - return msg - }), - expectPass: true, - }, - { - name: "empty sender", - msg: createMsg(func(msg types.MsgMint) types.MsgMint { - msg.Sender = "" - return msg - }), - expectPass: false, - }, - { - name: "zero amount", - msg: createMsg(func(msg types.MsgMint) types.MsgMint { - msg.Amount = sdk.NewCoin("bitcoin", sdk.ZeroInt()) - return msg - }), - expectPass: false, - }, - { - name: "negative amount", - msg: createMsg(func(msg types.MsgMint) types.MsgMint { - msg.Amount.Amount = sdk.NewInt(-10000000) - return msg - }), - expectPass: false, - }, - } - - for _, test := range tests { - if test.expectPass { - require.NoError(t, test.msg.ValidateBasic(), "test: %v", test.name) - } else { - require.Error(t, test.msg.ValidateBasic(), "test: %v", test.name) - } - } -} - -// TestMsgBurn tests if valid/invalid create denom messages are properly validated/invalidated -func TestMsgBurn(t *testing.T) { - // generate a private/public key pair and get the respective address - pk1 := ed25519.GenPrivKey().PubKey() - addr1 := sdk.AccAddress(pk1.Address()) - - // make a proper burn message - baseMsg := types.NewMsgBurn( - addr1.String(), - sdk.NewCoin("bitcoin", sdk.NewInt(500000000)), - ) - - // validate burn message was created as intended - require.Equal(t, baseMsg.Route(), types.RouterKey) - require.Equal(t, baseMsg.Type(), "tf_burn") - signers := baseMsg.GetSigners() - require.Equal(t, len(signers), 1) - require.Equal(t, signers[0].String(), addr1.String()) - - tests := []struct { - name string - msg func() *types.MsgBurn - expectPass bool - }{ - { - name: "proper msg", - msg: func() *types.MsgBurn { - msg := baseMsg - return msg - }, - expectPass: true, - }, - { - name: "empty sender", - msg: func() *types.MsgBurn { - msg := baseMsg - msg.Sender = "" - return msg - }, - expectPass: false, - }, - { - name: "zero amount", - msg: func() *types.MsgBurn { - msg := baseMsg - msg.Amount.Amount = sdk.ZeroInt() - return msg - }, - expectPass: false, - }, - { - name: "negative amount", - msg: func() *types.MsgBurn { - msg := baseMsg - msg.Amount.Amount = sdk.NewInt(-10000000) - return msg - }, - expectPass: false, - }, - } - - for _, test := range tests { - if test.expectPass { - require.NoError(t, test.msg().ValidateBasic(), "test: %v", test.name) - } else { - require.Error(t, test.msg().ValidateBasic(), "test: %v", test.name) - } - } -} - -// TestMsgChangeAdmin tests if valid/invalid create denom messages are properly validated/invalidated -func TestMsgChangeAdmin(t *testing.T) { - // generate a private/public key pair and get the respective address - pk1 := ed25519.GenPrivKey().PubKey() - addr1 := sdk.AccAddress(pk1.Address()) - pk2 := ed25519.GenPrivKey().PubKey() - addr2 := sdk.AccAddress(pk2.Address()) - tokenFactoryDenom := fmt.Sprintf("factory/%s/bitcoin", addr1.String()) - - // make a proper changeAdmin message - baseMsg := types.NewMsgChangeAdmin( - addr1.String(), - tokenFactoryDenom, - addr2.String(), - ) - - // validate changeAdmin message was created as intended - require.Equal(t, baseMsg.Route(), types.RouterKey) - require.Equal(t, baseMsg.Type(), "change_admin") - signers := baseMsg.GetSigners() - require.Equal(t, len(signers), 1) - require.Equal(t, signers[0].String(), addr1.String()) - - tests := []struct { - name string - msg func() *types.MsgChangeAdmin - expectPass bool - }{ - { - name: "proper msg", - msg: func() *types.MsgChangeAdmin { - msg := baseMsg - return msg - }, - expectPass: true, - }, - { - name: "empty sender", - msg: func() *types.MsgChangeAdmin { - msg := baseMsg - msg.Sender = "" - return msg - }, - expectPass: false, - }, - { - name: "empty newAdmin", - msg: func() *types.MsgChangeAdmin { - msg := baseMsg - msg.NewAdmin = "" - return msg - }, - expectPass: false, - }, - { - name: "invalid denom", - msg: func() *types.MsgChangeAdmin { - msg := baseMsg - msg.Denom = "bitcoin" - return msg - }, - expectPass: false, - }, - } - - for _, test := range tests { - if test.expectPass { - require.NoError(t, test.msg().ValidateBasic(), "test: %v", test.name) - } else { - require.Error(t, test.msg().ValidateBasic(), "test: %v", test.name) - } - } -} - -// TestMsgSetDenomMetadata tests if valid/invalid create denom messages are properly validated/invalidated -func TestMsgSetDenomMetadata(t *testing.T) { - // generate a private/public key pair and get the respective address - pk1 := ed25519.GenPrivKey().PubKey() - addr1 := sdk.AccAddress(pk1.Address()) - tokenFactoryDenom := fmt.Sprintf("factory/%s/bitcoin", addr1.String()) - denomMetadata := banktypes.Metadata{ - Description: "nakamoto", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: tokenFactoryDenom, - Exponent: 0, - }, - { - Denom: "sats", - Exponent: 6, - }, - }, - Display: "sats", - Base: tokenFactoryDenom, - Name: "bitcoin", - Symbol: "BTC", - } - invalidDenomMetadata := banktypes.Metadata{ - Description: "nakamoto", - DenomUnits: []*banktypes.DenomUnit{ - { - Denom: "bitcoin", - Exponent: 0, - }, - { - Denom: "sats", - Exponent: 6, - }, - }, - Display: "sats", - Base: "bitcoin", - Name: "bitcoin", - Symbol: "BTC", - } - - // make a proper setDenomMetadata message - baseMsg := types.NewMsgSetDenomMetadata( - addr1.String(), - denomMetadata, - ) - - // validate setDenomMetadata message was created as intended - require.Equal(t, baseMsg.Route(), types.RouterKey) - require.Equal(t, baseMsg.Type(), "set_denom_metadata") - signers := baseMsg.GetSigners() - require.Equal(t, len(signers), 1) - require.Equal(t, signers[0].String(), addr1.String()) - - tests := []struct { - name string - msg func() *types.MsgSetDenomMetadata - expectPass bool - }{ - { - name: "proper msg", - msg: func() *types.MsgSetDenomMetadata { - msg := baseMsg - return msg - }, - expectPass: true, - }, - { - name: "empty sender", - msg: func() *types.MsgSetDenomMetadata { - msg := baseMsg - msg.Sender = "" - return msg - }, - expectPass: false, - }, - { - name: "invalid metadata", - msg: func() *types.MsgSetDenomMetadata { - msg := baseMsg - msg.Metadata.Name = "" - return msg - }, - - expectPass: false, - }, - { - name: "invalid base", - msg: func() *types.MsgSetDenomMetadata { - msg := baseMsg - msg.Metadata = invalidDenomMetadata - return msg - }, - expectPass: false, - }, - } - - for _, test := range tests { - if test.expectPass { - require.NoError(t, test.msg().ValidateBasic(), "test: %v", test.name) - } else { - require.Error(t, test.msg().ValidateBasic(), "test: %v", test.name) - } - } -} diff --git a/x/tokenfactory/types/params.go b/x/tokenfactory/types/params.go deleted file mode 100644 index a483078..0000000 --- a/x/tokenfactory/types/params.go +++ /dev/null @@ -1,62 +0,0 @@ -package types - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" -) - -// Parameter store keys. -var ( - KeyDenomCreationFee = []byte("DenomCreationFee") -) - -// ParamTable for gamm module. -func ParamKeyTable() paramtypes.KeyTable { - return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) -} - -func NewParams(denomCreationFee sdk.Coins) Params { - return Params{ - DenomCreationFee: denomCreationFee, - } -} - -// default gamm module parameters. -func DefaultParams() Params { - return Params{ - // this was from osmosis - // DenomCreationFee: sdk.NewCoins(sdk.NewInt64Coin(appparams.BaseCoinUnit, 10_000_000)), // 10 OSMO - DenomCreationFee: sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 10_000_000)), // 10 OSMO - } -} - -// validate params. -func (p Params) Validate() error { - if err := validateDenomCreationFee(p.DenomCreationFee); err != nil { - return err - } - - return nil -} - -// Implements params.ParamSet. -func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { - return paramtypes.ParamSetPairs{ - paramtypes.NewParamSetPair(KeyDenomCreationFee, &p.DenomCreationFee, validateDenomCreationFee), - } -} - -func validateDenomCreationFee(i interface{}) error { - v, ok := i.(sdk.Coins) - if !ok { - return fmt.Errorf("invalid parameter type: %T", i) - } - - if v.Validate() != nil { - return fmt.Errorf("invalid denom creation fee: %+v", i) - } - - return nil -} diff --git a/x/tokenfactory/types/params.pb.go b/x/tokenfactory/types/params.pb.go deleted file mode 100644 index 73973e6..0000000 --- a/x/tokenfactory/types/params.pb.go +++ /dev/null @@ -1,354 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: cosmwasm/tokenfactory/v1beta1/params.proto - -package types - -import ( - fmt "fmt" - io "io" - math "math" - math_bits "math/bits" - - _ "github.com/cosmos/cosmos-proto" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" - types "github.com/cosmos/cosmos-sdk/types" - _ "github.com/cosmos/gogoproto/gogoproto" - proto "github.com/cosmos/gogoproto/proto" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = proto.Marshal - _ = fmt.Errorf - _ = math.Inf -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// Params defines the parameters for the tokenfactory module. -type Params struct { - DenomCreationFee github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=denom_creation_fee,json=denomCreationFee,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"denom_creation_fee" yaml:"denom_creation_fee"` -} - -func (m *Params) Reset() { *m = Params{} } -func (m *Params) String() string { return proto.CompactTextString(m) } -func (*Params) ProtoMessage() {} -func (*Params) Descriptor() ([]byte, []int) { - return fileDescriptor_c2e403a2e90cdef7, []int{0} -} - -func (m *Params) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_Params.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *Params) XXX_Merge(src proto.Message) { - xxx_messageInfo_Params.Merge(m, src) -} - -func (m *Params) XXX_Size() int { - return m.Size() -} - -func (m *Params) XXX_DiscardUnknown() { - xxx_messageInfo_Params.DiscardUnknown(m) -} - -var xxx_messageInfo_Params proto.InternalMessageInfo - -func (m *Params) GetDenomCreationFee() github_com_cosmos_cosmos_sdk_types.Coins { - if m != nil { - return m.DenomCreationFee - } - return nil -} - -func init() { - proto.RegisterType((*Params)(nil), "osmosis.tokenfactory.v1beta1.Params") -} - -func init() { - proto.RegisterFile("cosmwasm/tokenfactory/v1beta1/params.proto", fileDescriptor_c2e403a2e90cdef7) -} - -var fileDescriptor_c2e403a2e90cdef7 = []byte{ - // 314 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x90, 0xb1, 0x4a, 0x03, 0x41, - 0x10, 0x86, 0xef, 0x10, 0x52, 0xc4, 0x46, 0x82, 0x85, 0x09, 0xb2, 0x91, 0x54, 0x41, 0xc8, 0x2d, - 0x51, 0xb4, 0xb0, 0x4c, 0xc0, 0x4e, 0x14, 0x4b, 0x9b, 0x63, 0x6e, 0x33, 0x49, 0x8e, 0xb8, 0x37, - 0xc7, 0xee, 0xc4, 0x78, 0x6f, 0x61, 0x65, 0x6f, 0xeb, 0x93, 0xa4, 0x4c, 0x69, 0x15, 0x25, 0x79, - 0x03, 0x9f, 0x40, 0xb2, 0xb7, 0x4a, 0x44, 0xb0, 0xda, 0x19, 0xe6, 0xff, 0xbf, 0x99, 0x7f, 0xab, - 0xc7, 0x8a, 0xac, 0x9e, 0x81, 0xd5, 0x92, 0x69, 0x82, 0xd9, 0x10, 0x14, 0x93, 0x29, 0xe4, 0x43, - 0x37, 0x41, 0x86, 0xae, 0xcc, 0xc1, 0x80, 0xb6, 0x51, 0x6e, 0x88, 0xa9, 0x76, 0x48, 0x56, 0x93, - 0x4d, 0x6d, 0xb4, 0x2d, 0x8d, 0xbc, 0xb4, 0xb1, 0x3f, 0xa2, 0x11, 0x39, 0xa1, 0xdc, 0x54, 0xa5, - 0xa7, 0x71, 0xfe, 0x3f, 0x1f, 0xa6, 0x3c, 0x26, 0x93, 0x72, 0x11, 0x6b, 0x64, 0x18, 0x00, 0x83, - 0xf7, 0xd5, 0x95, 0x5b, 0x16, 0x97, 0xc0, 0xb2, 0xf1, 0x23, 0x51, 0x76, 0x32, 0x01, 0x8b, 0x3f, - 0x20, 0x45, 0x69, 0x56, 0xce, 0x5b, 0x2f, 0x61, 0xb5, 0x72, 0xe3, 0xee, 0xae, 0x3d, 0x87, 0xd5, - 0xda, 0x00, 0x33, 0xd2, 0xb1, 0x32, 0x08, 0x9c, 0x52, 0x16, 0x0f, 0x11, 0x0f, 0xc2, 0xa3, 0x9d, - 0xf6, 0xee, 0x49, 0x3d, 0xf2, 0xd8, 0x0d, 0xe8, 0x3b, 0x46, 0xd4, 0xa7, 0x34, 0xeb, 0x5d, 0xcd, - 0x97, 0xcd, 0xe0, 0x73, 0xd9, 0xac, 0x17, 0xa0, 0xef, 0x2f, 0x5a, 0x7f, 0x11, 0xad, 0xd7, 0xf7, - 0x66, 0x7b, 0x94, 0xf2, 0x78, 0x9a, 0x44, 0x8a, 0xb4, 0x3f, 0xd0, 0x3f, 0x1d, 0x3b, 0x98, 0x48, - 0x2e, 0x72, 0xb4, 0x8e, 0x66, 0x6f, 0xf7, 0x1c, 0xa0, 0xef, 0xfd, 0x97, 0x88, 0xbd, 0xeb, 0xf9, - 0x4a, 0x84, 0x8b, 0x95, 0x08, 0x3f, 0x56, 0x22, 0x7c, 0x5a, 0x8b, 0x60, 0xb1, 0x16, 0xc1, 0xdb, - 0x5a, 0x04, 0x77, 0x67, 0x5b, 0x54, 0x46, 0x93, 0x67, 0xc8, 0x33, 0x32, 0x13, 0x57, 0x77, 0x14, - 0x19, 0x94, 0x8f, 0xbf, 0xbf, 0xd2, 0x2d, 0x4a, 0x2a, 0x2e, 0xfb, 0xe9, 0x57, 0x00, 0x00, 0x00, - 0xff, 0xff, 0x87, 0xe5, 0x07, 0xed, 0xd0, 0x01, 0x00, 0x00, -} - -func (m *Params) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Params) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.DenomCreationFee) > 0 { - for iNdEx := len(m.DenomCreationFee) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.DenomCreationFee[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintParams(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func encodeVarintParams(dAtA []byte, offset int, v uint64) int { - offset -= sovParams(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} - -func (m *Params) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.DenomCreationFee) > 0 { - for _, e := range m.DenomCreationFee { - l = e.Size() - n += 1 + l + sovParams(uint64(l)) - } - } - return n -} - -func sovParams(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} - -func sozParams(x uint64) (n int) { - return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} - -func (m *Params) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowParams - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Params: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DenomCreationFee", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowParams - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthParams - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthParams - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DenomCreationFee = append(m.DenomCreationFee, types.Coin{}) - if err := m.DenomCreationFee[len(m.DenomCreationFee)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipParams(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthParams - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func skipParams(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowParams - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowParams - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowParams - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthParams - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupParams - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthParams - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") -) diff --git a/x/tokenfactory/types/query.pb.go b/x/tokenfactory/types/query.pb.go deleted file mode 100644 index 61e82d9..0000000 --- a/x/tokenfactory/types/query.pb.go +++ /dev/null @@ -1,1377 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: cosmwasm/tokenfactory/v1beta1/query.proto - -package types - -import ( - context "context" - fmt "fmt" - io "io" - math "math" - math_bits "math/bits" - - _ "github.com/cosmos/cosmos-sdk/types/query" - _ "github.com/cosmos/gogoproto/gogoproto" - grpc1 "github.com/cosmos/gogoproto/grpc" - proto "github.com/cosmos/gogoproto/proto" - _ "google.golang.org/genproto/googleapis/api/annotations" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = proto.Marshal - _ = fmt.Errorf - _ = math.Inf -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// QueryParamsRequest is the request type for the Query/Params RPC method. -type QueryParamsRequest struct{} - -func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } -func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } -func (*QueryParamsRequest) ProtoMessage() {} -func (*QueryParamsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d8606ce711f56ea6, []int{0} -} - -func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryParamsRequest.Merge(m, src) -} - -func (m *QueryParamsRequest) XXX_Size() int { - return m.Size() -} - -func (m *QueryParamsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo - -// QueryParamsResponse is the response type for the Query/Params RPC method. -type QueryParamsResponse struct { - // params defines the parameters of the module. - Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` -} - -func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } -func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } -func (*QueryParamsResponse) ProtoMessage() {} -func (*QueryParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d8606ce711f56ea6, []int{1} -} - -func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryParamsResponse.Merge(m, src) -} - -func (m *QueryParamsResponse) XXX_Size() int { - return m.Size() -} - -func (m *QueryParamsResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo - -func (m *QueryParamsResponse) GetParams() Params { - if m != nil { - return m.Params - } - return Params{} -} - -// QueryDenomAuthorityMetadataRequest defines the request structure for the -// DenomAuthorityMetadata gRPC query. -type QueryDenomAuthorityMetadataRequest struct { - Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty" yaml:"denom"` -} - -func (m *QueryDenomAuthorityMetadataRequest) Reset() { *m = QueryDenomAuthorityMetadataRequest{} } -func (m *QueryDenomAuthorityMetadataRequest) String() string { return proto.CompactTextString(m) } -func (*QueryDenomAuthorityMetadataRequest) ProtoMessage() {} -func (*QueryDenomAuthorityMetadataRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d8606ce711f56ea6, []int{2} -} - -func (m *QueryDenomAuthorityMetadataRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *QueryDenomAuthorityMetadataRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryDenomAuthorityMetadataRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *QueryDenomAuthorityMetadataRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryDenomAuthorityMetadataRequest.Merge(m, src) -} - -func (m *QueryDenomAuthorityMetadataRequest) XXX_Size() int { - return m.Size() -} - -func (m *QueryDenomAuthorityMetadataRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryDenomAuthorityMetadataRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryDenomAuthorityMetadataRequest proto.InternalMessageInfo - -func (m *QueryDenomAuthorityMetadataRequest) GetDenom() string { - if m != nil { - return m.Denom - } - return "" -} - -// QueryDenomAuthorityMetadataResponse defines the response structure for the -// DenomAuthorityMetadata gRPC query. -type QueryDenomAuthorityMetadataResponse struct { - AuthorityMetadata DenomAuthorityMetadata `protobuf:"bytes,1,opt,name=authority_metadata,json=authorityMetadata,proto3" json:"authority_metadata" yaml:"authority_metadata"` -} - -func (m *QueryDenomAuthorityMetadataResponse) Reset() { *m = QueryDenomAuthorityMetadataResponse{} } -func (m *QueryDenomAuthorityMetadataResponse) String() string { return proto.CompactTextString(m) } -func (*QueryDenomAuthorityMetadataResponse) ProtoMessage() {} -func (*QueryDenomAuthorityMetadataResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d8606ce711f56ea6, []int{3} -} - -func (m *QueryDenomAuthorityMetadataResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *QueryDenomAuthorityMetadataResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryDenomAuthorityMetadataResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *QueryDenomAuthorityMetadataResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryDenomAuthorityMetadataResponse.Merge(m, src) -} - -func (m *QueryDenomAuthorityMetadataResponse) XXX_Size() int { - return m.Size() -} - -func (m *QueryDenomAuthorityMetadataResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryDenomAuthorityMetadataResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryDenomAuthorityMetadataResponse proto.InternalMessageInfo - -func (m *QueryDenomAuthorityMetadataResponse) GetAuthorityMetadata() DenomAuthorityMetadata { - if m != nil { - return m.AuthorityMetadata - } - return DenomAuthorityMetadata{} -} - -// QueryDenomsFromCreatorRequest defines the request structure for the -// DenomsFromCreator gRPC query. -type QueryDenomsFromCreatorRequest struct { - Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty" yaml:"creator"` -} - -func (m *QueryDenomsFromCreatorRequest) Reset() { *m = QueryDenomsFromCreatorRequest{} } -func (m *QueryDenomsFromCreatorRequest) String() string { return proto.CompactTextString(m) } -func (*QueryDenomsFromCreatorRequest) ProtoMessage() {} -func (*QueryDenomsFromCreatorRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_d8606ce711f56ea6, []int{4} -} - -func (m *QueryDenomsFromCreatorRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *QueryDenomsFromCreatorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryDenomsFromCreatorRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *QueryDenomsFromCreatorRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryDenomsFromCreatorRequest.Merge(m, src) -} - -func (m *QueryDenomsFromCreatorRequest) XXX_Size() int { - return m.Size() -} - -func (m *QueryDenomsFromCreatorRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryDenomsFromCreatorRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryDenomsFromCreatorRequest proto.InternalMessageInfo - -func (m *QueryDenomsFromCreatorRequest) GetCreator() string { - if m != nil { - return m.Creator - } - return "" -} - -// QueryDenomsFromCreatorRequest defines the response structure for the -// DenomsFromCreator gRPC query. -type QueryDenomsFromCreatorResponse struct { - Denoms []string `protobuf:"bytes,1,rep,name=denoms,proto3" json:"denoms,omitempty" yaml:"denoms"` -} - -func (m *QueryDenomsFromCreatorResponse) Reset() { *m = QueryDenomsFromCreatorResponse{} } -func (m *QueryDenomsFromCreatorResponse) String() string { return proto.CompactTextString(m) } -func (*QueryDenomsFromCreatorResponse) ProtoMessage() {} -func (*QueryDenomsFromCreatorResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_d8606ce711f56ea6, []int{5} -} - -func (m *QueryDenomsFromCreatorResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *QueryDenomsFromCreatorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryDenomsFromCreatorResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *QueryDenomsFromCreatorResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryDenomsFromCreatorResponse.Merge(m, src) -} - -func (m *QueryDenomsFromCreatorResponse) XXX_Size() int { - return m.Size() -} - -func (m *QueryDenomsFromCreatorResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryDenomsFromCreatorResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryDenomsFromCreatorResponse proto.InternalMessageInfo - -func (m *QueryDenomsFromCreatorResponse) GetDenoms() []string { - if m != nil { - return m.Denoms - } - return nil -} - -func init() { - proto.RegisterType((*QueryParamsRequest)(nil), "osmosis.tokenfactory.v1beta1.QueryParamsRequest") - proto.RegisterType((*QueryParamsResponse)(nil), "osmosis.tokenfactory.v1beta1.QueryParamsResponse") - proto.RegisterType((*QueryDenomAuthorityMetadataRequest)(nil), "osmosis.tokenfactory.v1beta1.QueryDenomAuthorityMetadataRequest") - proto.RegisterType((*QueryDenomAuthorityMetadataResponse)(nil), "osmosis.tokenfactory.v1beta1.QueryDenomAuthorityMetadataResponse") - proto.RegisterType((*QueryDenomsFromCreatorRequest)(nil), "osmosis.tokenfactory.v1beta1.QueryDenomsFromCreatorRequest") - proto.RegisterType((*QueryDenomsFromCreatorResponse)(nil), "osmosis.tokenfactory.v1beta1.QueryDenomsFromCreatorResponse") -} - -func init() { - proto.RegisterFile("cosmwasm/tokenfactory/v1beta1/query.proto", fileDescriptor_d8606ce711f56ea6) -} - -var fileDescriptor_d8606ce711f56ea6 = []byte{ - // 582 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0x4d, 0x4f, 0x13, 0x41, - 0x18, 0xee, 0x2a, 0xd4, 0x30, 0x7e, 0x44, 0x46, 0x62, 0xb4, 0xc1, 0xad, 0x8e, 0x84, 0x80, 0xc1, - 0x1d, 0xc1, 0x8f, 0x83, 0x68, 0x94, 0xc5, 0xe8, 0x41, 0x89, 0xba, 0x37, 0xbd, 0x34, 0xd3, 0x65, - 0x58, 0x36, 0xb0, 0xfb, 0x2e, 0x33, 0x53, 0xb1, 0x21, 0x5c, 0x3c, 0x78, 0x36, 0xf1, 0xe8, 0x7f, - 0xf0, 0x77, 0x70, 0x24, 0xe1, 0xe2, 0xa9, 0x31, 0x2d, 0xf1, 0x07, 0xf4, 0x17, 0x98, 0xce, 0x4c, - 0xb5, 0xd8, 0xb2, 0xa9, 0x7a, 0xea, 0x64, 0xe6, 0x79, 0x9e, 0xf7, 0x79, 0xde, 0xf7, 0xed, 0xa2, - 0xd9, 0x10, 0x64, 0xb2, 0xcd, 0x64, 0x42, 0x15, 0x6c, 0xf0, 0x74, 0x8d, 0x85, 0x0a, 0x44, 0x9d, - 0xbe, 0x9b, 0xaf, 0x72, 0xc5, 0xe6, 0xe9, 0x56, 0x8d, 0x8b, 0xba, 0x97, 0x09, 0x50, 0x80, 0x27, - 0x41, 0x26, 0x20, 0x63, 0xe9, 0xf5, 0x22, 0x3d, 0x8b, 0x2c, 0x4d, 0x44, 0x10, 0x81, 0x06, 0xd2, - 0xce, 0xc9, 0x70, 0x4a, 0x93, 0x11, 0x40, 0xb4, 0xc9, 0x29, 0xcb, 0x62, 0xca, 0xd2, 0x14, 0x14, - 0x53, 0x31, 0xa4, 0xd2, 0xbe, 0xde, 0x08, 0xb5, 0x24, 0xad, 0x32, 0xc9, 0x4d, 0xa9, 0x5f, 0x85, - 0x33, 0x16, 0xc5, 0xa9, 0x06, 0x5b, 0xec, 0xbd, 0x7c, 0xa3, 0xac, 0xa6, 0xd6, 0x41, 0xc4, 0xaa, - 0x5e, 0x49, 0xb8, 0x62, 0xab, 0x4c, 0xb1, 0xde, 0x1a, 0xc7, 0xf3, 0x32, 0x26, 0x58, 0x62, 0xfd, - 0x90, 0x09, 0x84, 0x5f, 0x77, 0x5c, 0xbc, 0xd2, 0x97, 0x01, 0xdf, 0xaa, 0x71, 0xa9, 0xc8, 0x1b, - 0x74, 0xe1, 0xc8, 0xad, 0xcc, 0x20, 0x95, 0x1c, 0xfb, 0xa8, 0x68, 0xc8, 0x97, 0x9c, 0xab, 0xce, - 0xcc, 0xe9, 0x85, 0x29, 0x2f, 0xaf, 0x3f, 0x9e, 0x61, 0xfb, 0x23, 0x7b, 0x8d, 0x72, 0x21, 0xb0, - 0x4c, 0xf2, 0x02, 0x11, 0x2d, 0xfd, 0x84, 0xa7, 0x90, 0x2c, 0x75, 0x23, 0xac, 0xd8, 0x04, 0xd6, - 0x00, 0x9e, 0x46, 0xa3, 0xab, 0x1d, 0x80, 0x2e, 0x34, 0xe6, 0x9f, 0x6f, 0x37, 0xca, 0x67, 0xea, - 0x2c, 0xd9, 0xbc, 0x4f, 0xf4, 0x35, 0x09, 0xcc, 0x33, 0xf9, 0xea, 0xa0, 0xeb, 0xb9, 0x72, 0xd6, - 0xf9, 0x47, 0x07, 0xe1, 0xfe, 0x7e, 0xd9, 0x18, 0x77, 0xf2, 0x63, 0x0c, 0x96, 0xf6, 0xaf, 0x75, - 0x62, 0xb5, 0x1b, 0xe5, 0xcb, 0xc6, 0x57, 0xbf, 0x3a, 0x09, 0xc6, 0xd9, 0x9f, 0x2c, 0xb2, 0x82, - 0xae, 0xfc, 0xf6, 0x2b, 0x9f, 0x0a, 0x48, 0x96, 0x05, 0x67, 0x0a, 0x44, 0x37, 0xf9, 0x1c, 0x3a, - 0x15, 0x9a, 0x1b, 0x9b, 0x1d, 0xb7, 0x1b, 0xe5, 0x73, 0xa6, 0x86, 0x7d, 0x20, 0x41, 0x17, 0x42, - 0x9e, 0x23, 0xf7, 0x38, 0x39, 0x9b, 0x7c, 0x16, 0x15, 0x75, 0xab, 0x3a, 0x33, 0x3b, 0x39, 0x33, - 0xe6, 0x8f, 0xb7, 0x1b, 0xe5, 0xb3, 0x3d, 0xad, 0x94, 0x24, 0xb0, 0x80, 0x85, 0xc3, 0x11, 0x34, - 0xaa, 0xd5, 0xf0, 0x17, 0x07, 0x15, 0xcd, 0xf4, 0xf0, 0xad, 0xfc, 0xe6, 0xf4, 0x2f, 0x4f, 0x69, - 0xfe, 0x2f, 0x18, 0xc6, 0x24, 0x99, 0xfb, 0x70, 0x70, 0xf8, 0xf9, 0xc4, 0x34, 0x9e, 0xa2, 0x96, - 0x9a, 0xb7, 0xb9, 0xf8, 0x87, 0x83, 0x2e, 0x0e, 0x1e, 0x0a, 0x7e, 0x3c, 0x44, 0xed, 0xdc, 0xcd, - 0x2b, 0x2d, 0xfd, 0x87, 0x82, 0x4d, 0xf3, 0x4c, 0xa7, 0x59, 0xc2, 0x8f, 0xf2, 0xd3, 0x98, 0xae, - 0xd3, 0x1d, 0xfd, 0xbb, 0x3b, 0xe0, 0xef, 0x8c, 0x0f, 0x1c, 0x34, 0xde, 0x37, 0x59, 0xbc, 0x38, - 0xac, 0xc3, 0x01, 0xeb, 0x55, 0x7a, 0xf0, 0x6f, 0x64, 0x9b, 0x6c, 0x59, 0x27, 0x7b, 0x88, 0x17, - 0x87, 0x49, 0x56, 0x59, 0x13, 0x90, 0x54, 0xec, 0xa6, 0xd2, 0x1d, 0x7b, 0xd8, 0xf5, 0x5f, 0xee, - 0x35, 0x5d, 0x67, 0xbf, 0xe9, 0x3a, 0xdf, 0x9b, 0xae, 0xf3, 0xa9, 0xe5, 0x16, 0xf6, 0x5b, 0x6e, - 0xe1, 0x5b, 0xcb, 0x2d, 0xbc, 0xbd, 0x1b, 0xc5, 0x6a, 0xbd, 0x56, 0xf5, 0x42, 0x48, 0xa8, 0xe2, - 0x22, 0x4b, 0xb9, 0xda, 0x06, 0xb1, 0xa1, 0xcf, 0x37, 0x43, 0x10, 0x9c, 0xbe, 0x3f, 0x5a, 0x50, - 0xd5, 0x33, 0x2e, 0xab, 0x45, 0xfd, 0x29, 0xbb, 0xfd, 0x33, 0x00, 0x00, 0xff, 0xff, 0x57, 0xf9, - 0x26, 0xf2, 0xd9, 0x05, 0x00, 0x00, -} - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ context.Context - _ grpc.ClientConn -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// QueryClient is the client API for Query service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type QueryClient interface { - // Params defines a gRPC query method that returns the tokenfactory module's - // parameters. - Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) - // DenomAuthorityMetadata defines a gRPC query method for fetching - // DenomAuthorityMetadata for a particular denom. - DenomAuthorityMetadata(ctx context.Context, in *QueryDenomAuthorityMetadataRequest, opts ...grpc.CallOption) (*QueryDenomAuthorityMetadataResponse, error) - // DenomsFromCreator defines a gRPC query method for fetching all - // denominations created by a specific admin/creator. - DenomsFromCreator(ctx context.Context, in *QueryDenomsFromCreatorRequest, opts ...grpc.CallOption) (*QueryDenomsFromCreatorResponse, error) -} - -type queryClient struct { - cc grpc1.ClientConn -} - -func NewQueryClient(cc grpc1.ClientConn) QueryClient { - return &queryClient{cc} -} - -func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { - out := new(QueryParamsResponse) - err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Query/Params", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *queryClient) DenomAuthorityMetadata(ctx context.Context, in *QueryDenomAuthorityMetadataRequest, opts ...grpc.CallOption) (*QueryDenomAuthorityMetadataResponse, error) { - out := new(QueryDenomAuthorityMetadataResponse) - err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Query/DenomAuthorityMetadata", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *queryClient) DenomsFromCreator(ctx context.Context, in *QueryDenomsFromCreatorRequest, opts ...grpc.CallOption) (*QueryDenomsFromCreatorResponse, error) { - out := new(QueryDenomsFromCreatorResponse) - err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Query/DenomsFromCreator", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// QueryServer is the server API for Query service. -type QueryServer interface { - // Params defines a gRPC query method that returns the tokenfactory module's - // parameters. - Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) - // DenomAuthorityMetadata defines a gRPC query method for fetching - // DenomAuthorityMetadata for a particular denom. - DenomAuthorityMetadata(context.Context, *QueryDenomAuthorityMetadataRequest) (*QueryDenomAuthorityMetadataResponse, error) - // DenomsFromCreator defines a gRPC query method for fetching all - // denominations created by a specific admin/creator. - DenomsFromCreator(context.Context, *QueryDenomsFromCreatorRequest) (*QueryDenomsFromCreatorResponse, error) -} - -// UnimplementedQueryServer can be embedded to have forward compatible implementations. -type UnimplementedQueryServer struct{} - -func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") -} - -func (*UnimplementedQueryServer) DenomAuthorityMetadata(ctx context.Context, req *QueryDenomAuthorityMetadataRequest) (*QueryDenomAuthorityMetadataResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DenomAuthorityMetadata not implemented") -} - -func (*UnimplementedQueryServer) DenomsFromCreator(ctx context.Context, req *QueryDenomsFromCreatorRequest) (*QueryDenomsFromCreatorResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DenomsFromCreator not implemented") -} - -func RegisterQueryServer(s grpc1.Server, srv QueryServer) { - s.RegisterService(&_Query_serviceDesc, srv) -} - -func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryParamsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).Params(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/osmosis.tokenfactory.v1beta1.Query/Params", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Query_DenomAuthorityMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryDenomAuthorityMetadataRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).DenomAuthorityMetadata(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/osmosis.tokenfactory.v1beta1.Query/DenomAuthorityMetadata", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).DenomAuthorityMetadata(ctx, req.(*QueryDenomAuthorityMetadataRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Query_DenomsFromCreator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryDenomsFromCreatorRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).DenomsFromCreator(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/osmosis.tokenfactory.v1beta1.Query/DenomsFromCreator", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).DenomsFromCreator(ctx, req.(*QueryDenomsFromCreatorRequest)) - } - return interceptor(ctx, in, info, handler) -} - -var _Query_serviceDesc = grpc.ServiceDesc{ - ServiceName: "osmosis.tokenfactory.v1beta1.Query", - HandlerType: (*QueryServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Params", - Handler: _Query_Params_Handler, - }, - { - MethodName: "DenomAuthorityMetadata", - Handler: _Query_DenomAuthorityMetadata_Handler, - }, - { - MethodName: "DenomsFromCreator", - Handler: _Query_DenomsFromCreator_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "cosmwasm/tokenfactory/v1beta1/query.proto", -} - -func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *QueryDenomAuthorityMetadataRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryDenomAuthorityMetadataRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryDenomAuthorityMetadataRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Denom) > 0 { - i -= len(m.Denom) - copy(dAtA[i:], m.Denom) - i = encodeVarintQuery(dAtA, i, uint64(len(m.Denom))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *QueryDenomAuthorityMetadataResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryDenomAuthorityMetadataResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryDenomAuthorityMetadataResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.AuthorityMetadata.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - -func (m *QueryDenomsFromCreatorRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryDenomsFromCreatorRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryDenomsFromCreatorRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Creator) > 0 { - i -= len(m.Creator) - copy(dAtA[i:], m.Creator) - i = encodeVarintQuery(dAtA, i, uint64(len(m.Creator))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *QueryDenomsFromCreatorResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *QueryDenomsFromCreatorResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryDenomsFromCreatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Denoms) > 0 { - for iNdEx := len(m.Denoms) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Denoms[iNdEx]) - copy(dAtA[i:], m.Denoms[iNdEx]) - i = encodeVarintQuery(dAtA, i, uint64(len(m.Denoms[iNdEx]))) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { - offset -= sovQuery(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} - -func (m *QueryParamsRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *QueryParamsResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Params.Size() - n += 1 + l + sovQuery(uint64(l)) - return n -} - -func (m *QueryDenomAuthorityMetadataRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Denom) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - return n -} - -func (m *QueryDenomAuthorityMetadataResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.AuthorityMetadata.Size() - n += 1 + l + sovQuery(uint64(l)) - return n -} - -func (m *QueryDenomsFromCreatorRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Creator) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - return n -} - -func (m *QueryDenomsFromCreatorResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Denoms) > 0 { - for _, s := range m.Denoms { - l = len(s) - n += 1 + l + sovQuery(uint64(l)) - } - } - return n -} - -func sovQuery(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} - -func sozQuery(x uint64) (n int) { - return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} - -func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *QueryDenomAuthorityMetadataRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryDenomAuthorityMetadataRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryDenomAuthorityMetadataRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Denom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *QueryDenomAuthorityMetadataResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryDenomAuthorityMetadataResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryDenomAuthorityMetadataResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AuthorityMetadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.AuthorityMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *QueryDenomsFromCreatorRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryDenomsFromCreatorRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryDenomsFromCreatorRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Creator = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *QueryDenomsFromCreatorResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: QueryDenomsFromCreatorResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryDenomsFromCreatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Denoms", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Denoms = append(m.Denoms, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func skipQuery(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowQuery - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowQuery - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowQuery - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthQuery - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupQuery - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthQuery - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") -) diff --git a/x/tokenfactory/types/query.pb.gw.go b/x/tokenfactory/types/query.pb.gw.go deleted file mode 100644 index af07c01..0000000 --- a/x/tokenfactory/types/query.pb.gw.go +++ /dev/null @@ -1,343 +0,0 @@ -// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. -// source: cosmwasm/tokenfactory/v1beta1/query.proto - -/* -Package types is a reverse proxy. - -It translates gRPC into RESTful JSON APIs. -*/ -package types - -import ( - "context" - "io" - "net/http" - - "github.com/golang/protobuf/descriptor" - "github.com/golang/protobuf/proto" - "github.com/grpc-ecosystem/grpc-gateway/runtime" - "github.com/grpc-ecosystem/grpc-gateway/utilities" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/grpclog" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -// Suppress "imported and not used" errors -var ( - _ codes.Code - _ io.Reader - _ status.Status - _ = runtime.String - _ = utilities.NewDoubleArray - _ = descriptor.ForMessage - _ = metadata.Join -) - -func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryParamsRequest - var metadata runtime.ServerMetadata - - msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err -} - -func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryParamsRequest - var metadata runtime.ServerMetadata - - msg, err := server.Params(ctx, &protoReq) - return msg, metadata, err -} - -func request_Query_DenomAuthorityMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryDenomAuthorityMetadataRequest - var metadata runtime.ServerMetadata - - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["denom"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") - } - - protoReq.Denom, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) - } - - msg, err := client.DenomAuthorityMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err -} - -func local_request_Query_DenomAuthorityMetadata_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryDenomAuthorityMetadataRequest - var metadata runtime.ServerMetadata - - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["denom"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") - } - - protoReq.Denom, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) - } - - msg, err := server.DenomAuthorityMetadata(ctx, &protoReq) - return msg, metadata, err -} - -func request_Query_DenomsFromCreator_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryDenomsFromCreatorRequest - var metadata runtime.ServerMetadata - - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["creator"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "creator") - } - - protoReq.Creator, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "creator", err) - } - - msg, err := client.DenomsFromCreator(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err -} - -func local_request_Query_DenomsFromCreator_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryDenomsFromCreatorRequest - var metadata runtime.ServerMetadata - - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["creator"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "creator") - } - - protoReq.Creator, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "creator", err) - } - - msg, err := server.DenomsFromCreator(ctx, &protoReq) - return msg, metadata, err -} - -// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". -// UnaryRPC :call QueryServer directly. -// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. -// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. -func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { - mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - }) - - mux.Handle("GET", pattern_Query_DenomAuthorityMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Query_DenomAuthorityMetadata_0(rctx, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_DenomAuthorityMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - }) - - mux.Handle("GET", pattern_Query_DenomsFromCreator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - var stream runtime.ServerTransportStream - ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := local_request_Query_DenomsFromCreator_0(rctx, inboundMarshaler, server, req, pathParams) - md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_DenomsFromCreator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - }) - - return nil -} - -// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but -// automatically dials to "endpoint" and closes the connection when "ctx" gets done. -func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { - conn, err := grpc.Dial(endpoint, opts...) - if err != nil { - return err - } - defer func() { - if err != nil { - if cerr := conn.Close(); cerr != nil { - grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) - } - return - } - go func() { - <-ctx.Done() - if cerr := conn.Close(); cerr != nil { - grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) - } - }() - }() - - return RegisterQueryHandler(ctx, mux, conn) -} - -// RegisterQueryHandler registers the http handlers for service Query to "mux". -// The handlers forward requests to the grpc endpoint over "conn". -func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { - return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) -} - -// RegisterQueryHandlerClient registers the http handlers for service Query -// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". -// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" -// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in -// "QueryClient" to call the correct interceptors. -func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { - mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - }) - - mux.Handle("GET", pattern_Query_DenomAuthorityMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Query_DenomAuthorityMetadata_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_DenomAuthorityMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - }) - - mux.Handle("GET", pattern_Query_DenomsFromCreator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(req.Context()) - defer cancel() - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_Query_DenomsFromCreator_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_Query_DenomsFromCreator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - }) - - return nil -} - -var ( - pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"osmosis", "tokenfactory", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false))) - - pattern_Query_DenomAuthorityMetadata_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"osmosis", "tokenfactory", "v1beta1", "denoms", "denom", "authority_metadata"}, "", runtime.AssumeColonVerbOpt(false))) - - pattern_Query_DenomsFromCreator_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"osmosis", "tokenfactory", "v1beta1", "denoms_from_creator", "creator"}, "", runtime.AssumeColonVerbOpt(false))) -) - -var ( - forward_Query_Params_0 = runtime.ForwardResponseMessage - - forward_Query_DenomAuthorityMetadata_0 = runtime.ForwardResponseMessage - - forward_Query_DenomsFromCreator_0 = runtime.ForwardResponseMessage -) diff --git a/x/tokenfactory/types/tx.pb.go b/x/tokenfactory/types/tx.pb.go deleted file mode 100644 index 8aafe6e..0000000 --- a/x/tokenfactory/types/tx.pb.go +++ /dev/null @@ -1,2329 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: cosmwasm/tokenfactory/v1beta1/tx.proto - -package types - -import ( - context "context" - fmt "fmt" - io "io" - math "math" - math_bits "math/bits" - - types "github.com/cosmos/cosmos-sdk/types" - types1 "github.com/cosmos/cosmos-sdk/x/bank/types" - _ "github.com/cosmos/gogoproto/gogoproto" - grpc1 "github.com/cosmos/gogoproto/grpc" - proto "github.com/cosmos/gogoproto/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = proto.Marshal - _ = fmt.Errorf - _ = math.Inf -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// MsgCreateDenom defines the message structure for the CreateDenom gRPC service -// method. It allows an account to create a new denom. It requires a sender -// address and a sub denomination. The (sender_address, sub_denomination) tuple -// must be unique and cannot be re-used. -// -// The resulting denom created is defined as -// . The resulting denom's admin is -// originally set to be the creator, but this can be changed later. The token -// denom does not indicate the current admin. -type MsgCreateDenom struct { - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` - // subdenom can be up to 44 "alphanumeric" characters long. - Subdenom string `protobuf:"bytes,2,opt,name=subdenom,proto3" json:"subdenom,omitempty" yaml:"subdenom"` -} - -func (m *MsgCreateDenom) Reset() { *m = MsgCreateDenom{} } -func (m *MsgCreateDenom) String() string { return proto.CompactTextString(m) } -func (*MsgCreateDenom) ProtoMessage() {} -func (*MsgCreateDenom) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{0} -} - -func (m *MsgCreateDenom) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgCreateDenom) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgCreateDenom.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgCreateDenom) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgCreateDenom.Merge(m, src) -} - -func (m *MsgCreateDenom) XXX_Size() int { - return m.Size() -} - -func (m *MsgCreateDenom) XXX_DiscardUnknown() { - xxx_messageInfo_MsgCreateDenom.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgCreateDenom proto.InternalMessageInfo - -func (m *MsgCreateDenom) GetSender() string { - if m != nil { - return m.Sender - } - return "" -} - -func (m *MsgCreateDenom) GetSubdenom() string { - if m != nil { - return m.Subdenom - } - return "" -} - -// MsgCreateDenomResponse is the return value of MsgCreateDenom -// It returns the full string of the newly created denom. -type MsgCreateDenomResponse struct { - NewTokenDenom string `protobuf:"bytes,1,opt,name=new_token_denom,json=newTokenDenom,proto3" json:"new_token_denom,omitempty" yaml:"new_token_denom"` -} - -func (m *MsgCreateDenomResponse) Reset() { *m = MsgCreateDenomResponse{} } -func (m *MsgCreateDenomResponse) String() string { return proto.CompactTextString(m) } -func (*MsgCreateDenomResponse) ProtoMessage() {} -func (*MsgCreateDenomResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{1} -} - -func (m *MsgCreateDenomResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgCreateDenomResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgCreateDenomResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgCreateDenomResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgCreateDenomResponse.Merge(m, src) -} - -func (m *MsgCreateDenomResponse) XXX_Size() int { - return m.Size() -} - -func (m *MsgCreateDenomResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgCreateDenomResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgCreateDenomResponse proto.InternalMessageInfo - -func (m *MsgCreateDenomResponse) GetNewTokenDenom() string { - if m != nil { - return m.NewTokenDenom - } - return "" -} - -// MsgMint is the sdk.Msg type for allowing an admin account to mint -// more of a token. For now, we only support minting to the sender account -type MsgMint struct { - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` - Amount types.Coin `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount" yaml:"amount"` -} - -func (m *MsgMint) Reset() { *m = MsgMint{} } -func (m *MsgMint) String() string { return proto.CompactTextString(m) } -func (*MsgMint) ProtoMessage() {} -func (*MsgMint) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{2} -} - -func (m *MsgMint) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgMint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgMint.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgMint) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgMint.Merge(m, src) -} - -func (m *MsgMint) XXX_Size() int { - return m.Size() -} - -func (m *MsgMint) XXX_DiscardUnknown() { - xxx_messageInfo_MsgMint.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgMint proto.InternalMessageInfo - -func (m *MsgMint) GetSender() string { - if m != nil { - return m.Sender - } - return "" -} - -func (m *MsgMint) GetAmount() types.Coin { - if m != nil { - return m.Amount - } - return types.Coin{} -} - -// MsgMintResponse defines the response structure for an executed -// MsgMint message. -type MsgMintResponse struct{} - -func (m *MsgMintResponse) Reset() { *m = MsgMintResponse{} } -func (m *MsgMintResponse) String() string { return proto.CompactTextString(m) } -func (*MsgMintResponse) ProtoMessage() {} -func (*MsgMintResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{3} -} - -func (m *MsgMintResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgMintResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgMintResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgMintResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgMintResponse.Merge(m, src) -} - -func (m *MsgMintResponse) XXX_Size() int { - return m.Size() -} - -func (m *MsgMintResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgMintResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgMintResponse proto.InternalMessageInfo - -// MsgBurn is the sdk.Msg type for allowing an admin account to burn -// a token. For now, we only support burning from the sender account. -type MsgBurn struct { - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` - Amount types.Coin `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount" yaml:"amount"` -} - -func (m *MsgBurn) Reset() { *m = MsgBurn{} } -func (m *MsgBurn) String() string { return proto.CompactTextString(m) } -func (*MsgBurn) ProtoMessage() {} -func (*MsgBurn) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{4} -} - -func (m *MsgBurn) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgBurn) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgBurn.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgBurn) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgBurn.Merge(m, src) -} - -func (m *MsgBurn) XXX_Size() int { - return m.Size() -} - -func (m *MsgBurn) XXX_DiscardUnknown() { - xxx_messageInfo_MsgBurn.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgBurn proto.InternalMessageInfo - -func (m *MsgBurn) GetSender() string { - if m != nil { - return m.Sender - } - return "" -} - -func (m *MsgBurn) GetAmount() types.Coin { - if m != nil { - return m.Amount - } - return types.Coin{} -} - -// MsgBurnResponse defines the response structure for an executed -// MsgBurn message. -type MsgBurnResponse struct{} - -func (m *MsgBurnResponse) Reset() { *m = MsgBurnResponse{} } -func (m *MsgBurnResponse) String() string { return proto.CompactTextString(m) } -func (*MsgBurnResponse) ProtoMessage() {} -func (*MsgBurnResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{5} -} - -func (m *MsgBurnResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgBurnResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgBurnResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgBurnResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgBurnResponse.Merge(m, src) -} - -func (m *MsgBurnResponse) XXX_Size() int { - return m.Size() -} - -func (m *MsgBurnResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgBurnResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgBurnResponse proto.InternalMessageInfo - -// MsgChangeAdmin is the sdk.Msg type for allowing an admin account to reassign -// adminship of a denom to a new account -type MsgChangeAdmin struct { - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` - Denom string `protobuf:"bytes,2,opt,name=denom,proto3" json:"denom,omitempty" yaml:"denom"` - NewAdmin string `protobuf:"bytes,3,opt,name=new_admin,json=newAdmin,proto3" json:"new_admin,omitempty" yaml:"new_admin"` -} - -func (m *MsgChangeAdmin) Reset() { *m = MsgChangeAdmin{} } -func (m *MsgChangeAdmin) String() string { return proto.CompactTextString(m) } -func (*MsgChangeAdmin) ProtoMessage() {} -func (*MsgChangeAdmin) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{6} -} - -func (m *MsgChangeAdmin) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgChangeAdmin) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgChangeAdmin.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgChangeAdmin) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgChangeAdmin.Merge(m, src) -} - -func (m *MsgChangeAdmin) XXX_Size() int { - return m.Size() -} - -func (m *MsgChangeAdmin) XXX_DiscardUnknown() { - xxx_messageInfo_MsgChangeAdmin.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgChangeAdmin proto.InternalMessageInfo - -func (m *MsgChangeAdmin) GetSender() string { - if m != nil { - return m.Sender - } - return "" -} - -func (m *MsgChangeAdmin) GetDenom() string { - if m != nil { - return m.Denom - } - return "" -} - -func (m *MsgChangeAdmin) GetNewAdmin() string { - if m != nil { - return m.NewAdmin - } - return "" -} - -// MsgChangeAdminResponse defines the response structure for an executed -// MsgChangeAdmin message. -type MsgChangeAdminResponse struct{} - -func (m *MsgChangeAdminResponse) Reset() { *m = MsgChangeAdminResponse{} } -func (m *MsgChangeAdminResponse) String() string { return proto.CompactTextString(m) } -func (*MsgChangeAdminResponse) ProtoMessage() {} -func (*MsgChangeAdminResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{7} -} - -func (m *MsgChangeAdminResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgChangeAdminResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgChangeAdminResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgChangeAdminResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgChangeAdminResponse.Merge(m, src) -} - -func (m *MsgChangeAdminResponse) XXX_Size() int { - return m.Size() -} - -func (m *MsgChangeAdminResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgChangeAdminResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgChangeAdminResponse proto.InternalMessageInfo - -// MsgSetDenomMetadata is the sdk.Msg type for allowing an admin account to set -// the denom's bank metadata. -type MsgSetDenomMetadata struct { - Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` - Metadata types1.Metadata `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata" yaml:"metadata"` -} - -func (m *MsgSetDenomMetadata) Reset() { *m = MsgSetDenomMetadata{} } -func (m *MsgSetDenomMetadata) String() string { return proto.CompactTextString(m) } -func (*MsgSetDenomMetadata) ProtoMessage() {} -func (*MsgSetDenomMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{8} -} - -func (m *MsgSetDenomMetadata) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgSetDenomMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgSetDenomMetadata.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgSetDenomMetadata) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgSetDenomMetadata.Merge(m, src) -} - -func (m *MsgSetDenomMetadata) XXX_Size() int { - return m.Size() -} - -func (m *MsgSetDenomMetadata) XXX_DiscardUnknown() { - xxx_messageInfo_MsgSetDenomMetadata.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgSetDenomMetadata proto.InternalMessageInfo - -func (m *MsgSetDenomMetadata) GetSender() string { - if m != nil { - return m.Sender - } - return "" -} - -func (m *MsgSetDenomMetadata) GetMetadata() types1.Metadata { - if m != nil { - return m.Metadata - } - return types1.Metadata{} -} - -// MsgSetDenomMetadataResponse defines the response structure for an executed -// MsgSetDenomMetadata message. -type MsgSetDenomMetadataResponse struct{} - -func (m *MsgSetDenomMetadataResponse) Reset() { *m = MsgSetDenomMetadataResponse{} } -func (m *MsgSetDenomMetadataResponse) String() string { return proto.CompactTextString(m) } -func (*MsgSetDenomMetadataResponse) ProtoMessage() {} -func (*MsgSetDenomMetadataResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_345508fcea0bfc02, []int{9} -} - -func (m *MsgSetDenomMetadataResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} - -func (m *MsgSetDenomMetadataResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_MsgSetDenomMetadataResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} - -func (m *MsgSetDenomMetadataResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgSetDenomMetadataResponse.Merge(m, src) -} - -func (m *MsgSetDenomMetadataResponse) XXX_Size() int { - return m.Size() -} - -func (m *MsgSetDenomMetadataResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgSetDenomMetadataResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_MsgSetDenomMetadataResponse proto.InternalMessageInfo - -func init() { - proto.RegisterType((*MsgCreateDenom)(nil), "osmosis.tokenfactory.v1beta1.MsgCreateDenom") - proto.RegisterType((*MsgCreateDenomResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgCreateDenomResponse") - proto.RegisterType((*MsgMint)(nil), "osmosis.tokenfactory.v1beta1.MsgMint") - proto.RegisterType((*MsgMintResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgMintResponse") - proto.RegisterType((*MsgBurn)(nil), "osmosis.tokenfactory.v1beta1.MsgBurn") - proto.RegisterType((*MsgBurnResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgBurnResponse") - proto.RegisterType((*MsgChangeAdmin)(nil), "osmosis.tokenfactory.v1beta1.MsgChangeAdmin") - proto.RegisterType((*MsgChangeAdminResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgChangeAdminResponse") - proto.RegisterType((*MsgSetDenomMetadata)(nil), "osmosis.tokenfactory.v1beta1.MsgSetDenomMetadata") - proto.RegisterType((*MsgSetDenomMetadataResponse)(nil), "osmosis.tokenfactory.v1beta1.MsgSetDenomMetadataResponse") -} - -func init() { - proto.RegisterFile("cosmwasm/tokenfactory/v1beta1/tx.proto", fileDescriptor_345508fcea0bfc02) -} - -var fileDescriptor_345508fcea0bfc02 = []byte{ - // 600 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x55, 0x41, 0x6e, 0xd3, 0x40, - 0x14, 0x8d, 0x69, 0x29, 0xe9, 0x94, 0x92, 0xd4, 0x2d, 0x25, 0x18, 0x6a, 0xa3, 0x91, 0xa8, 0x40, - 0xa2, 0xb6, 0x52, 0x60, 0x01, 0x3b, 0x5c, 0x16, 0x6c, 0x2c, 0x24, 0xc3, 0x0a, 0x55, 0xaa, 0x26, - 0xce, 0xe0, 0x5a, 0xa9, 0x67, 0x82, 0x67, 0x42, 0x9a, 0x0d, 0xe2, 0x08, 0x2c, 0x10, 0x87, 0xe0, - 0x24, 0x5d, 0x76, 0xc9, 0xca, 0x42, 0xc9, 0x0d, 0x72, 0x02, 0xe4, 0x99, 0xb1, 0xe3, 0x34, 0x88, - 0x24, 0x2b, 0x76, 0x8e, 0xff, 0x7b, 0x6f, 0xde, 0xbc, 0xff, 0x7f, 0x0c, 0xf6, 0x03, 0xca, 0xe2, - 0x3e, 0x62, 0xb1, 0xc3, 0x69, 0x07, 0x93, 0x8f, 0x28, 0xe0, 0x34, 0x19, 0x38, 0x9f, 0x9b, 0x2d, - 0xcc, 0x51, 0xd3, 0xe1, 0xe7, 0x76, 0x37, 0xa1, 0x9c, 0xea, 0xf7, 0x29, 0x8b, 0x29, 0x8b, 0x98, - 0x5d, 0x86, 0xd9, 0x0a, 0x66, 0xec, 0x84, 0x34, 0xa4, 0x02, 0xe8, 0x64, 0x4f, 0x92, 0x63, 0x98, - 0x81, 0x20, 0x39, 0x2d, 0xc4, 0x70, 0xa1, 0x18, 0xd0, 0x88, 0xcc, 0xd4, 0x49, 0xa7, 0xa8, 0x67, - 0x3f, 0x64, 0x1d, 0x9e, 0x81, 0x5b, 0x1e, 0x0b, 0x8f, 0x12, 0x8c, 0x38, 0x7e, 0x8d, 0x09, 0x8d, - 0xf5, 0xc7, 0x60, 0x8d, 0x61, 0xd2, 0xc6, 0x49, 0x43, 0x7b, 0xa0, 0x3d, 0x5a, 0x77, 0xb7, 0xc6, - 0xa9, 0xb5, 0x39, 0x40, 0xf1, 0xd9, 0x4b, 0x28, 0xdf, 0x43, 0x5f, 0x01, 0x74, 0x07, 0x54, 0x59, - 0xaf, 0xd5, 0xce, 0x68, 0x8d, 0x6b, 0x02, 0xbc, 0x3d, 0x4e, 0xad, 0x9a, 0x02, 0xab, 0x0a, 0xf4, - 0x0b, 0x10, 0x3c, 0x06, 0xbb, 0xd3, 0xa7, 0xf9, 0x98, 0x75, 0x29, 0x61, 0x58, 0x77, 0x41, 0x8d, - 0xe0, 0xfe, 0x89, 0xb8, 0xf9, 0x89, 0x54, 0x94, 0xc7, 0x1b, 0xe3, 0xd4, 0xda, 0x95, 0x8a, 0x57, - 0x00, 0xd0, 0xdf, 0x24, 0xb8, 0xff, 0x3e, 0x7b, 0x21, 0xb4, 0xe0, 0x17, 0x70, 0xc3, 0x63, 0xa1, - 0x17, 0x11, 0xbe, 0xcc, 0x25, 0xde, 0x80, 0x35, 0x14, 0xd3, 0x1e, 0xe1, 0xe2, 0x0a, 0x1b, 0x87, - 0x77, 0x6d, 0x19, 0x99, 0x9d, 0x45, 0x9a, 0xa7, 0x6f, 0x1f, 0xd1, 0x88, 0xb8, 0xb7, 0x2f, 0x52, - 0xab, 0x32, 0x51, 0x92, 0x34, 0xe8, 0x2b, 0x3e, 0xdc, 0x02, 0x35, 0x75, 0x7e, 0x7e, 0x2d, 0x65, - 0xc9, 0xed, 0x25, 0xe4, 0x7f, 0x5a, 0xca, 0xce, 0x2f, 0x2c, 0xfd, 0xd0, 0x64, 0xcb, 0x4f, 0x11, - 0x09, 0xf1, 0xab, 0x76, 0x1c, 0x2d, 0x65, 0x6d, 0x1f, 0x5c, 0x2f, 0xf7, 0xbb, 0x3e, 0x4e, 0xad, - 0x9b, 0x12, 0xa9, 0x7a, 0x22, 0xcb, 0x7a, 0x13, 0xac, 0x67, 0xed, 0x42, 0x99, 0x7e, 0x63, 0x45, - 0x60, 0x77, 0xc6, 0xa9, 0x55, 0x9f, 0x74, 0x52, 0x94, 0xa0, 0x5f, 0x25, 0xb8, 0x2f, 0x5c, 0xc0, - 0x86, 0x1c, 0x8e, 0x89, 0xaf, 0xc2, 0xf2, 0x77, 0x0d, 0x6c, 0x7b, 0x2c, 0x7c, 0x87, 0xb9, 0x68, - 0xb4, 0x87, 0x39, 0x6a, 0x23, 0x8e, 0x96, 0xf1, 0xed, 0x83, 0x6a, 0xac, 0x68, 0x2a, 0xd4, 0xbd, - 0x49, 0xa8, 0xa4, 0x53, 0x84, 0x9a, 0x6b, 0xbb, 0x77, 0x54, 0xb0, 0x6a, 0x9a, 0x73, 0x32, 0xf4, - 0x0b, 0x1d, 0xb8, 0x07, 0xee, 0xfd, 0xc5, 0x55, 0xee, 0xfa, 0xf0, 0xe7, 0x2a, 0x58, 0xf1, 0x58, - 0xa8, 0x7f, 0x02, 0x1b, 0xe5, 0xfd, 0x7a, 0x62, 0xff, 0x6b, 0xcd, 0xed, 0xe9, 0xfd, 0x30, 0x9e, - 0x2d, 0x83, 0x2e, 0xb6, 0xe9, 0x18, 0xac, 0x8a, 0x35, 0x78, 0x38, 0x97, 0x9d, 0xc1, 0x8c, 0x83, - 0x85, 0x60, 0x65, 0x75, 0x31, 0xd1, 0xf3, 0xd5, 0x33, 0xd8, 0x02, 0xea, 0xe5, 0xf9, 0x14, 0x71, - 0x95, 0x66, 0x73, 0x81, 0xb8, 0x26, 0xe8, 0x45, 0xe2, 0x9a, 0x9d, 0x2f, 0xfd, 0xab, 0x06, 0xea, - 0x33, 0xc3, 0xd5, 0x9c, 0x2b, 0x75, 0x95, 0x62, 0xbc, 0x58, 0x9a, 0x92, 0x5b, 0x70, 0xdf, 0x5e, - 0x0c, 0x4d, 0xed, 0x72, 0x68, 0x6a, 0xbf, 0x87, 0xa6, 0xf6, 0x6d, 0x64, 0x56, 0x2e, 0x47, 0x66, - 0xe5, 0xd7, 0xc8, 0xac, 0x7c, 0x78, 0x1e, 0x46, 0xfc, 0xb4, 0xd7, 0xb2, 0x03, 0x1a, 0x3b, 0x1c, - 0x27, 0x5d, 0x82, 0x79, 0x9f, 0x26, 0x1d, 0xf1, 0x7c, 0x10, 0xd0, 0x04, 0x3b, 0xe7, 0xd3, 0xdf, - 0x15, 0x3e, 0xe8, 0x62, 0xd6, 0x5a, 0x13, 0xff, 0xef, 0x4f, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, - 0x50, 0xaf, 0x99, 0xd6, 0x7d, 0x06, 0x00, 0x00, -} - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ context.Context - _ grpc.ClientConn -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// MsgClient is the client API for Msg service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type MsgClient interface { - // CreateDenom Creates a denom of factory/{creator address}/{subdenom} given - // the denom creator address and the subdenom. Subdenoms can contain - // [a-zA-Z0-9./]. - CreateDenom(ctx context.Context, in *MsgCreateDenom, opts ...grpc.CallOption) (*MsgCreateDenomResponse, error) - // Mint is message type that represents a request to mint a new denom. - Mint(ctx context.Context, in *MsgMint, opts ...grpc.CallOption) (*MsgMintResponse, error) - // Burn message type that represents a request to burn (i.e., destroy) a - // denom. - Burn(ctx context.Context, in *MsgBurn, opts ...grpc.CallOption) (*MsgBurnResponse, error) - // A message type that represents a request to change the administrator of the - // denom. - ChangeAdmin(ctx context.Context, in *MsgChangeAdmin, opts ...grpc.CallOption) (*MsgChangeAdminResponse, error) - // A message type that represents a request to set metadata for a - // denomination. - SetDenomMetadata(ctx context.Context, in *MsgSetDenomMetadata, opts ...grpc.CallOption) (*MsgSetDenomMetadataResponse, error) -} - -type msgClient struct { - cc grpc1.ClientConn -} - -func NewMsgClient(cc grpc1.ClientConn) MsgClient { - return &msgClient{cc} -} - -func (c *msgClient) CreateDenom(ctx context.Context, in *MsgCreateDenom, opts ...grpc.CallOption) (*MsgCreateDenomResponse, error) { - out := new(MsgCreateDenomResponse) - err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/CreateDenom", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *msgClient) Mint(ctx context.Context, in *MsgMint, opts ...grpc.CallOption) (*MsgMintResponse, error) { - out := new(MsgMintResponse) - err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/Mint", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *msgClient) Burn(ctx context.Context, in *MsgBurn, opts ...grpc.CallOption) (*MsgBurnResponse, error) { - out := new(MsgBurnResponse) - err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/Burn", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *msgClient) ChangeAdmin(ctx context.Context, in *MsgChangeAdmin, opts ...grpc.CallOption) (*MsgChangeAdminResponse, error) { - out := new(MsgChangeAdminResponse) - err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/ChangeAdmin", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *msgClient) SetDenomMetadata(ctx context.Context, in *MsgSetDenomMetadata, opts ...grpc.CallOption) (*MsgSetDenomMetadataResponse, error) { - out := new(MsgSetDenomMetadataResponse) - err := c.cc.Invoke(ctx, "/osmosis.tokenfactory.v1beta1.Msg/SetDenomMetadata", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// MsgServer is the server API for Msg service. -type MsgServer interface { - // CreateDenom Creates a denom of factory/{creator address}/{subdenom} given - // the denom creator address and the subdenom. Subdenoms can contain - // [a-zA-Z0-9./]. - CreateDenom(context.Context, *MsgCreateDenom) (*MsgCreateDenomResponse, error) - // Mint is message type that represents a request to mint a new denom. - Mint(context.Context, *MsgMint) (*MsgMintResponse, error) - // Burn message type that represents a request to burn (i.e., destroy) a - // denom. - Burn(context.Context, *MsgBurn) (*MsgBurnResponse, error) - // A message type that represents a request to change the administrator of the - // denom. - ChangeAdmin(context.Context, *MsgChangeAdmin) (*MsgChangeAdminResponse, error) - // A message type that represents a request to set metadata for a - // denomination. - SetDenomMetadata(context.Context, *MsgSetDenomMetadata) (*MsgSetDenomMetadataResponse, error) -} - -// UnimplementedMsgServer can be embedded to have forward compatible implementations. -type UnimplementedMsgServer struct{} - -func (*UnimplementedMsgServer) CreateDenom(ctx context.Context, req *MsgCreateDenom) (*MsgCreateDenomResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateDenom not implemented") -} - -func (*UnimplementedMsgServer) Mint(ctx context.Context, req *MsgMint) (*MsgMintResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Mint not implemented") -} - -func (*UnimplementedMsgServer) Burn(ctx context.Context, req *MsgBurn) (*MsgBurnResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Burn not implemented") -} - -func (*UnimplementedMsgServer) ChangeAdmin(ctx context.Context, req *MsgChangeAdmin) (*MsgChangeAdminResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ChangeAdmin not implemented") -} - -func (*UnimplementedMsgServer) SetDenomMetadata(ctx context.Context, req *MsgSetDenomMetadata) (*MsgSetDenomMetadataResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method SetDenomMetadata not implemented") -} - -func RegisterMsgServer(s grpc1.Server, srv MsgServer) { - s.RegisterService(&_Msg_serviceDesc, srv) -} - -func _Msg_CreateDenom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgCreateDenom) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).CreateDenom(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/CreateDenom", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).CreateDenom(ctx, req.(*MsgCreateDenom)) - } - return interceptor(ctx, in, info, handler) -} - -func _Msg_Mint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgMint) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).Mint(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/Mint", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).Mint(ctx, req.(*MsgMint)) - } - return interceptor(ctx, in, info, handler) -} - -func _Msg_Burn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgBurn) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).Burn(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/Burn", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).Burn(ctx, req.(*MsgBurn)) - } - return interceptor(ctx, in, info, handler) -} - -func _Msg_ChangeAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgChangeAdmin) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).ChangeAdmin(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/ChangeAdmin", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).ChangeAdmin(ctx, req.(*MsgChangeAdmin)) - } - return interceptor(ctx, in, info, handler) -} - -func _Msg_SetDenomMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgSetDenomMetadata) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(MsgServer).SetDenomMetadata(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/osmosis.tokenfactory.v1beta1.Msg/SetDenomMetadata", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).SetDenomMetadata(ctx, req.(*MsgSetDenomMetadata)) - } - return interceptor(ctx, in, info, handler) -} - -var _Msg_serviceDesc = grpc.ServiceDesc{ - ServiceName: "osmosis.tokenfactory.v1beta1.Msg", - HandlerType: (*MsgServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "CreateDenom", - Handler: _Msg_CreateDenom_Handler, - }, - { - MethodName: "Mint", - Handler: _Msg_Mint_Handler, - }, - { - MethodName: "Burn", - Handler: _Msg_Burn_Handler, - }, - { - MethodName: "ChangeAdmin", - Handler: _Msg_ChangeAdmin_Handler, - }, - { - MethodName: "SetDenomMetadata", - Handler: _Msg_SetDenomMetadata_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "cosmwasm/tokenfactory/v1beta1/tx.proto", -} - -func (m *MsgCreateDenom) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgCreateDenom) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgCreateDenom) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Subdenom) > 0 { - i -= len(m.Subdenom) - copy(dAtA[i:], m.Subdenom) - i = encodeVarintTx(dAtA, i, uint64(len(m.Subdenom))) - i-- - dAtA[i] = 0x12 - } - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgCreateDenomResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgCreateDenomResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgCreateDenomResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.NewTokenDenom) > 0 { - i -= len(m.NewTokenDenom) - copy(dAtA[i:], m.NewTokenDenom) - i = encodeVarintTx(dAtA, i, uint64(len(m.NewTokenDenom))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgMint) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgMint) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgMint) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgMintResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgMintResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgMintResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *MsgBurn) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgBurn) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgBurn) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgBurnResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgBurnResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgBurnResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *MsgChangeAdmin) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgChangeAdmin) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgChangeAdmin) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.NewAdmin) > 0 { - i -= len(m.NewAdmin) - copy(dAtA[i:], m.NewAdmin) - i = encodeVarintTx(dAtA, i, uint64(len(m.NewAdmin))) - i-- - dAtA[i] = 0x1a - } - if len(m.Denom) > 0 { - i -= len(m.Denom) - copy(dAtA[i:], m.Denom) - i = encodeVarintTx(dAtA, i, uint64(len(m.Denom))) - i-- - dAtA[i] = 0x12 - } - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgChangeAdminResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgChangeAdminResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgChangeAdminResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func (m *MsgSetDenomMetadata) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgSetDenomMetadata) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgSetDenomMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - { - size, err := m.Metadata.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintTx(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x12 - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *MsgSetDenomMetadataResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *MsgSetDenomMetadataResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *MsgSetDenomMetadataResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - return len(dAtA) - i, nil -} - -func encodeVarintTx(dAtA []byte, offset int, v uint64) int { - offset -= sovTx(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} - -func (m *MsgCreateDenom) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.Subdenom) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgCreateDenomResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.NewTokenDenom) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgMint) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = m.Amount.Size() - n += 1 + l + sovTx(uint64(l)) - return n -} - -func (m *MsgMintResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *MsgBurn) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = m.Amount.Size() - n += 1 + l + sovTx(uint64(l)) - return n -} - -func (m *MsgBurnResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *MsgChangeAdmin) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.Denom) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.NewAdmin) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - return n -} - -func (m *MsgChangeAdminResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func (m *MsgSetDenomMetadata) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = m.Metadata.Size() - n += 1 + l + sovTx(uint64(l)) - return n -} - -func (m *MsgSetDenomMetadataResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n -} - -func sovTx(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} - -func sozTx(x uint64) (n int) { - return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} - -func (m *MsgCreateDenom) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgCreateDenom: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateDenom: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Sender = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Subdenom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Subdenom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgCreateDenomResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgCreateDenomResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgCreateDenomResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NewTokenDenom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NewTokenDenom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgMint) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgMint: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgMint: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Sender = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgMintResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgMintResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgMintResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgBurn) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgBurn: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgBurn: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Sender = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgBurnResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgBurnResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgBurnResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgChangeAdmin) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgChangeAdmin: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgChangeAdmin: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Sender = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Denom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NewAdmin", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NewAdmin = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgChangeAdminResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgChangeAdminResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgChangeAdminResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgSetDenomMetadata) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgSetDenomMetadata: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgSetDenomMetadata: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Sender = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func (m *MsgSetDenomMetadataResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MsgSetDenomMetadataResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MsgSetDenomMetadataResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -func skipTx(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTx - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTx - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowTx - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthTx - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupTx - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthTx - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") -) From 5ad1a45ae827df01e7b7bc6dde767e1cb85cf56e Mon Sep 17 00:00:00 2001 From: hard-nett Date: Wed, 28 Jun 2023 05:18:15 -0400 Subject: [PATCH 04/14] remove forked cosmwasm module & proto --- proto/cosmwasm/wasm/v1/authz.proto | 131 - proto/cosmwasm/wasm/v1/genesis.proto | 56 - proto/cosmwasm/wasm/v1/ibc.proto | 37 - proto/cosmwasm/wasm/v1/proposal.proto | 329 - proto/cosmwasm/wasm/v1/query.proto | 268 - proto/cosmwasm/wasm/v1/tx.proto | 391 - proto/cosmwasm/wasm/v1/types.proto | 148 - x/ibchooks/.cargo/config | 2 + x/ibchooks/README.md | 185 + x/ibchooks/client/cli/query.go | 76 + x/ibchooks/hooks.go | 145 + x/ibchooks/ibc_module.go | 262 + x/ibchooks/ics4_middleware.go | 78 + x/ibchooks/keeper/keeper.go | 62 + x/ibchooks/sdkmodule.go | 126 + x/ibchooks/types/errors.go | 15 + x/ibchooks/types/keys.go | 8 + x/ibchooks/wasm_hook.go | 421 + x/wasm/Governance.md | 205 - x/wasm/IBC.md | 137 - x/wasm/README.md | 219 - x/wasm/alias.go | 124 - x/wasm/client/cli/new_tx.go | 160 - x/wasm/client/cli/query.go | 675 -- x/wasm/client/cli/tx.go | 566 -- x/wasm/client/cli/tx_test.go | 59 - x/wasm/common_test.go | 35 - x/wasm/exported/exported.go | 18 - x/wasm/genesis_test.go | 99 - x/wasm/ibc.go | 357 - x/wasm/ibc_integration_test.go | 126 - x/wasm/ibc_reflect_test.go | 124 - x/wasm/ibc_test.go | 82 - x/wasm/ibctesting/README.md | 2 - x/wasm/ibctesting/chain.go | 611 -- x/wasm/ibctesting/coordinator.go | 311 - x/wasm/ibctesting/endpoint.go | 657 -- x/wasm/ibctesting/event_utils.go | 118 - x/wasm/ibctesting/faucet.go | 53 - x/wasm/ibctesting/path.go | 109 - x/wasm/ibctesting/wasm.go | 129 - x/wasm/ioutils/ioutil.go | 41 - x/wasm/ioutils/ioutil_test.go | 82 - x/wasm/ioutils/utils.go | 45 - x/wasm/ioutils/utils_test.go | 68 - x/wasm/keeper/addresses.go | 76 - x/wasm/keeper/addresses_test.go | 432 - x/wasm/keeper/ante.go | 97 - x/wasm/keeper/ante_test.go | 189 - x/wasm/keeper/api.go | 43 - x/wasm/keeper/authz_policy.go | 63 - x/wasm/keeper/authz_policy_test.go | 345 - x/wasm/keeper/bench_test.go | 102 - x/wasm/keeper/contract_keeper.go | 130 - x/wasm/keeper/contract_keeper_test.go | 169 - x/wasm/keeper/events.go | 68 - x/wasm/keeper/events_test.go | 290 - x/wasm/keeper/gas_register.go | 253 - x/wasm/keeper/gas_register_test.go | 472 -- x/wasm/keeper/genesis.go | 120 - x/wasm/keeper/genesis_test.go | 705 -- x/wasm/keeper/handler_plugin.go | 219 - x/wasm/keeper/handler_plugin_encoders.go | 395 - x/wasm/keeper/handler_plugin_encoders_test.go | 933 --- x/wasm/keeper/handler_plugin_test.go | 415 - x/wasm/keeper/ibc.go | 56 - x/wasm/keeper/ibc_test.go | 82 - x/wasm/keeper/keeper.go | 1211 --- x/wasm/keeper/keeper_cgo.go | 65 - x/wasm/keeper/keeper_no_cgo.go | 34 - x/wasm/keeper/keeper_test.go | 2418 ------ x/wasm/keeper/metrics.go | 72 - x/wasm/keeper/migrations.go | 32 - x/wasm/keeper/migrations_integration_test.go | 72 - x/wasm/keeper/msg_dispatcher.go | 223 - x/wasm/keeper/msg_dispatcher_test.go | 430 - x/wasm/keeper/msg_server.go | 365 - x/wasm/keeper/msg_server_integration_test.go | 855 -- x/wasm/keeper/options.go | 170 - x/wasm/keeper/options_test.go | 114 - x/wasm/keeper/proposal_handler.go | 342 - x/wasm/keeper/proposal_integration_test.go | 926 --- x/wasm/keeper/querier.go | 347 - x/wasm/keeper/querier_test.go | 919 --- x/wasm/keeper/query_plugins.go | 614 -- x/wasm/keeper/query_plugins_test.go | 851 -- x/wasm/keeper/recurse_test.go | 306 - x/wasm/keeper/reflect_test.go | 683 -- x/wasm/keeper/relay.go | 203 - x/wasm/keeper/relay_test.go | 711 -- x/wasm/keeper/snapshotter.go | 141 - x/wasm/keeper/snapshotter_integration_test.go | 124 - x/wasm/keeper/staking_test.go | 739 -- x/wasm/keeper/submsg_test.go | 552 -- x/wasm/keeper/test_common.go | 816 -- x/wasm/keeper/test_fuzz.go | 78 - x/wasm/keeper/testdata/broken_crc.gzip | Bin 809232 -> 0 bytes x/wasm/keeper/testdata/burner.wasm | Bin 127528 -> 0 bytes x/wasm/keeper/testdata/contracts.go | 94 - x/wasm/keeper/testdata/cyberpunk.wasm | Bin 170223 -> 0 bytes x/wasm/keeper/testdata/download_releases.sh | 23 - x/wasm/keeper/testdata/genesis.json | 219 - x/wasm/keeper/testdata/hackatom.wasm | Bin 177474 -> 0 bytes x/wasm/keeper/testdata/hackatom.wasm.gzip | Bin 64560 -> 0 bytes x/wasm/keeper/testdata/ibc_reflect.wasm | Bin 254798 -> 0 bytes x/wasm/keeper/testdata/ibc_reflect_send.wasm | Bin 269429 -> 0 bytes x/wasm/keeper/testdata/reflect.wasm | Bin 257191 -> 0 bytes x/wasm/keeper/testdata/reflect.wasm.v1_0 | Bin 262794 -> 0 bytes x/wasm/keeper/testdata/reflect_1_1.wasm | Bin 257047 -> 0 bytes x/wasm/keeper/testdata/staking.wasm | Bin 222166 -> 0 bytes x/wasm/keeper/testdata/version.txt | 1 - x/wasm/keeper/wasmtesting/extension_mocks.go | 28 - x/wasm/keeper/wasmtesting/gas_register.go | 74 - x/wasm/keeper/wasmtesting/message_router.go | 27 - x/wasm/keeper/wasmtesting/messenger.go | 38 - x/wasm/keeper/wasmtesting/mock_engine.go | 402 - x/wasm/keeper/wasmtesting/mock_keepers.go | 112 - x/wasm/keeper/wasmtesting/msg_dispatcher.go | 17 - x/wasm/keeper/wasmtesting/query_handler.go | 17 - x/wasm/keeper/wasmtesting/store.go | 26 - x/wasm/migrations/v1/store.go | 36 - x/wasm/migrations/v1/store_test.go | 71 - x/wasm/migrations/v2/store.go | 33 - x/wasm/migrations/v2/store_test.go | 44 - x/wasm/module.go | 314 - x/wasm/module_test.go | 651 -- x/wasm/relay_pingpong_test.go | 381 - x/wasm/relay_test.go | 824 -- x/wasm/simulation/genesis.go | 27 - x/wasm/simulation/operations.go | 566 -- x/wasm/simulation/proposals.go | 369 - x/wasm/testdata/escrow_0.7.wasm | Bin 91577 -> 0 bytes x/wasm/types/ante.go | 24 - x/wasm/types/authz.go | 533 -- x/wasm/types/authz.pb.go | 1870 ----- x/wasm/types/authz_test.go | 729 -- x/wasm/types/codec.go | 141 - x/wasm/types/errors.go | 150 - x/wasm/types/errors_test.go | 86 - x/wasm/types/events.go | 34 - x/wasm/types/expected_keepers.go | 118 - x/wasm/types/exported_keepers.go | 119 - x/wasm/types/feature_flag.go | 4 - x/wasm/types/genesis.go | 102 - x/wasm/types/genesis.pb.go | 1420 ---- x/wasm/types/genesis_test.go | 212 - x/wasm/types/iavl_range_test.go | 83 - x/wasm/types/ibc.pb.go | 766 -- x/wasm/types/json_matching.go | 34 - x/wasm/types/json_matching_test.go | 134 - x/wasm/types/keys.go | 131 - x/wasm/types/keys_test.go | 148 - x/wasm/types/params.go | 208 - x/wasm/types/params_legacy.go | 26 - x/wasm/types/params_test.go | 306 - x/wasm/types/proposal.go | 986 --- x/wasm/types/proposal.pb.go | 5856 -------------- x/wasm/types/proposal_test.go | 1123 --- x/wasm/types/query.pb.go | 5639 ------------- x/wasm/types/query.pb.gw.go | 1187 --- x/wasm/types/test_fixtures.go | 443 - x/wasm/types/tx.go | 637 -- x/wasm/types/tx.pb.go | 7139 ----------------- x/wasm/types/tx_test.go | 1139 --- x/wasm/types/types.go | 437 - x/wasm/types/types.pb.go | 2548 ------ x/wasm/types/types_test.go | 751 -- x/wasm/types/validation.go | 79 - x/wasm/types/wasmer_engine.go | 277 - 169 files changed, 1380 insertions(+), 66652 deletions(-) delete mode 100644 proto/cosmwasm/wasm/v1/authz.proto delete mode 100644 proto/cosmwasm/wasm/v1/genesis.proto delete mode 100644 proto/cosmwasm/wasm/v1/ibc.proto delete mode 100644 proto/cosmwasm/wasm/v1/proposal.proto delete mode 100644 proto/cosmwasm/wasm/v1/query.proto delete mode 100644 proto/cosmwasm/wasm/v1/tx.proto delete mode 100644 proto/cosmwasm/wasm/v1/types.proto create mode 100644 x/ibchooks/.cargo/config create mode 100644 x/ibchooks/README.md create mode 100644 x/ibchooks/client/cli/query.go create mode 100644 x/ibchooks/hooks.go create mode 100644 x/ibchooks/ibc_module.go create mode 100644 x/ibchooks/ics4_middleware.go create mode 100644 x/ibchooks/keeper/keeper.go create mode 100644 x/ibchooks/sdkmodule.go create mode 100644 x/ibchooks/types/errors.go create mode 100644 x/ibchooks/types/keys.go create mode 100644 x/ibchooks/wasm_hook.go delete mode 100644 x/wasm/Governance.md delete mode 100644 x/wasm/IBC.md delete mode 100644 x/wasm/README.md delete mode 100644 x/wasm/alias.go delete mode 100644 x/wasm/client/cli/new_tx.go delete mode 100644 x/wasm/client/cli/query.go delete mode 100644 x/wasm/client/cli/tx.go delete mode 100644 x/wasm/client/cli/tx_test.go delete mode 100644 x/wasm/common_test.go delete mode 100644 x/wasm/exported/exported.go delete mode 100644 x/wasm/genesis_test.go delete mode 100644 x/wasm/ibc.go delete mode 100644 x/wasm/ibc_integration_test.go delete mode 100644 x/wasm/ibc_reflect_test.go delete mode 100644 x/wasm/ibc_test.go delete mode 100644 x/wasm/ibctesting/README.md delete mode 100644 x/wasm/ibctesting/chain.go delete mode 100644 x/wasm/ibctesting/coordinator.go delete mode 100644 x/wasm/ibctesting/endpoint.go delete mode 100644 x/wasm/ibctesting/event_utils.go delete mode 100644 x/wasm/ibctesting/faucet.go delete mode 100644 x/wasm/ibctesting/path.go delete mode 100644 x/wasm/ibctesting/wasm.go delete mode 100644 x/wasm/ioutils/ioutil.go delete mode 100644 x/wasm/ioutils/ioutil_test.go delete mode 100644 x/wasm/ioutils/utils.go delete mode 100644 x/wasm/ioutils/utils_test.go delete mode 100644 x/wasm/keeper/addresses.go delete mode 100644 x/wasm/keeper/addresses_test.go delete mode 100644 x/wasm/keeper/ante.go delete mode 100644 x/wasm/keeper/ante_test.go delete mode 100644 x/wasm/keeper/api.go delete mode 100644 x/wasm/keeper/authz_policy.go delete mode 100644 x/wasm/keeper/authz_policy_test.go delete mode 100644 x/wasm/keeper/bench_test.go delete mode 100644 x/wasm/keeper/contract_keeper.go delete mode 100644 x/wasm/keeper/contract_keeper_test.go delete mode 100644 x/wasm/keeper/events.go delete mode 100644 x/wasm/keeper/events_test.go delete mode 100644 x/wasm/keeper/gas_register.go delete mode 100644 x/wasm/keeper/gas_register_test.go delete mode 100644 x/wasm/keeper/genesis.go delete mode 100644 x/wasm/keeper/genesis_test.go delete mode 100644 x/wasm/keeper/handler_plugin.go delete mode 100644 x/wasm/keeper/handler_plugin_encoders.go delete mode 100644 x/wasm/keeper/handler_plugin_encoders_test.go delete mode 100644 x/wasm/keeper/handler_plugin_test.go delete mode 100644 x/wasm/keeper/ibc.go delete mode 100644 x/wasm/keeper/ibc_test.go delete mode 100644 x/wasm/keeper/keeper.go delete mode 100644 x/wasm/keeper/keeper_cgo.go delete mode 100644 x/wasm/keeper/keeper_no_cgo.go delete mode 100644 x/wasm/keeper/keeper_test.go delete mode 100644 x/wasm/keeper/metrics.go delete mode 100644 x/wasm/keeper/migrations.go delete mode 100644 x/wasm/keeper/migrations_integration_test.go delete mode 100644 x/wasm/keeper/msg_dispatcher.go delete mode 100644 x/wasm/keeper/msg_dispatcher_test.go delete mode 100644 x/wasm/keeper/msg_server.go delete mode 100644 x/wasm/keeper/msg_server_integration_test.go delete mode 100644 x/wasm/keeper/options.go delete mode 100644 x/wasm/keeper/options_test.go delete mode 100644 x/wasm/keeper/proposal_handler.go delete mode 100644 x/wasm/keeper/proposal_integration_test.go delete mode 100644 x/wasm/keeper/querier.go delete mode 100644 x/wasm/keeper/querier_test.go delete mode 100644 x/wasm/keeper/query_plugins.go delete mode 100644 x/wasm/keeper/query_plugins_test.go delete mode 100644 x/wasm/keeper/recurse_test.go delete mode 100644 x/wasm/keeper/reflect_test.go delete mode 100644 x/wasm/keeper/relay.go delete mode 100644 x/wasm/keeper/relay_test.go delete mode 100644 x/wasm/keeper/snapshotter.go delete mode 100644 x/wasm/keeper/snapshotter_integration_test.go delete mode 100644 x/wasm/keeper/staking_test.go delete mode 100644 x/wasm/keeper/submsg_test.go delete mode 100644 x/wasm/keeper/test_common.go delete mode 100644 x/wasm/keeper/test_fuzz.go delete mode 100644 x/wasm/keeper/testdata/broken_crc.gzip delete mode 100644 x/wasm/keeper/testdata/burner.wasm delete mode 100644 x/wasm/keeper/testdata/contracts.go delete mode 100644 x/wasm/keeper/testdata/cyberpunk.wasm delete mode 100755 x/wasm/keeper/testdata/download_releases.sh delete mode 100644 x/wasm/keeper/testdata/genesis.json delete mode 100644 x/wasm/keeper/testdata/hackatom.wasm delete mode 100644 x/wasm/keeper/testdata/hackatom.wasm.gzip delete mode 100644 x/wasm/keeper/testdata/ibc_reflect.wasm delete mode 100644 x/wasm/keeper/testdata/ibc_reflect_send.wasm delete mode 100644 x/wasm/keeper/testdata/reflect.wasm delete mode 100644 x/wasm/keeper/testdata/reflect.wasm.v1_0 delete mode 100644 x/wasm/keeper/testdata/reflect_1_1.wasm delete mode 100644 x/wasm/keeper/testdata/staking.wasm delete mode 100644 x/wasm/keeper/testdata/version.txt delete mode 100644 x/wasm/keeper/wasmtesting/extension_mocks.go delete mode 100644 x/wasm/keeper/wasmtesting/gas_register.go delete mode 100644 x/wasm/keeper/wasmtesting/message_router.go delete mode 100644 x/wasm/keeper/wasmtesting/messenger.go delete mode 100644 x/wasm/keeper/wasmtesting/mock_engine.go delete mode 100644 x/wasm/keeper/wasmtesting/mock_keepers.go delete mode 100644 x/wasm/keeper/wasmtesting/msg_dispatcher.go delete mode 100644 x/wasm/keeper/wasmtesting/query_handler.go delete mode 100644 x/wasm/keeper/wasmtesting/store.go delete mode 100644 x/wasm/migrations/v1/store.go delete mode 100644 x/wasm/migrations/v1/store_test.go delete mode 100644 x/wasm/migrations/v2/store.go delete mode 100644 x/wasm/migrations/v2/store_test.go delete mode 100644 x/wasm/module.go delete mode 100644 x/wasm/module_test.go delete mode 100644 x/wasm/relay_pingpong_test.go delete mode 100644 x/wasm/relay_test.go delete mode 100644 x/wasm/simulation/genesis.go delete mode 100644 x/wasm/simulation/operations.go delete mode 100644 x/wasm/simulation/proposals.go delete mode 100644 x/wasm/testdata/escrow_0.7.wasm delete mode 100644 x/wasm/types/ante.go delete mode 100644 x/wasm/types/authz.go delete mode 100644 x/wasm/types/authz.pb.go delete mode 100644 x/wasm/types/authz_test.go delete mode 100644 x/wasm/types/codec.go delete mode 100644 x/wasm/types/errors.go delete mode 100644 x/wasm/types/errors_test.go delete mode 100644 x/wasm/types/events.go delete mode 100644 x/wasm/types/expected_keepers.go delete mode 100644 x/wasm/types/exported_keepers.go delete mode 100644 x/wasm/types/feature_flag.go delete mode 100644 x/wasm/types/genesis.go delete mode 100644 x/wasm/types/genesis.pb.go delete mode 100644 x/wasm/types/genesis_test.go delete mode 100644 x/wasm/types/iavl_range_test.go delete mode 100644 x/wasm/types/ibc.pb.go delete mode 100644 x/wasm/types/json_matching.go delete mode 100644 x/wasm/types/json_matching_test.go delete mode 100644 x/wasm/types/keys.go delete mode 100644 x/wasm/types/keys_test.go delete mode 100644 x/wasm/types/params.go delete mode 100644 x/wasm/types/params_legacy.go delete mode 100644 x/wasm/types/params_test.go delete mode 100644 x/wasm/types/proposal.go delete mode 100644 x/wasm/types/proposal.pb.go delete mode 100644 x/wasm/types/proposal_test.go delete mode 100644 x/wasm/types/query.pb.go delete mode 100644 x/wasm/types/query.pb.gw.go delete mode 100644 x/wasm/types/test_fixtures.go delete mode 100644 x/wasm/types/tx.go delete mode 100644 x/wasm/types/tx.pb.go delete mode 100644 x/wasm/types/tx_test.go delete mode 100644 x/wasm/types/types.go delete mode 100644 x/wasm/types/types.pb.go delete mode 100644 x/wasm/types/types_test.go delete mode 100644 x/wasm/types/validation.go delete mode 100644 x/wasm/types/wasmer_engine.go diff --git a/proto/cosmwasm/wasm/v1/authz.proto b/proto/cosmwasm/wasm/v1/authz.proto deleted file mode 100644 index ce10a3d..0000000 --- a/proto/cosmwasm/wasm/v1/authz.proto +++ /dev/null @@ -1,131 +0,0 @@ -syntax = "proto3"; -package cosmwasm.wasm.v1; - -import "gogoproto/gogo.proto"; -import "cosmos_proto/cosmos.proto"; -import "cosmos/base/v1beta1/coin.proto"; -import "google/protobuf/any.proto"; -import "amino/amino.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/wasm/types"; -option (gogoproto.goproto_getters_all) = false; - -// ContractExecutionAuthorization defines authorization for wasm execute. -// Since: wasmd 0.30 -message ContractExecutionAuthorization { - option (amino.name) = "wasm/ContractExecutionAuthorization"; - option (cosmos_proto.implements_interface) = - "cosmos.authz.v1beta1.Authorization"; - - // Grants for contract executions - repeated ContractGrant grants = 1 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// ContractMigrationAuthorization defines authorization for wasm contract -// migration. Since: wasmd 0.30 -message ContractMigrationAuthorization { - option (amino.name) = "wasm/ContractMigrationAuthorization"; - option (cosmos_proto.implements_interface) = - "cosmos.authz.v1beta1.Authorization"; - - // Grants for contract migrations - repeated ContractGrant grants = 1 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// ContractGrant a granted permission for a single contract -// Since: wasmd 0.30 -message ContractGrant { - // Contract is the bech32 address of the smart contract - string contract = 1; - - // Limit defines execution limits that are enforced and updated when the grant - // is applied. When the limit lapsed the grant is removed. - google.protobuf.Any limit = 2 [ (cosmos_proto.accepts_interface) = - "cosmwasm.wasm.v1.ContractAuthzLimitX" ]; - - // Filter define more fine-grained control on the message payload passed - // to the contract in the operation. When no filter applies on execution, the - // operation is prohibited. - google.protobuf.Any filter = 3 - [ (cosmos_proto.accepts_interface) = - "cosmwasm.wasm.v1.ContractAuthzFilterX" ]; -} - -// MaxCallsLimit limited number of calls to the contract. No funds transferable. -// Since: wasmd 0.30 -message MaxCallsLimit { - option (amino.name) = "wasm/MaxCallsLimit"; - option (cosmos_proto.implements_interface) = - "cosmwasm.wasm.v1.ContractAuthzLimitX"; - - // Remaining number that is decremented on each execution - uint64 remaining = 1; -} - -// MaxFundsLimit defines the maximal amounts that can be sent to the contract. -// Since: wasmd 0.30 -message MaxFundsLimit { - option (amino.name) = "wasm/MaxFundsLimit"; - option (cosmos_proto.implements_interface) = - "cosmwasm.wasm.v1.ContractAuthzLimitX"; - - // Amounts is the maximal amount of tokens transferable to the contract. - repeated cosmos.base.v1beta1.Coin amounts = 1 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; -} - -// CombinedLimit defines the maximal amounts that can be sent to a contract and -// the maximal number of calls executable. Both need to remain >0 to be valid. -// Since: wasmd 0.30 -message CombinedLimit { - option (amino.name) = "wasm/CombinedLimit"; - option (cosmos_proto.implements_interface) = - "cosmwasm.wasm.v1.ContractAuthzLimitX"; - - // Remaining number that is decremented on each execution - uint64 calls_remaining = 1; - // Amounts is the maximal amount of tokens transferable to the contract. - repeated cosmos.base.v1beta1.Coin amounts = 2 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; -} - -// AllowAllMessagesFilter is a wildcard to allow any type of contract payload -// message. -// Since: wasmd 0.30 -message AllowAllMessagesFilter { - option (amino.name) = "wasm/AllowAllMessagesFilter"; - option (cosmos_proto.implements_interface) = - "cosmwasm.wasm.v1.ContractAuthzFilterX"; -} - -// AcceptedMessageKeysFilter accept only the specific contract message keys in -// the json object to be executed. -// Since: wasmd 0.30 -message AcceptedMessageKeysFilter { - option (amino.name) = "wasm/AcceptedMessageKeysFilter"; - option (cosmos_proto.implements_interface) = - "cosmwasm.wasm.v1.ContractAuthzFilterX"; - - // Messages is the list of unique keys - repeated string keys = 1; -} - -// AcceptedMessagesFilter accept only the specific raw contract messages to be -// executed. -// Since: wasmd 0.30 -message AcceptedMessagesFilter { - option (amino.name) = "wasm/AcceptedMessagesFilter"; - option (cosmos_proto.implements_interface) = - "cosmwasm.wasm.v1.ContractAuthzFilterX"; - - // Messages is the list of raw contract messages - repeated bytes messages = 1 [ (gogoproto.casttype) = "RawContractMessage" ]; -} \ No newline at end of file diff --git a/proto/cosmwasm/wasm/v1/genesis.proto b/proto/cosmwasm/wasm/v1/genesis.proto deleted file mode 100644 index 898ccee..0000000 --- a/proto/cosmwasm/wasm/v1/genesis.proto +++ /dev/null @@ -1,56 +0,0 @@ -syntax = "proto3"; -package cosmwasm.wasm.v1; - -import "gogoproto/gogo.proto"; -import "cosmwasm/wasm/v1/types.proto"; -import "amino/amino.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/wasm/types"; - -// GenesisState - genesis state of x/wasm -message GenesisState { - Params params = 1 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; - repeated Code codes = 2 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.jsontag) = "codes,omitempty" - ]; - repeated Contract contracts = 3 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.jsontag) = "contracts,omitempty" - ]; - repeated Sequence sequences = 4 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.jsontag) = "sequences,omitempty" - ]; -} - -// Code struct encompasses CodeInfo and CodeBytes -message Code { - uint64 code_id = 1 [ (gogoproto.customname) = "CodeID" ]; - CodeInfo code_info = 2 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; - bytes code_bytes = 3; - // Pinned to wasmvm cache - bool pinned = 4; -} - -// Contract struct encompasses ContractAddress, ContractInfo, and ContractState -message Contract { - string contract_address = 1; - ContractInfo contract_info = 2 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; - repeated Model contract_state = 3 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; - repeated ContractCodeHistoryEntry contract_code_history = 4 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// Sequence key and value of an id generation counter -message Sequence { - bytes id_key = 1 [ (gogoproto.customname) = "IDKey" ]; - uint64 value = 2; -} \ No newline at end of file diff --git a/proto/cosmwasm/wasm/v1/ibc.proto b/proto/cosmwasm/wasm/v1/ibc.proto deleted file mode 100644 index 196387b..0000000 --- a/proto/cosmwasm/wasm/v1/ibc.proto +++ /dev/null @@ -1,37 +0,0 @@ -syntax = "proto3"; -package cosmwasm.wasm.v1; - -import "gogoproto/gogo.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/wasm/types"; -option (gogoproto.goproto_getters_all) = false; - -// MsgIBCSend -message MsgIBCSend { - // the channel by which the packet will be sent - string channel = 2 [ (gogoproto.moretags) = "yaml:\"source_channel\"" ]; - - // Timeout height relative to the current block height. - // The timeout is disabled when set to 0. - uint64 timeout_height = 4 - [ (gogoproto.moretags) = "yaml:\"timeout_height\"" ]; - // Timeout timestamp (in nanoseconds) relative to the current block timestamp. - // The timeout is disabled when set to 0. - uint64 timeout_timestamp = 5 - [ (gogoproto.moretags) = "yaml:\"timeout_timestamp\"" ]; - - // Data is the payload to transfer. We must not make assumption what format or - // content is in here. - bytes data = 6; -} - -// MsgIBCSendResponse -message MsgIBCSendResponse { - // Sequence number of the IBC packet sent - uint64 sequence = 1; -} - -// MsgIBCCloseChannel port and channel need to be owned by the contract -message MsgIBCCloseChannel { - string channel = 2 [ (gogoproto.moretags) = "yaml:\"source_channel\"" ]; -} \ No newline at end of file diff --git a/proto/cosmwasm/wasm/v1/proposal.proto b/proto/cosmwasm/wasm/v1/proposal.proto deleted file mode 100644 index f411345..0000000 --- a/proto/cosmwasm/wasm/v1/proposal.proto +++ /dev/null @@ -1,329 +0,0 @@ -syntax = "proto3"; -package cosmwasm.wasm.v1; - -import "gogoproto/gogo.proto"; -import "cosmos_proto/cosmos.proto"; -import "cosmos/base/v1beta1/coin.proto"; -import "cosmwasm/wasm/v1/types.proto"; -import "amino/amino.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/wasm/types"; -option (gogoproto.goproto_stringer_all) = false; -option (gogoproto.goproto_getters_all) = false; -option (gogoproto.equal_all) = true; - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit StoreCodeProposal. To submit WASM code to the system, -// a simple MsgStoreCode can be invoked from the x/gov module via -// a v1 governance proposal. -message StoreCodeProposal { - option deprecated = true; - option (amino.name) = "wasm/StoreCodeProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // RunAs is the address that is passed to the contract's environment as sender - string run_as = 3; - // WASMByteCode can be raw or gzip compressed - bytes wasm_byte_code = 4 [ (gogoproto.customname) = "WASMByteCode" ]; - // Used in v1beta1 - reserved 5, 6; - // InstantiatePermission to apply on contract creation, optional - AccessConfig instantiate_permission = 7; - // UnpinCode code on upload, optional - bool unpin_code = 8; - // Source is the URL where the code is hosted - string source = 9; - // Builder is the docker image used to build the code deterministically, used - // for smart contract verification - string builder = 10; - // CodeHash is the SHA256 sum of the code outputted by builder, used for smart - // contract verification - bytes code_hash = 11; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit InstantiateContractProposal. To instantiate a contract, -// a simple MsgInstantiateContract can be invoked from the x/gov module via -// a v1 governance proposal. -message InstantiateContractProposal { - option deprecated = true; - option (amino.name) = "wasm/InstantiateContractProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // RunAs is the address that is passed to the contract's environment as sender - string run_as = 3; - // Admin is an optional address that can execute migrations - string admin = 4; - // CodeID is the reference to the stored WASM code - uint64 code_id = 5 [ (gogoproto.customname) = "CodeID" ]; - // Label is optional metadata to be stored with a constract instance. - string label = 6; - // Msg json encoded message to be passed to the contract on instantiation - bytes msg = 7 [ (gogoproto.casttype) = "RawContractMessage" ]; - // Funds coins that are transferred to the contract on instantiation - repeated cosmos.base.v1beta1.Coin funds = 8 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit InstantiateContract2Proposal. To instantiate contract 2, -// a simple MsgInstantiateContract2 can be invoked from the x/gov module via -// a v1 governance proposal. -message InstantiateContract2Proposal { - option deprecated = true; - option (amino.name) = "wasm/InstantiateContract2Proposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // RunAs is the address that is passed to the contract's enviroment as sender - string run_as = 3; - // Admin is an optional address that can execute migrations - string admin = 4; - // CodeID is the reference to the stored WASM code - uint64 code_id = 5 [ (gogoproto.customname) = "CodeID" ]; - // Label is optional metadata to be stored with a constract instance. - string label = 6; - // Msg json encode message to be passed to the contract on instantiation - bytes msg = 7 [ (gogoproto.casttype) = "RawContractMessage" ]; - // Funds coins that are transferred to the contract on instantiation - repeated cosmos.base.v1beta1.Coin funds = 8 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; - // Salt is an arbitrary value provided by the sender. Size can be 1 to 64. - bytes salt = 9; - // FixMsg include the msg value into the hash for the predictable address. - // Default is false - bool fix_msg = 10; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit MigrateContractProposal. To migrate a contract, -// a simple MsgMigrateContract can be invoked from the x/gov module via -// a v1 governance proposal. -message MigrateContractProposal { - option deprecated = true; - option (amino.name) = "wasm/MigrateContractProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // Note: skipping 3 as this was previously used for unneeded run_as - - // Contract is the address of the smart contract - string contract = 4; - // CodeID references the new WASM code - uint64 code_id = 5 [ (gogoproto.customname) = "CodeID" ]; - // Msg json encoded message to be passed to the contract on migration - bytes msg = 6 [ (gogoproto.casttype) = "RawContractMessage" ]; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit SudoContractProposal. To call sudo on a contract, -// a simple MsgSudoContract can be invoked from the x/gov module via -// a v1 governance proposal. -message SudoContractProposal { - option deprecated = true; - option (amino.name) = "wasm/SudoContractProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // Contract is the address of the smart contract - string contract = 3; - // Msg json encoded message to be passed to the contract as sudo - bytes msg = 4 [ (gogoproto.casttype) = "RawContractMessage" ]; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit ExecuteContractProposal. To call execute on a contract, -// a simple MsgExecuteContract can be invoked from the x/gov module via -// a v1 governance proposal. -message ExecuteContractProposal { - option deprecated = true; - option (amino.name) = "wasm/ExecuteContractProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // RunAs is the address that is passed to the contract's environment as sender - string run_as = 3; - // Contract is the address of the smart contract - string contract = 4; - // Msg json encoded message to be passed to the contract as execute - bytes msg = 5 [ (gogoproto.casttype) = "RawContractMessage" ]; - // Funds coins that are transferred to the contract on instantiation - repeated cosmos.base.v1beta1.Coin funds = 6 [ - (gogoproto.nullable) = false, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit UpdateAdminProposal. To set an admin for a contract, -// a simple MsgUpdateAdmin can be invoked from the x/gov module via -// a v1 governance proposal. -message UpdateAdminProposal { - option deprecated = true; - option (amino.name) = "wasm/UpdateAdminProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // NewAdmin address to be set - string new_admin = 3 [ (gogoproto.moretags) = "yaml:\"new_admin\"" ]; - // Contract is the address of the smart contract - string contract = 4; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit ClearAdminProposal. To clear the admin of a contract, -// a simple MsgClearAdmin can be invoked from the x/gov module via -// a v1 governance proposal. -message ClearAdminProposal { - option deprecated = true; - option (amino.name) = "wasm/ClearAdminProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // Contract is the address of the smart contract - string contract = 3; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit PinCodesProposal. To pin a set of code ids in the wasmvm -// cache, a simple MsgPinCodes can be invoked from the x/gov module via -// a v1 governance proposal. -message PinCodesProposal { - option deprecated = true; - option (amino.name) = "wasm/PinCodesProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; - // Description is a human readable text - string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; - // CodeIDs references the new WASM codes - repeated uint64 code_ids = 3 [ - (gogoproto.customname) = "CodeIDs", - (gogoproto.moretags) = "yaml:\"code_ids\"" - ]; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit UnpinCodesProposal. To unpin a set of code ids in the wasmvm -// cache, a simple MsgUnpinCodes can be invoked from the x/gov module via -// a v1 governance proposal. -message UnpinCodesProposal { - option deprecated = true; - option (amino.name) = "wasm/UnpinCodesProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; - // Description is a human readable text - string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; - // CodeIDs references the WASM codes - repeated uint64 code_ids = 3 [ - (gogoproto.customname) = "CodeIDs", - (gogoproto.moretags) = "yaml:\"code_ids\"" - ]; -} - -// AccessConfigUpdate contains the code id and the access config to be -// applied. -message AccessConfigUpdate { - // CodeID is the reference to the stored WASM code to be updated - uint64 code_id = 1 [ (gogoproto.customname) = "CodeID" ]; - // InstantiatePermission to apply to the set of code ids - AccessConfig instantiate_permission = 2 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit UpdateInstantiateConfigProposal. To update instantiate config -// to a set of code ids, a simple MsgUpdateInstantiateConfig can be invoked from -// the x/gov module via a v1 governance proposal. -message UpdateInstantiateConfigProposal { - option deprecated = true; - option (amino.name) = "wasm/UpdateInstantiateConfigProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; - // Description is a human readable text - string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; - // AccessConfigUpdate contains the list of code ids and the access config - // to be applied. - repeated AccessConfigUpdate access_config_updates = 3 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// Deprecated: Do not use. Since wasmd v0.40, there is no longer a need for -// an explicit StoreAndInstantiateContractProposal. To store and instantiate -// the contract, a simple MsgStoreAndInstantiateContract can be invoked from -// the x/gov module via a v1 governance proposal. -message StoreAndInstantiateContractProposal { - option deprecated = true; - option (amino.name) = "wasm/StoreAndInstantiateContractProposal"; - option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; - - // Title is a short summary - string title = 1; - // Description is a human readable text - string description = 2; - // RunAs is the address that is passed to the contract's environment as sender - string run_as = 3; - // WASMByteCode can be raw or gzip compressed - bytes wasm_byte_code = 4 [ (gogoproto.customname) = "WASMByteCode" ]; - // InstantiatePermission to apply on contract creation, optional - AccessConfig instantiate_permission = 5; - // UnpinCode code on upload, optional - bool unpin_code = 6; - // Admin is an optional address that can execute migrations - string admin = 7; - // Label is optional metadata to be stored with a constract instance. - string label = 8; - // Msg json encoded message to be passed to the contract on instantiation - bytes msg = 9 [ (gogoproto.casttype) = "RawContractMessage" ]; - // Funds coins that are transferred to the contract on instantiation - repeated cosmos.base.v1beta1.Coin funds = 10 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; - // Source is the URL where the code is hosted - string source = 11; - // Builder is the docker image used to build the code deterministically, used - // for smart contract verification - string builder = 12; - // CodeHash is the SHA256 sum of the code outputted by builder, used for smart - // contract verification - bytes code_hash = 13; -} diff --git a/proto/cosmwasm/wasm/v1/query.proto b/proto/cosmwasm/wasm/v1/query.proto deleted file mode 100644 index 5a2039b..0000000 --- a/proto/cosmwasm/wasm/v1/query.proto +++ /dev/null @@ -1,268 +0,0 @@ -syntax = "proto3"; -package cosmwasm.wasm.v1; - -import "gogoproto/gogo.proto"; -import "cosmwasm/wasm/v1/types.proto"; -import "google/api/annotations.proto"; -import "cosmos/base/query/v1beta1/pagination.proto"; -import "amino/amino.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/wasm/types"; -option (gogoproto.goproto_getters_all) = false; -option (gogoproto.equal_all) = false; - -// Query provides defines the gRPC querier service -service Query { - // ContractInfo gets the contract meta data - rpc ContractInfo(QueryContractInfoRequest) - returns (QueryContractInfoResponse) { - option (google.api.http).get = "/cosmwasm/wasm/v1/contract/{address}"; - } - // ContractHistory gets the contract code history - rpc ContractHistory(QueryContractHistoryRequest) - returns (QueryContractHistoryResponse) { - option (google.api.http).get = - "/cosmwasm/wasm/v1/contract/{address}/history"; - } - // ContractsByCode lists all smart contracts for a code id - rpc ContractsByCode(QueryContractsByCodeRequest) - returns (QueryContractsByCodeResponse) { - option (google.api.http).get = "/cosmwasm/wasm/v1/code/{code_id}/contracts"; - } - // AllContractState gets all raw store data for a single contract - rpc AllContractState(QueryAllContractStateRequest) - returns (QueryAllContractStateResponse) { - option (google.api.http).get = "/cosmwasm/wasm/v1/contract/{address}/state"; - } - // RawContractState gets single key from the raw store data of a contract - rpc RawContractState(QueryRawContractStateRequest) - returns (QueryRawContractStateResponse) { - option (google.api.http).get = - "/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}"; - } - // SmartContractState get smart query result from the contract - rpc SmartContractState(QuerySmartContractStateRequest) - returns (QuerySmartContractStateResponse) { - option (google.api.http).get = - "/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}"; - } - // Code gets the binary code and metadata for a singe wasm code - rpc Code(QueryCodeRequest) returns (QueryCodeResponse) { - option (google.api.http).get = "/cosmwasm/wasm/v1/code/{code_id}"; - } - // Codes gets the metadata for all stored wasm codes - rpc Codes(QueryCodesRequest) returns (QueryCodesResponse) { - option (google.api.http).get = "/cosmwasm/wasm/v1/code"; - } - - // PinnedCodes gets the pinned code ids - rpc PinnedCodes(QueryPinnedCodesRequest) returns (QueryPinnedCodesResponse) { - option (google.api.http).get = "/cosmwasm/wasm/v1/codes/pinned"; - } - - // Params gets the module params - rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { - option (google.api.http).get = "/cosmwasm/wasm/v1/codes/params"; - } - - // ContractsByCreator gets the contracts by creator - rpc ContractsByCreator(QueryContractsByCreatorRequest) - returns (QueryContractsByCreatorResponse) { - option (google.api.http).get = - "/cosmwasm/wasm/v1/contracts/creator/{creator_address}"; - } -} - -// QueryContractInfoRequest is the request type for the Query/ContractInfo RPC -// method -message QueryContractInfoRequest { - // address is the address of the contract to query - string address = 1; -} -// QueryContractInfoResponse is the response type for the Query/ContractInfo RPC -// method -message QueryContractInfoResponse { - option (gogoproto.equal) = true; - - // address is the address of the contract - string address = 1; - ContractInfo contract_info = 2 [ - (gogoproto.embed) = true, - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.jsontag) = "" - ]; -} - -// QueryContractHistoryRequest is the request type for the Query/ContractHistory -// RPC method -message QueryContractHistoryRequest { - // address is the address of the contract to query - string address = 1; - // pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 2; -} - -// QueryContractHistoryResponse is the response type for the -// Query/ContractHistory RPC method -message QueryContractHistoryResponse { - repeated ContractCodeHistoryEntry entries = 1 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; - // pagination defines the pagination in the response. - cosmos.base.query.v1beta1.PageResponse pagination = 2; -} - -// QueryContractsByCodeRequest is the request type for the Query/ContractsByCode -// RPC method -message QueryContractsByCodeRequest { - uint64 code_id = 1; // grpc-gateway_out does not support Go style CodID - // pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 2; -} - -// QueryContractsByCodeResponse is the response type for the -// Query/ContractsByCode RPC method -message QueryContractsByCodeResponse { - // contracts are a set of contract addresses - repeated string contracts = 1; - - // pagination defines the pagination in the response. - cosmos.base.query.v1beta1.PageResponse pagination = 2; -} - -// QueryAllContractStateRequest is the request type for the -// Query/AllContractState RPC method -message QueryAllContractStateRequest { - // address is the address of the contract - string address = 1; - // pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 2; -} - -// QueryAllContractStateResponse is the response type for the -// Query/AllContractState RPC method -message QueryAllContractStateResponse { - repeated Model models = 1 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; - // pagination defines the pagination in the response. - cosmos.base.query.v1beta1.PageResponse pagination = 2; -} - -// QueryRawContractStateRequest is the request type for the -// Query/RawContractState RPC method -message QueryRawContractStateRequest { - // address is the address of the contract - string address = 1; - bytes query_data = 2; -} - -// QueryRawContractStateResponse is the response type for the -// Query/RawContractState RPC method -message QueryRawContractStateResponse { - // Data contains the raw store data - bytes data = 1; -} - -// QuerySmartContractStateRequest is the request type for the -// Query/SmartContractState RPC method -message QuerySmartContractStateRequest { - // address is the address of the contract - string address = 1; - // QueryData contains the query data passed to the contract - bytes query_data = 2 [ (gogoproto.casttype) = "RawContractMessage" ]; -} - -// QuerySmartContractStateResponse is the response type for the -// Query/SmartContractState RPC method -message QuerySmartContractStateResponse { - // Data contains the json data returned from the smart contract - bytes data = 1 [ (gogoproto.casttype) = "RawContractMessage" ]; -} - -// QueryCodeRequest is the request type for the Query/Code RPC method -message QueryCodeRequest { - uint64 code_id = 1; // grpc-gateway_out does not support Go style CodID -} - -// CodeInfoResponse contains code meta data from CodeInfo -message CodeInfoResponse { - option (gogoproto.equal) = true; - - uint64 code_id = 1 [ - (gogoproto.customname) = "CodeID", - (gogoproto.jsontag) = "id" - ]; // id for legacy support - string creator = 2; - bytes data_hash = 3 - [ (gogoproto.casttype) = - "github.com/cometbft/cometbft/libs/bytes.HexBytes" ]; - // Used in v1beta1 - reserved 4, 5; - AccessConfig instantiate_permission = 6 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// QueryCodeResponse is the response type for the Query/Code RPC method -message QueryCodeResponse { - option (gogoproto.equal) = true; - CodeInfoResponse code_info = 1 - [ (gogoproto.embed) = true, (gogoproto.jsontag) = "" ]; - bytes data = 2 [ (gogoproto.jsontag) = "data" ]; -} - -// QueryCodesRequest is the request type for the Query/Codes RPC method -message QueryCodesRequest { - // pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 1; -} - -// QueryCodesResponse is the response type for the Query/Codes RPC method -message QueryCodesResponse { - repeated CodeInfoResponse code_infos = 1 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; - // pagination defines the pagination in the response. - cosmos.base.query.v1beta1.PageResponse pagination = 2; -} - -// QueryPinnedCodesRequest is the request type for the Query/PinnedCodes -// RPC method -message QueryPinnedCodesRequest { - // pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 2; -} - -// QueryPinnedCodesResponse is the response type for the -// Query/PinnedCodes RPC method -message QueryPinnedCodesResponse { - repeated uint64 code_ids = 1 [ (gogoproto.customname) = "CodeIDs" ]; - // pagination defines the pagination in the response. - cosmos.base.query.v1beta1.PageResponse pagination = 2; -} - -// QueryParamsRequest is the request type for the Query/Params RPC method. -message QueryParamsRequest {} - -// QueryParamsResponse is the response type for the Query/Params RPC method. -message QueryParamsResponse { - // params defines the parameters of the module. - Params params = 1 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// QueryContractsByCreatorRequest is the request type for the -// Query/ContractsByCreator RPC method. -message QueryContractsByCreatorRequest { - // CreatorAddress is the address of contract creator - string creator_address = 1; - // Pagination defines an optional pagination for the request. - cosmos.base.query.v1beta1.PageRequest pagination = 2; -} - -// QueryContractsByCreatorResponse is the response type for the -// Query/ContractsByCreator RPC method. -message QueryContractsByCreatorResponse { - // ContractAddresses result set - repeated string contract_addresses = 1; - // Pagination defines the pagination in the response. - cosmos.base.query.v1beta1.PageResponse pagination = 2; -} \ No newline at end of file diff --git a/proto/cosmwasm/wasm/v1/tx.proto b/proto/cosmwasm/wasm/v1/tx.proto deleted file mode 100644 index 79a9930..0000000 --- a/proto/cosmwasm/wasm/v1/tx.proto +++ /dev/null @@ -1,391 +0,0 @@ -syntax = "proto3"; -package cosmwasm.wasm.v1; - -import "cosmos/base/v1beta1/coin.proto"; -import "cosmos/msg/v1/msg.proto"; -import "gogoproto/gogo.proto"; -import "cosmwasm/wasm/v1/types.proto"; -import "cosmos_proto/cosmos.proto"; -import "amino/amino.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/wasm/types"; -option (gogoproto.goproto_getters_all) = false; - -// Msg defines the wasm Msg service. -service Msg { - // StoreCode to submit Wasm code to the system - rpc StoreCode(MsgStoreCode) returns (MsgStoreCodeResponse); - // InstantiateContract creates a new smart contract instance for the given - // code id. - rpc InstantiateContract(MsgInstantiateContract) - returns (MsgInstantiateContractResponse); - // InstantiateContract2 creates a new smart contract instance for the given - // code id with a predictable address - rpc InstantiateContract2(MsgInstantiateContract2) - returns (MsgInstantiateContract2Response); - // Execute submits the given message data to a smart contract - rpc ExecuteContract(MsgExecuteContract) returns (MsgExecuteContractResponse); - // Migrate runs a code upgrade/ downgrade for a smart contract - rpc MigrateContract(MsgMigrateContract) returns (MsgMigrateContractResponse); - // UpdateAdmin sets a new admin for a smart contract - rpc UpdateAdmin(MsgUpdateAdmin) returns (MsgUpdateAdminResponse); - // ClearAdmin removes any admin stored for a smart contract - rpc ClearAdmin(MsgClearAdmin) returns (MsgClearAdminResponse); - // UpdateInstantiateConfig updates instantiate config for a smart contract - rpc UpdateInstantiateConfig(MsgUpdateInstantiateConfig) - returns (MsgUpdateInstantiateConfigResponse); - // UpdateParams defines a governance operation for updating the x/wasm - // module parameters. The authority is defined in the keeper. - // - // Since: 0.40 - rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); - // SudoContract defines a governance operation for calling sudo - // on a contract. The authority is defined in the keeper. - // - // Since: 0.40 - rpc SudoContract(MsgSudoContract) returns (MsgSudoContractResponse); - // PinCodes defines a governance operation for pinning a set of - // code ids in the wasmvm cache. The authority is defined in the keeper. - // - // Since: 0.40 - rpc PinCodes(MsgPinCodes) returns (MsgPinCodesResponse); - // UnpinCodes defines a governance operation for unpinning a set of - // code ids in the wasmvm cache. The authority is defined in the keeper. - // - // Since: 0.40 - rpc UnpinCodes(MsgUnpinCodes) returns (MsgUnpinCodesResponse); - // StoreAndInstantiateContract defines a governance operation for storing - // and instantiating the contract. The authority is defined in the keeper. - // - // Since: 0.40 - rpc StoreAndInstantiateContract(MsgStoreAndInstantiateContract) - returns (MsgStoreAndInstantiateContractResponse); -} - -// MsgStoreCode submit Wasm code to the system -message MsgStoreCode { - option (amino.name) = "wasm/MsgStoreCode"; - option (cosmos.msg.v1.signer) = "sender"; - - // Sender is the actor that signed the messages - string sender = 1; - // WASMByteCode can be raw or gzip compressed - bytes wasm_byte_code = 2 [ (gogoproto.customname) = "WASMByteCode" ]; - // Used in v1beta1 - reserved 3, 4; - // InstantiatePermission access control to apply on contract creation, - // optional - AccessConfig instantiate_permission = 5; -} -// MsgStoreCodeResponse returns store result data. -message MsgStoreCodeResponse { - // CodeID is the reference to the stored WASM code - uint64 code_id = 1 [ (gogoproto.customname) = "CodeID" ]; - // Checksum is the sha256 hash of the stored code - bytes checksum = 2; -} - -// MsgInstantiateContract create a new smart contract instance for the given -// code id. -message MsgInstantiateContract { - option (amino.name) = "wasm/MsgInstantiateContract"; - option (cosmos.msg.v1.signer) = "sender"; - - // Sender is the that actor that signed the messages - string sender = 1; - // Admin is an optional address that can execute migrations - string admin = 2; - // CodeID is the reference to the stored WASM code - uint64 code_id = 3 [ (gogoproto.customname) = "CodeID" ]; - // Label is optional metadata to be stored with a contract instance. - string label = 4; - // Msg json encoded message to be passed to the contract on instantiation - bytes msg = 5 [ (gogoproto.casttype) = "RawContractMessage" ]; - // Funds coins that are transferred to the contract on instantiation - repeated cosmos.base.v1beta1.Coin funds = 6 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; -} - -// MsgInstantiateContractResponse return instantiation result data -message MsgInstantiateContractResponse { - // Address is the bech32 address of the new contract instance. - string address = 1; - // Data contains bytes to returned from the contract - bytes data = 2; -} - -// MsgInstantiateContract2 create a new smart contract instance for the given -// code id with a predicable address. -message MsgInstantiateContract2 { - option (amino.name) = "wasm/MsgInstantiateContract2"; - option (cosmos.msg.v1.signer) = "sender"; - - // Sender is the that actor that signed the messages - string sender = 1; - // Admin is an optional address that can execute migrations - string admin = 2; - // CodeID is the reference to the stored WASM code - uint64 code_id = 3 [ (gogoproto.customname) = "CodeID" ]; - // Label is optional metadata to be stored with a contract instance. - string label = 4; - // Msg json encoded message to be passed to the contract on instantiation - bytes msg = 5 [ (gogoproto.casttype) = "RawContractMessage" ]; - // Funds coins that are transferred to the contract on instantiation - repeated cosmos.base.v1beta1.Coin funds = 6 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; - // Salt is an arbitrary value provided by the sender. Size can be 1 to 64. - bytes salt = 7; - // FixMsg include the msg value into the hash for the predictable address. - // Default is false - bool fix_msg = 8; -} - -// MsgInstantiateContract2Response return instantiation result data -message MsgInstantiateContract2Response { - // Address is the bech32 address of the new contract instance. - string address = 1; - // Data contains bytes to returned from the contract - bytes data = 2; -} - -// MsgExecuteContract submits the given message data to a smart contract -message MsgExecuteContract { - option (amino.name) = "wasm/MsgExecuteContract"; - option (cosmos.msg.v1.signer) = "sender"; - - // Sender is the that actor that signed the messages - string sender = 1; - // Contract is the address of the smart contract - string contract = 2; - // Msg json encoded message to be passed to the contract - bytes msg = 3 [ (gogoproto.casttype) = "RawContractMessage" ]; - // Funds coins that are transferred to the contract on execution - repeated cosmos.base.v1beta1.Coin funds = 5 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; -} - -// MsgExecuteContractResponse returns execution result data. -message MsgExecuteContractResponse { - // Data contains bytes to returned from the contract - bytes data = 1; -} - -// MsgMigrateContract runs a code upgrade/ downgrade for a smart contract -message MsgMigrateContract { - option (amino.name) = "wasm/MsgMigrateContract"; - option (cosmos.msg.v1.signer) = "sender"; - - // Sender is the that actor that signed the messages - string sender = 1; - // Contract is the address of the smart contract - string contract = 2; - // CodeID references the new WASM code - uint64 code_id = 3 [ (gogoproto.customname) = "CodeID" ]; - // Msg json encoded message to be passed to the contract on migration - bytes msg = 4 [ (gogoproto.casttype) = "RawContractMessage" ]; -} - -// MsgMigrateContractResponse returns contract migration result data. -message MsgMigrateContractResponse { - // Data contains same raw bytes returned as data from the wasm contract. - // (May be empty) - bytes data = 1; -} - -// MsgUpdateAdmin sets a new admin for a smart contract -message MsgUpdateAdmin { - option (amino.name) = "wasm/MsgUpdateAdmin"; - option (cosmos.msg.v1.signer) = "sender"; - - // Sender is the that actor that signed the messages - string sender = 1; - // NewAdmin address to be set - string new_admin = 2; - // Contract is the address of the smart contract - string contract = 3; -} - -// MsgUpdateAdminResponse returns empty data -message MsgUpdateAdminResponse {} - -// MsgClearAdmin removes any admin stored for a smart contract -message MsgClearAdmin { - option (amino.name) = "wasm/MsgClearAdmin"; - option (cosmos.msg.v1.signer) = "sender"; - - // Sender is the actor that signed the messages - string sender = 1; - // Contract is the address of the smart contract - string contract = 3; -} - -// MsgClearAdminResponse returns empty data -message MsgClearAdminResponse {} - -// MsgUpdateInstantiateConfig updates instantiate config for a smart contract -message MsgUpdateInstantiateConfig { - option (amino.name) = "wasm/MsgUpdateInstantiateConfig"; - option (cosmos.msg.v1.signer) = "sender"; - - // Sender is the that actor that signed the messages - string sender = 1; - // CodeID references the stored WASM code - uint64 code_id = 2 [ (gogoproto.customname) = "CodeID" ]; - // NewInstantiatePermission is the new access control - AccessConfig new_instantiate_permission = 3; -} - -// MsgUpdateInstantiateConfigResponse returns empty data -message MsgUpdateInstantiateConfigResponse {} - -// MsgUpdateParams is the MsgUpdateParams request type. -// -// Since: 0.40 -message MsgUpdateParams { - option (amino.name) = "wasm/MsgUpdateParams"; - option (cosmos.msg.v1.signer) = "authority"; - - // Authority is the address of the governance account. - string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - - // params defines the x/wasm parameters to update. - // - // NOTE: All parameters must be supplied. - Params params = 2 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// MsgUpdateParamsResponse defines the response structure for executing a -// MsgUpdateParams message. -// -// Since: 0.40 -message MsgUpdateParamsResponse {} - -// MsgSudoContract is the MsgSudoContract request type. -// -// Since: 0.40 -message MsgSudoContract { - option (amino.name) = "wasm/MsgSudoContract"; - option (cosmos.msg.v1.signer) = "authority"; - - // Authority is the address of the governance account. - string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - - // Contract is the address of the smart contract - string contract = 2; - // Msg json encoded message to be passed to the contract as sudo - bytes msg = 3 [ (gogoproto.casttype) = "RawContractMessage" ]; -} - -// MsgSudoContractResponse defines the response structure for executing a -// MsgSudoContract message. -// -// Since: 0.40 -message MsgSudoContractResponse { - // Data contains bytes to returned from the contract - bytes data = 1; -} - -// MsgPinCodes is the MsgPinCodes request type. -// -// Since: 0.40 -message MsgPinCodes { - option (amino.name) = "wasm/MsgPinCodes"; - option (cosmos.msg.v1.signer) = "authority"; - - // Authority is the address of the governance account. - string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - // CodeIDs references the new WASM codes - repeated uint64 code_ids = 2 [ - (gogoproto.customname) = "CodeIDs", - (gogoproto.moretags) = "yaml:\"code_ids\"" - ]; -} - -// MsgPinCodesResponse defines the response structure for executing a -// MsgPinCodes message. -// -// Since: 0.40 -message MsgPinCodesResponse {} - -// MsgUnpinCodes is the MsgUnpinCodes request type. -// -// Since: 0.40 -message MsgUnpinCodes { - option (amino.name) = "wasm/MsgUnpinCodes"; - option (cosmos.msg.v1.signer) = "authority"; - - // Authority is the address of the governance account. - string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - // CodeIDs references the WASM codes - repeated uint64 code_ids = 2 [ - (gogoproto.customname) = "CodeIDs", - (gogoproto.moretags) = "yaml:\"code_ids\"" - ]; -} - -// MsgUnpinCodesResponse defines the response structure for executing a -// MsgUnpinCodes message. -// -// Since: 0.40 -message MsgUnpinCodesResponse {} - -// MsgStoreAndInstantiateContract is the MsgStoreAndInstantiateContract -// request type. -// -// Since: 0.40 -message MsgStoreAndInstantiateContract { - option (amino.name) = "wasm/MsgStoreAndInstantiateContract"; - option (cosmos.msg.v1.signer) = "authority"; - - // Authority is the address of the governance account. - string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; - // WASMByteCode can be raw or gzip compressed - bytes wasm_byte_code = 3 [ (gogoproto.customname) = "WASMByteCode" ]; - // InstantiatePermission to apply on contract creation, optional - AccessConfig instantiate_permission = 4; - // UnpinCode code on upload, optional. As default the uploaded contract is - // pinned to cache. - bool unpin_code = 5; - // Admin is an optional address that can execute migrations - string admin = 6; - // Label is optional metadata to be stored with a constract instance. - string label = 7; - // Msg json encoded message to be passed to the contract on instantiation - bytes msg = 8 [ (gogoproto.casttype) = "RawContractMessage" ]; - // Funds coins that are transferred from the authority account to the contract - // on instantiation - repeated cosmos.base.v1beta1.Coin funds = 9 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" - ]; - // Source is the URL where the code is hosted - string source = 10; - // Builder is the docker image used to build the code deterministically, used - // for smart contract verification - string builder = 11; - // CodeHash is the SHA256 sum of the code outputted by builder, used for smart - // contract verification - bytes code_hash = 12; -} - -// MsgStoreAndInstantiateContractResponse defines the response structure -// for executing a MsgStoreAndInstantiateContract message. -// -// Since: 0.40 -message MsgStoreAndInstantiateContractResponse { - // Address is the bech32 address of the new contract instance. - string address = 1; - // Data contains bytes to returned from the contract - bytes data = 2; -} \ No newline at end of file diff --git a/proto/cosmwasm/wasm/v1/types.proto b/proto/cosmwasm/wasm/v1/types.proto deleted file mode 100644 index 1572b5a..0000000 --- a/proto/cosmwasm/wasm/v1/types.proto +++ /dev/null @@ -1,148 +0,0 @@ -syntax = "proto3"; -package cosmwasm.wasm.v1; - -import "cosmos_proto/cosmos.proto"; -import "gogoproto/gogo.proto"; -import "google/protobuf/any.proto"; -import "amino/amino.proto"; - -option go_package = "github.com/terpnetwork/terp-core/x/wasm/types"; -option (gogoproto.goproto_getters_all) = false; -option (gogoproto.equal_all) = true; - -// AccessType permission types -enum AccessType { - option (gogoproto.goproto_enum_prefix) = false; - option (gogoproto.goproto_enum_stringer) = false; - // AccessTypeUnspecified placeholder for empty value - ACCESS_TYPE_UNSPECIFIED = 0 - [ (gogoproto.enumvalue_customname) = "AccessTypeUnspecified" ]; - // AccessTypeNobody forbidden - ACCESS_TYPE_NOBODY = 1 - [ (gogoproto.enumvalue_customname) = "AccessTypeNobody" ]; - // AccessTypeOnlyAddress restricted to a single address - // Deprecated: use AccessTypeAnyOfAddresses instead - ACCESS_TYPE_ONLY_ADDRESS = 2 - [ (gogoproto.enumvalue_customname) = "AccessTypeOnlyAddress" ]; - // AccessTypeEverybody unrestricted - ACCESS_TYPE_EVERYBODY = 3 - [ (gogoproto.enumvalue_customname) = "AccessTypeEverybody" ]; - // AccessTypeAnyOfAddresses allow any of the addresses - ACCESS_TYPE_ANY_OF_ADDRESSES = 4 - [ (gogoproto.enumvalue_customname) = "AccessTypeAnyOfAddresses" ]; -} - -// AccessTypeParam -message AccessTypeParam { - option (gogoproto.goproto_stringer) = true; - AccessType value = 1 [ (gogoproto.moretags) = "yaml:\"value\"" ]; -} - -// AccessConfig access control type. -message AccessConfig { - option (gogoproto.goproto_stringer) = true; - AccessType permission = 1 [ (gogoproto.moretags) = "yaml:\"permission\"" ]; - - // Address - // Deprecated: replaced by addresses - string address = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ]; - repeated string addresses = 3 [ (gogoproto.moretags) = "yaml:\"addresses\"" ]; -} - -// Params defines the set of wasm parameters. -message Params { - option (gogoproto.goproto_stringer) = false; - AccessConfig code_upload_access = 1 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, - (gogoproto.moretags) = "yaml:\"code_upload_access\"" - ]; - AccessType instantiate_default_permission = 2 - [ (gogoproto.moretags) = "yaml:\"instantiate_default_permission\"" ]; -} - -// CodeInfo is data for the uploaded contract WASM code -message CodeInfo { - // CodeHash is the unique identifier created by wasmvm - bytes code_hash = 1; - // Creator address who initially stored the code - string creator = 2; - // Used in v1beta1 - reserved 3, 4; - // InstantiateConfig access control to apply on contract creation, optional - AccessConfig instantiate_config = 5 - [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true ]; -} - -// ContractInfo stores a WASM contract instance -message ContractInfo { - option (gogoproto.equal) = true; - - // CodeID is the reference to the stored Wasm code - uint64 code_id = 1 [ (gogoproto.customname) = "CodeID" ]; - // Creator address who initially instantiated the contract - string creator = 2; - // Admin is an optional address that can execute migrations - string admin = 3; - // Label is optional metadata to be stored with a contract instance. - string label = 4; - // Created Tx position when the contract was instantiated. - AbsoluteTxPosition created = 5; - string ibc_port_id = 6 [ (gogoproto.customname) = "IBCPortID" ]; - - // Extension is an extension point to store custom metadata within the - // persistence model. - google.protobuf.Any extension = 7 - [ (cosmos_proto.accepts_interface) = - "cosmwasm.wasm.v1.ContractInfoExtension" ]; -} - -// ContractCodeHistoryOperationType actions that caused a code change -enum ContractCodeHistoryOperationType { - option (gogoproto.goproto_enum_prefix) = false; - // ContractCodeHistoryOperationTypeUnspecified placeholder for empty value - CONTRACT_CODE_HISTORY_OPERATION_TYPE_UNSPECIFIED = 0 - [ (gogoproto.enumvalue_customname) = - "ContractCodeHistoryOperationTypeUnspecified" ]; - // ContractCodeHistoryOperationTypeInit on chain contract instantiation - CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT = 1 - [ (gogoproto.enumvalue_customname) = - "ContractCodeHistoryOperationTypeInit" ]; - // ContractCodeHistoryOperationTypeMigrate code migration - CONTRACT_CODE_HISTORY_OPERATION_TYPE_MIGRATE = 2 - [ (gogoproto.enumvalue_customname) = - "ContractCodeHistoryOperationTypeMigrate" ]; - // ContractCodeHistoryOperationTypeGenesis based on genesis data - CONTRACT_CODE_HISTORY_OPERATION_TYPE_GENESIS = 3 - [ (gogoproto.enumvalue_customname) = - "ContractCodeHistoryOperationTypeGenesis" ]; -} - -// ContractCodeHistoryEntry metadata to a contract. -message ContractCodeHistoryEntry { - ContractCodeHistoryOperationType operation = 1; - // CodeID is the reference to the stored WASM code - uint64 code_id = 2 [ (gogoproto.customname) = "CodeID" ]; - // Updated Tx position when the operation was executed. - AbsoluteTxPosition updated = 3; - bytes msg = 4 [ (gogoproto.casttype) = "RawContractMessage" ]; -} - -// AbsoluteTxPosition is a unique transaction position that allows for global -// ordering of transactions. -message AbsoluteTxPosition { - // BlockHeight is the block the contract was created at - uint64 block_height = 1; - // TxIndex is a monotonic counter within the block (actual transaction index, - // or gas consumed) - uint64 tx_index = 2; -} - -// Model is a struct that holds a KV pair -message Model { - // hex-encode key to read it better (this is often ascii) - bytes key = 1 [ (gogoproto.casttype) = - "github.com/cometbft/cometbft/libs/bytes.HexBytes" ]; - // base64-encode raw value - bytes value = 2; -} \ No newline at end of file diff --git a/x/ibchooks/.cargo/config b/x/ibchooks/.cargo/config new file mode 100644 index 0000000..946af0f --- /dev/null +++ b/x/ibchooks/.cargo/config @@ -0,0 +1,2 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" diff --git a/x/ibchooks/README.md b/x/ibchooks/README.md new file mode 100644 index 0000000..1955c92 --- /dev/null +++ b/x/ibchooks/README.md @@ -0,0 +1,185 @@ +# IBC-hooks + +## Wasm Hooks + +The wasm hook is an IBC middleware which is used to allow ICS-20 token transfers to initiate contract calls. +This allows cross-chain contract calls, that involve token movement. +This is useful for a variety of usecases. +One of primary importance is cross-chain swaps, which is an extremely powerful primitive. + +The mechanism enabling this is a `memo` field on every ICS20 transfer packet as of [IBC v3.4.0](https://medium.com/the-interchain-foundation/moving-beyond-simple-token-transfers-d42b2b1dc29b). +Wasm hooks is an IBC middleware that parses an ICS20 transfer, and if the `memo` field is of a particular form, executes a wasm contract call. We now detail the `memo` format for `wasm` contract calls, and the execution guarantees provided. + +### Cosmwasm Contract Execution Format + +Before we dive into the IBC metadata format, we show the cosmwasm execute message format, so the reader has a sense of what are the fields we need to be setting in. +The cosmwasm `MsgExecuteContract` is defined [here](https://github.com/CosmWasm/wasmd/blob/4fe2fbc8f322efdaf187e2e5c99ce32fd1df06f0/x/wasm/types/tx.pb.go#L340-L349 +) as the following type: + +```go +type MsgExecuteContract struct { + // Sender is the that actor that signed the messages + Sender string + // Contract is the address of the smart contract + Contract string + // Msg json encoded message to be passed to the contract + Msg RawContractMessage + // Funds coins that are transferred to the contract on execution + Funds sdk.Coins +} +``` + +So we detail where we want to get each of these fields from: + +* Sender: We cannot trust the sender of an IBC packet, the counterparty chain has full ability to lie about it. +We cannot risk this sender being confused for a particular user or module address on Osmosis. +So we replace the sender with an account to represent the sender prefixed by the channel and a wasm module prefix. +This is done by setting the sender to `Bech32(Hash("ibc-wasm-hook-intermediary" || channelID || sender))`, where the channelId is the channel id on the local chain. +* Contract: This field should be directly obtained from the ICS-20 packet metadata +* Msg: This field should be directly obtained from the ICS-20 packet metadata. +* Funds: This field is set to the amount of funds being sent over in the ICS 20 packet. One detail is that the denom in the packet is the counterparty chains representation of the denom, so we have to translate it to Osmosis' representation. + +So our constructed cosmwasm message that we execute will look like: + +```go +msg := MsgExecuteContract{ + // Sender is the that actor that signed the messages + Sender: "osmo1-hash-of-channel-and-sender", + // Contract is the address of the smart contract + Contract: packet.data.memo["wasm"]["ContractAddress"], + // Msg json encoded message to be passed to the contract + Msg: packet.data.memo["wasm"]["Msg"], + // Funds coins that are transferred to the contract on execution + Funds: sdk.NewCoin{Denom: ibc.ConvertSenderDenomToLocalDenom(packet.data.Denom), Amount: packet.data.Amount} +``` + +### ICS20 packet structure + +So given the details above, we propogate the implied ICS20 packet data structure. +ICS20 is JSON native, so we use JSON for the memo format. + +```json +{ + //... other ibc fields that we don't care about + "data":{ + "denom": "denom on counterparty chain (e.g. uatom)", // will be transformed to the local denom (ibc/...) + "amount": "1000", + "sender": "addr on counterparty chain", // will be transformed + "receiver": "contract addr or blank", + "memo": { + "wasm": { + "contract": "osmo1contractAddr", + "msg": { + "raw_message_fields": "raw_message_data", + } + } + } + } +} +``` + +An ICS20 packet is formatted correctly for wasmhooks iff the following all hold: + +* `memo` is not blank +* `memo` is valid JSON +* `memo` has at least one key, with value `"wasm"` +* `memo["wasm"]` has exactly two entries, `"contract"` and `"msg"` +* `memo["wasm"]["msg"]` is a valid JSON object +* `receiver == "" || receiver == memo["wasm"]["contract"]` + +We consider an ICS20 packet as directed towards wasmhooks iff all of the following hold: + +* `memo` is not blank +* `memo` is valid JSON +* `memo` has at least one key, with name `"wasm"` + +If an ICS20 packet is not directed towards wasmhooks, wasmhooks doesn't do anything. +If an ICS20 packet is directed towards wasmhooks, and is formated incorrectly, then wasmhooks returns an error. + +### Execution flow + +Pre wasm hooks: + +* Ensure the incoming IBC packet is cryptogaphically valid +* Ensure the incoming IBC packet is not timed out. + +In Wasm hooks, pre packet execution: + +* Ensure the packet is correctly formatted (as defined above) +* Edit the receiver to be the hardcoded IBC module account + +In wasm hooks, post packet execution: + +* Construct wasm message as defined before +* Execute wasm message +* if wasm message has error, return ErrAck +* otherwise continue through middleware + +## Ack callbacks + +A contract that sends an IBC transfer, may need to listen for the ACK from that packet. To allow +contracts to listen on the ack of specific packets, we provide Ack callbacks. + +### Design + +The sender of an IBC transfer packet may specify a callback for when the ack of that packet is received in the memo +field of the transfer packet. + +Crucially, _only_ the IBC packet sender can set the callback. + +### Use case + +The crosschain swaps implementation sends an IBC transfer. If the transfer were to fail, we want to allow the sender +to be able to retrieve their funds (which would otherwise be stuck in the contract). To do this, we allow users to +retrieve the funds after the timeout has passed, but without the ack information, we cannot guarantee that the send +hasn't failed (i.e.: returned an error ack notifying that the receiving change didn't accept it) + +### Implementation + +#### Callback information in memo + +For the callback to be processed, the transfer packet's memo should contain the following in its JSON: + +`{"ibc_callback": "osmo1contractAddr"}` + +The wasm hooks will keep the mapping from the packet's channel and sequence to the contract in storage. When an ack is +received, it will notify the specified contract via a sudo message. + +#### Interface for receiving the Acks and Timeouts + +The contract that awaits the callback should implement the following interface for a sudo message: + +```rust +#[cw_serde] +pub enum IBCLifecycleComplete { + #[serde(rename = "ibc_ack")] + IBCAck { + /// The source channel (osmosis side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + /// String encoded version of the ack as seen by OnAcknowledgementPacket(..) + ack: String, + /// Weather an ack is a success of failure according to the transfer spec + success: bool, + }, + #[serde(rename = "ibc_timeout")] + IBCTimeout { + /// The source channel (osmosis side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + }, +} + +/// Message type for `sudo` entry_point +#[cw_serde] +pub enum SudoMsg { + #[serde(rename = "ibc_lifecycle_complete")] + IBCLifecycleComplete(IBCLifecycleComplete), +} +``` + +# Testing strategy + +See go tests. \ No newline at end of file diff --git a/x/ibchooks/client/cli/query.go b/x/ibchooks/client/cli/query.go new file mode 100644 index 0000000..989062f --- /dev/null +++ b/x/ibchooks/client/cli/query.go @@ -0,0 +1,76 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/flags" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/terpnetwork/terp-core/v2/x/ibchooks/keeper" + "github.com/terpnetwork/terp-core/v2/x/ibchooks/types" +) + +func indexRunCmd(cmd *cobra.Command, _ []string) error { + usageTemplate := `Usage:{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}} + +{{if .HasAvailableSubCommands}}Available Commands:{{range .Commands}}{{if .IsAvailableCommand}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` + cmd.SetUsageTemplate(usageTemplate) + return cmd.Help() +} + +// GetQueryCmd returns the cli query commands for this module. +func GetQueryCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: indexRunCmd, + } + + cmd.AddCommand( + GetCmdWasmSender(), + ) + return cmd +} + +// GetCmdPoolParams return pool params. +func GetCmdWasmSender() *cobra.Command { + cmd := &cobra.Command{ + Use: "wasm-sender ", + Short: "Generate the local address for a wasm hooks sender", + Long: strings.TrimSpace( + fmt.Sprintf(`Generate the local address for a wasm hooks sender. +Example: +$ %s query ibc-hooks wasm-hooks-sender channel-42 juno12smx2wdlyttvyzvzg54y2vnqwq2qjatezqwqxu +`, + version.AppName, + ), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + channelID := args[0] + originalSender := args[1] + prefix := sdk.GetConfig().GetBech32AccountAddrPrefix() + senderBech32, err := keeper.DeriveIntermediateSender(channelID, originalSender, prefix) + if err != nil { + return err + } + fmt.Println(senderBech32) + return nil + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/ibchooks/hooks.go b/x/ibchooks/hooks.go new file mode 100644 index 0000000..b9f76de --- /dev/null +++ b/x/ibchooks/hooks.go @@ -0,0 +1,145 @@ +package ibchooks + +import ( + // ibc-go + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + + // external libraries + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" +) + +type Hooks interface{} + +type OnChanOpenInitOverrideHooks interface { + OnChanOpenInitOverride(im IBCMiddleware, ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string) (string, error) +} +type OnChanOpenInitBeforeHooks interface { + OnChanOpenInitBeforeHook(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string) +} +type OnChanOpenInitAfterHooks interface { + OnChanOpenInitAfterHook(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, version string, finalVersion string, err error) +} + +// OnChanOpenTry Hooks +type OnChanOpenTryOverrideHooks interface { + OnChanOpenTryOverride(im IBCMiddleware, ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string) (string, error) +} +type OnChanOpenTryBeforeHooks interface { + OnChanOpenTryBeforeHook(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string) +} +type OnChanOpenTryAfterHooks interface { + OnChanOpenTryAfterHook(ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID, channelID string, channelCap *capabilitytypes.Capability, counterparty channeltypes.Counterparty, counterpartyVersion string, version string, err error) +} + +// OnChanOpenAck Hooks +type OnChanOpenAckOverrideHooks interface { + OnChanOpenAckOverride(im IBCMiddleware, ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string) error +} +type OnChanOpenAckBeforeHooks interface { + OnChanOpenAckBeforeHook(ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string) +} +type OnChanOpenAckAfterHooks interface { + OnChanOpenAckAfterHook(ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string, err error) +} + +// OnChanOpenConfirm Hooks +type OnChanOpenConfirmOverrideHooks interface { + OnChanOpenConfirmOverride(im IBCMiddleware, ctx sdk.Context, portID, channelID string) error +} +type OnChanOpenConfirmBeforeHooks interface { + OnChanOpenConfirmBeforeHook(ctx sdk.Context, portID, channelID string) +} +type OnChanOpenConfirmAfterHooks interface { + OnChanOpenConfirmAfterHook(ctx sdk.Context, portID, channelID string, err error) +} + +// OnChanCloseInit Hooks +type OnChanCloseInitOverrideHooks interface { + OnChanCloseInitOverride(im IBCMiddleware, ctx sdk.Context, portID, channelID string) error +} +type OnChanCloseInitBeforeHooks interface { + OnChanCloseInitBeforeHook(ctx sdk.Context, portID, channelID string) +} +type OnChanCloseInitAfterHooks interface { + OnChanCloseInitAfterHook(ctx sdk.Context, portID, channelID string, err error) +} + +// OnChanCloseConfirm Hooks +type OnChanCloseConfirmOverrideHooks interface { + OnChanCloseConfirmOverride(im IBCMiddleware, ctx sdk.Context, portID, channelID string) error +} +type OnChanCloseConfirmBeforeHooks interface { + OnChanCloseConfirmBeforeHook(ctx sdk.Context, portID, channelID string) +} +type OnChanCloseConfirmAfterHooks interface { + OnChanCloseConfirmAfterHook(ctx sdk.Context, portID, channelID string, err error) +} + +// OnRecvPacket Hooks +type OnRecvPacketOverrideHooks interface { + OnRecvPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) ibcexported.Acknowledgement +} +type OnRecvPacketBeforeHooks interface { + OnRecvPacketBeforeHook(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) +} +type OnRecvPacketAfterHooks interface { + OnRecvPacketAfterHook(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, ack ibcexported.Acknowledgement) +} + +// OnAcknowledgementPacket Hooks +type OnAcknowledgementPacketOverrideHooks interface { + OnAcknowledgementPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error +} +type OnAcknowledgementPacketBeforeHooks interface { + OnAcknowledgementPacketBeforeHook(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) +} +type OnAcknowledgementPacketAfterHooks interface { + OnAcknowledgementPacketAfterHook(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, err error) +} + +// OnTimeoutPacket Hooks +type OnTimeoutPacketOverrideHooks interface { + OnTimeoutPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error +} +type OnTimeoutPacketBeforeHooks interface { + OnTimeoutPacketBeforeHook(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) +} +type OnTimeoutPacketAfterHooks interface { + OnTimeoutPacketAfterHook(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress, err error) +} + +// SendPacket Hooks +type SendPacketOverrideHooks interface { + SendPacketOverride(i ICS4Middleware, ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) (uint64, error) +} +type SendPacketBeforeHooks interface { + SendPacketBeforeHook(ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) +} +type SendPacketAfterHooks interface { + SendPacketAfterHook(ctx sdk.Context, chanCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte, err error) +} + +// WriteAcknowledgement Hooks +type WriteAcknowledgementOverrideHooks interface { + WriteAcknowledgementOverride(i ICS4Middleware, ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement) error +} +type WriteAcknowledgementBeforeHooks interface { + WriteAcknowledgementBeforeHook(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement) +} +type WriteAcknowledgementAfterHooks interface { + WriteAcknowledgementAfterHook(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement, err error) +} + +// GetAppVersion Hooks +type GetAppVersionOverrideHooks interface { + GetAppVersionOverride(i ICS4Middleware, ctx sdk.Context, portID, channelID string) (string, bool) +} +type GetAppVersionBeforeHooks interface { + GetAppVersionBeforeHook(ctx sdk.Context, portID, channelID string) +} +type GetAppVersionAfterHooks interface { + GetAppVersionAfterHook(ctx sdk.Context, portID, channelID string, result string, success bool) +} diff --git a/x/ibchooks/ibc_module.go b/x/ibchooks/ibc_module.go new file mode 100644 index 0000000..1ba3ba2 --- /dev/null +++ b/x/ibchooks/ibc_module.go @@ -0,0 +1,262 @@ +package ibchooks + +import ( + // ibc-go + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + + // external libraries + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" +) + +var _ porttypes.Middleware = &IBCMiddleware{} + +type IBCMiddleware struct { + App porttypes.IBCModule + ICS4Middleware *ICS4Middleware +} + +func NewIBCMiddleware(app porttypes.IBCModule, ics4 *ICS4Middleware) IBCMiddleware { + return IBCMiddleware{ + App: app, + ICS4Middleware: ics4, + } +} + +// OnChanOpenInit implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenInitOverrideHooks); ok { + return hook.OnChanOpenInitOverride(im, ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) + } + + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenInitBeforeHooks); ok { + hook.OnChanOpenInitBeforeHook(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) + } + + finalVersion, err := im.App.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) + + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenInitAfterHooks); ok { + hook.OnChanOpenInitAfterHook(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version, finalVersion, err) + } + return version, err +} + +// OnChanOpenTry implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenTryOverrideHooks); ok { + return hook.OnChanOpenTryOverride(im, ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) + } + + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenTryBeforeHooks); ok { + hook.OnChanOpenTryBeforeHook(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) + } + + version, err := im.App.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) + + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenTryAfterHooks); ok { + hook.OnChanOpenTryAfterHook(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion, version, err) + } + return version, err +} + +// OnChanOpenAck implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenAckOverrideHooks); ok { + return hook.OnChanOpenAckOverride(im, ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenAckBeforeHooks); ok { + hook.OnChanOpenAckBeforeHook(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + err := im.App.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenAckAfterHooks); ok { + hook.OnChanOpenAckAfterHook(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion, err) + } + + return err +} + +// OnChanOpenConfirm implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenConfirmOverrideHooks); ok { + return hook.OnChanOpenConfirmOverride(im, ctx, portID, channelID) + } + + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenConfirmBeforeHooks); ok { + hook.OnChanOpenConfirmBeforeHook(ctx, portID, channelID) + } + err := im.App.OnChanOpenConfirm(ctx, portID, channelID) + if hook, ok := im.ICS4Middleware.Hooks.(OnChanOpenConfirmAfterHooks); ok { + hook.OnChanOpenConfirmAfterHook(ctx, portID, channelID, err) + } + return err +} + +// OnChanCloseInit implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // Here we can remove the limits when a new channel is closed. For now, they can remove them manually on the contract + if hook, ok := im.ICS4Middleware.Hooks.(OnChanCloseInitOverrideHooks); ok { + return hook.OnChanCloseInitOverride(im, ctx, portID, channelID) + } + + if hook, ok := im.ICS4Middleware.Hooks.(OnChanCloseInitBeforeHooks); ok { + hook.OnChanCloseInitBeforeHook(ctx, portID, channelID) + } + err := im.App.OnChanCloseInit(ctx, portID, channelID) + if hook, ok := im.ICS4Middleware.Hooks.(OnChanCloseInitAfterHooks); ok { + hook.OnChanCloseInitAfterHook(ctx, portID, channelID, err) + } + + return err +} + +// OnChanCloseConfirm implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // Here we can remove the limits when a new channel is closed. For now, they can remove them manually on the contract + if hook, ok := im.ICS4Middleware.Hooks.(OnChanCloseConfirmOverrideHooks); ok { + return hook.OnChanCloseConfirmOverride(im, ctx, portID, channelID) + } + + if hook, ok := im.ICS4Middleware.Hooks.(OnChanCloseConfirmBeforeHooks); ok { + hook.OnChanCloseConfirmBeforeHook(ctx, portID, channelID) + } + err := im.App.OnChanCloseConfirm(ctx, portID, channelID) + if hook, ok := im.ICS4Middleware.Hooks.(OnChanCloseConfirmAfterHooks); ok { + hook.OnChanCloseConfirmAfterHook(ctx, portID, channelID, err) + } + + return err +} + +// OnRecvPacket implements the IBCMiddleware interface +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + if hook, ok := im.ICS4Middleware.Hooks.(OnRecvPacketOverrideHooks); ok { + return hook.OnRecvPacketOverride(im, ctx, packet, relayer) + } + + if hook, ok := im.ICS4Middleware.Hooks.(OnRecvPacketBeforeHooks); ok { + hook.OnRecvPacketBeforeHook(ctx, packet, relayer) + } + + ack := im.App.OnRecvPacket(ctx, packet, relayer) + + if hook, ok := im.ICS4Middleware.Hooks.(OnRecvPacketAfterHooks); ok { + hook.OnRecvPacketAfterHook(ctx, packet, relayer, ack) + } + + return ack +} + +// OnAcknowledgementPacket implements the IBCMiddleware interface +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + if hook, ok := im.ICS4Middleware.Hooks.(OnAcknowledgementPacketOverrideHooks); ok { + return hook.OnAcknowledgementPacketOverride(im, ctx, packet, acknowledgement, relayer) + } + if hook, ok := im.ICS4Middleware.Hooks.(OnAcknowledgementPacketBeforeHooks); ok { + hook.OnAcknowledgementPacketBeforeHook(ctx, packet, acknowledgement, relayer) + } + + err := im.App.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + + if hook, ok := im.ICS4Middleware.Hooks.(OnAcknowledgementPacketAfterHooks); ok { + hook.OnAcknowledgementPacketAfterHook(ctx, packet, acknowledgement, relayer, err) + } + + return err +} + +// OnTimeoutPacket implements the IBCMiddleware interface +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + if hook, ok := im.ICS4Middleware.Hooks.(OnTimeoutPacketOverrideHooks); ok { + return hook.OnTimeoutPacketOverride(im, ctx, packet, relayer) + } + + if hook, ok := im.ICS4Middleware.Hooks.(OnTimeoutPacketBeforeHooks); ok { + hook.OnTimeoutPacketBeforeHook(ctx, packet, relayer) + } + err := im.App.OnTimeoutPacket(ctx, packet, relayer) + if hook, ok := im.ICS4Middleware.Hooks.(OnTimeoutPacketAfterHooks); ok { + hook.OnTimeoutPacketAfterHook(ctx, packet, relayer, err) + } + + return err +} + +// SendPacket implements the ICS4 Wrapper interface +func (im IBCMiddleware) SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + return im.ICS4Middleware.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) +} + +// WriteAcknowledgement implements the ICS4 Wrapper interface +func (im IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, +) error { + return im.ICS4Middleware.WriteAcknowledgement(ctx, chanCap, packet, ack) +} + +func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return im.ICS4Middleware.GetAppVersion(ctx, portID, channelID) +} diff --git a/x/ibchooks/ics4_middleware.go b/x/ibchooks/ics4_middleware.go new file mode 100644 index 0000000..ce60c21 --- /dev/null +++ b/x/ibchooks/ics4_middleware.go @@ -0,0 +1,78 @@ +package ibchooks + +import ( + // ibc-go + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + + // external libraries + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" +) + +var _ porttypes.ICS4Wrapper = &ICS4Middleware{} + +type ICS4Middleware struct { + channel porttypes.ICS4Wrapper + + // Hooks + Hooks Hooks +} + +func NewICS4Middleware(channel porttypes.ICS4Wrapper, hooks Hooks) ICS4Middleware { + return ICS4Middleware{ + channel: channel, + Hooks: hooks, + } +} + +func (i ICS4Middleware) SendPacket(ctx sdk.Context, channelCap *capabilitytypes.Capability, _ string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) (sequence uint64, err error) { + if hook, ok := i.Hooks.(SendPacketOverrideHooks); ok { + return hook.SendPacketOverride(i, ctx, channelCap, sourceChannel, sourceChannel, timeoutHeight, timeoutTimestamp, data) + } + + if hook, ok := i.Hooks.(SendPacketBeforeHooks); ok { + hook.SendPacketBeforeHook(ctx, channelCap, sourceChannel, sourceChannel, timeoutHeight, timeoutTimestamp, data) + } + + seq, err := i.channel.SendPacket(ctx, channelCap, sourceChannel, sourceChannel, timeoutHeight, timeoutTimestamp, data) + + if hook, ok := i.Hooks.(SendPacketAfterHooks); ok { + hook.SendPacketAfterHook(ctx, channelCap, sourceChannel, sourceChannel, timeoutHeight, timeoutTimestamp, data, err) + } + + return seq, err +} + +func (i ICS4Middleware) WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI, ack ibcexported.Acknowledgement) error { + if hook, ok := i.Hooks.(WriteAcknowledgementOverrideHooks); ok { + return hook.WriteAcknowledgementOverride(i, ctx, chanCap, packet, ack) + } + + if hook, ok := i.Hooks.(WriteAcknowledgementBeforeHooks); ok { + hook.WriteAcknowledgementBeforeHook(ctx, chanCap, packet, ack) + } + err := i.channel.WriteAcknowledgement(ctx, chanCap, packet, ack) + if hook, ok := i.Hooks.(WriteAcknowledgementAfterHooks); ok { + hook.WriteAcknowledgementAfterHook(ctx, chanCap, packet, ack, err) + } + + return err +} + +func (i ICS4Middleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + if hook, ok := i.Hooks.(GetAppVersionOverrideHooks); ok { + return hook.GetAppVersionOverride(i, ctx, portID, channelID) + } + + if hook, ok := i.Hooks.(GetAppVersionBeforeHooks); ok { + hook.GetAppVersionBeforeHook(ctx, portID, channelID) + } + version, err := i.channel.GetAppVersion(ctx, portID, channelID) + if hook, ok := i.Hooks.(GetAppVersionAfterHooks); ok { + hook.GetAppVersionAfterHook(ctx, portID, channelID, version, err) + } + + return version, err +} diff --git a/x/ibchooks/keeper/keeper.go b/x/ibchooks/keeper/keeper.go new file mode 100644 index 0000000..0609f4b --- /dev/null +++ b/x/ibchooks/keeper/keeper.go @@ -0,0 +1,62 @@ +package keeper + +import ( + "fmt" + + "github.com/cometbft/cometbft/libs/log" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + + "github.com/terpnetwork/terp-core/v2/x/ibchooks/types" +) + +type ( + Keeper struct { + storeKey storetypes.StoreKey + } +) + +// NewKeeper returns a new instance of the x/ibchooks keeper +func NewKeeper( + storeKey storetypes.StoreKey, +) Keeper { + return Keeper{ + storeKey: storeKey, + } +} + +// Logger returns a logger for the x/tokenfactory module +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} + +func GetPacketKey(channel string, packetSequence uint64) []byte { + return []byte(fmt.Sprintf("%s::%d", channel, packetSequence)) +} + +// StorePacketCallback stores which contract will be listening for the ack or timeout of a packet +func (k Keeper) StorePacketCallback(ctx sdk.Context, channel string, packetSequence uint64, contract string) { + store := ctx.KVStore(k.storeKey) + store.Set(GetPacketKey(channel, packetSequence), []byte(contract)) +} + +// GetPacketCallback returns the bech32 addr of the contract that is expecting a callback from a packet +func (k Keeper) GetPacketCallback(ctx sdk.Context, channel string, packetSequence uint64) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(GetPacketKey(channel, packetSequence))) +} + +// DeletePacketCallback deletes the callback from storage once it has been processed +func (k Keeper) DeletePacketCallback(ctx sdk.Context, channel string, packetSequence uint64) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetPacketKey(channel, packetSequence)) +} + +func DeriveIntermediateSender(channel, originalSender, bech32Prefix string) (string, error) { + senderStr := fmt.Sprintf("%s/%s", channel, originalSender) + senderHash32 := address.Hash(types.SenderPrefix, []byte(senderStr)) + sender := sdk.AccAddress(senderHash32) + return sdk.Bech32ifyAddressBytes(bech32Prefix, sender) +} diff --git a/x/ibchooks/sdkmodule.go b/x/ibchooks/sdkmodule.go new file mode 100644 index 0000000..df15bee --- /dev/null +++ b/x/ibchooks/sdkmodule.go @@ -0,0 +1,126 @@ +package ibchooks + +import ( + "encoding/json" + + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + + "github.com/terpnetwork/terp-core/v2/x/ibchooks/client/cli" + "github.com/terpnetwork/terp-core/v2/x/ibchooks/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic defines the basic application module used by the ibc-hooks module. +type AppModuleBasic struct{} + +var _ module.AppModuleBasic = AppModuleBasic{} + +// Name returns the ibc-hooks module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the ibc-hooks module's types on the given LegacyAmino codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(_ *codec.LegacyAmino) {} + +// RegisterInterfaces registers the module's interface types. +func (b AppModuleBasic) RegisterInterfaces(_ cdctypes.InterfaceRegistry) {} + +// DefaultGenesis returns default genesis state as raw bytes for the +// module. +func (AppModuleBasic) DefaultGenesis(_ codec.JSONCodec) json.RawMessage { + emptyString := "{}" + return []byte(emptyString) +} + +// ValidateGenesis performs genesis state validation for the ibc-hooks module. +func (AppModuleBasic) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConfig, _ json.RawMessage) error { + return nil +} + +// RegisterRESTRoutes registers the REST routes for the ibc-hooks module. +func (AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the ibc-hooks module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(_ client.Context, _ *runtime.ServeMux) {} + +// GetTxCmd returns no root tx command for the ibc-hooks module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { return nil } + +// GetQueryCmd returns the root query command for the ibc-hooks module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// ___________________________________________________________________________ + +// AppModule implements an application module for the ibc-hooks module. +type AppModule struct { + AppModuleBasic + + authKeeper authkeeper.AccountKeeper +} + +// NewAppModule creates a new AppModule object. +func NewAppModule(ak authkeeper.AccountKeeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + authKeeper: ak, + } +} + +// Name returns the ibc-hooks module's name. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterInvariants registers the ibc-hooks module invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// QuerierRoute returns the module's querier route name. +func (AppModule) QuerierRoute() string { + return "" +} + +// RegisterServices registers a gRPC query service to respond to the +// module-specific gRPC queries. +func (am AppModule) RegisterServices(_ module.Configurator) { +} + +// InitGenesis performs genesis initialization for the ibc-hooks module. It returns +// no validator updates. +func (am AppModule) InitGenesis(_ sdk.Context, _ codec.JSONCodec, _ json.RawMessage) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +func (am AppModule) ExportGenesis(_ sdk.Context, _ codec.JSONCodec) json.RawMessage { + return json.RawMessage([]byte("{}")) +} + +// BeginBlock returns the begin blocker for the ibc-hooks module. +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) { +} + +// EndBlock returns the end blocker for the ibc-hooks module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } diff --git a/x/ibchooks/types/errors.go b/x/ibchooks/types/errors.go new file mode 100644 index 0000000..6c85a36 --- /dev/null +++ b/x/ibchooks/types/errors.go @@ -0,0 +1,15 @@ +package types + +import errorsmod "cosmossdk.io/errors" + +var ( + ErrBadMetadataFormatMsg = "wasm metadata not properly formatted for: '%v'. %s" + ErrBadExecutionMsg = "cannot execute contract: %v" + + ErrMsgValidation = errorsmod.Register("wasm-hooks", 2, "error in wasmhook message validation") + ErrMarshaling = errorsmod.Register("wasm-hooks", 3, "cannot marshal the ICS20 packet") + ErrInvalidPacket = errorsmod.Register("wasm-hooks", 4, "invalid packet data") + ErrBadResponse = errorsmod.Register("wasm-hooks", 5, "cannot create response") + ErrWasmError = errorsmod.Register("wasm-hooks", 6, "wasm error") + ErrBadSender = errorsmod.Register("wasm-hooks", 7, "bad sender") +) diff --git a/x/ibchooks/types/keys.go b/x/ibchooks/types/keys.go new file mode 100644 index 0000000..9a3e7de --- /dev/null +++ b/x/ibchooks/types/keys.go @@ -0,0 +1,8 @@ +package types + +const ( + ModuleName = "ibchooks" + StoreKey = "hooks-for-ibc" // not using the module name because of collisions with key "ibc" + IBCCallbackKey = "ibc_callback" + SenderPrefix = "ibc-wasm-hook-intermediary" +) diff --git a/x/ibchooks/wasm_hook.go b/x/ibchooks/wasm_hook.go new file mode 100644 index 0000000..9db9716 --- /dev/null +++ b/x/ibchooks/wasm_hook.go @@ -0,0 +1,421 @@ +package ibchooks + +import ( + "encoding/json" + "fmt" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + + "github.com/terpnetwork/terp-core/v2/osmoutils" + "github.com/terpnetwork/terp-core/v2/x/ibchooks/keeper" + "github.com/terpnetwork/terp-core/v2/x/ibchooks/types" +) + +type ContractAck struct { + ContractResult []byte `json:"contract_result"` + IbcAck []byte `json:"ibc_ack"` +} + +type WasmHooks struct { + WasmKeeper *wasmkeeper.Keeper + ContractKeeper *wasmkeeper.PermissionedKeeper + ibcHooksKeeper *keeper.Keeper + bech32PrefixAccAddr string +} + +func NewWasmHooks(ibcHooksKeeper *keeper.Keeper, contractKeeper *wasmkeeper.PermissionedKeeper, wasmKeeper *wasmkeeper.Keeper, bech32PrefixAccAddr string) WasmHooks { + return WasmHooks{ + WasmKeeper: wasmKeeper, + ContractKeeper: contractKeeper, + ibcHooksKeeper: ibcHooksKeeper, + bech32PrefixAccAddr: bech32PrefixAccAddr, + } +} + +func (h WasmHooks) ProperlyConfigured() bool { + return h.ContractKeeper != nil && h.ibcHooksKeeper != nil +} + +func (h WasmHooks) OnRecvPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) ibcexported.Acknowledgement { + if !h.ProperlyConfigured() { + // Not configured + return im.App.OnRecvPacket(ctx, packet, relayer) + } + isIcs20, data := isIcs20Packet(packet) + if !isIcs20 { + return im.App.OnRecvPacket(ctx, packet, relayer) + } + + // Validate the memo + isWasmRouted, contractAddr, msgBytes, err := ValidateAndParseMemo(data.GetMemo(), data.Receiver) + if !isWasmRouted { + return im.App.OnRecvPacket(ctx, packet, relayer) + } + if err != nil { + return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrMsgValidation, err.Error()) + } + if msgBytes == nil || contractAddr == nil { // This should never happen + return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrMsgValidation) + } + + // Calculate the receiver / contract caller based on the packet's channel and sender + channel := packet.GetDestChannel() + sender := data.GetSender() + senderBech32, err := keeper.DeriveIntermediateSender(channel, sender, h.bech32PrefixAccAddr) + if err != nil { + return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrBadSender, fmt.Sprintf("cannot convert sender address %s/%s to bech32: %s", channel, sender, err.Error())) + } + + // The funds sent on this packet need to be transferred to the intermediary account for the sender. + // For this, we override the ICS20 packet's Receiver (essentially hijacking the funds to this new address) + // and execute the underlying OnRecvPacket() call (which should eventually land on the transfer app's + // relay.go and send the sunds to the intermediary account. + // + // If that succeeds, we make the contract call + data.Receiver = senderBech32 + bz, err := json.Marshal(data) + if err != nil { + return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrMarshaling, err.Error()) + } + packet.Data = bz + + // Execute the receive + ack := im.App.OnRecvPacket(ctx, packet, relayer) + if !ack.Success() { + return ack + } + + amount, ok := sdk.NewIntFromString(data.GetAmount()) + if !ok { + // This should never happen, as it should've been caught in the underlaying call to OnRecvPacket, + // but returning here for completeness + return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrInvalidPacket, "Amount is not an int") + } + + // The packet's denom is the denom in the sender chain. This needs to be converted to the local denom. + denom := osmoutils.MustExtractDenomFromPacketOnRecv(packet) + funds := sdk.NewCoins(sdk.NewCoin(denom, amount)) + + // Execute the contract + execMsg := wasmtypes.MsgExecuteContract{ + Sender: senderBech32, + Contract: contractAddr.String(), + Msg: msgBytes, + Funds: funds, + } + response, err := h.execWasmMsg(ctx, &execMsg) + if err != nil { + return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrWasmError, err.Error()) + } + + fullAck := ContractAck{ContractResult: response.Data, IbcAck: ack.Acknowledgement()} + bz, err = json.Marshal(fullAck) + if err != nil { + return osmoutils.NewEmitErrorAcknowledgement(ctx, types.ErrBadResponse, err.Error()) + } + + return channeltypes.NewResultAcknowledgement(bz) +} + +func (h WasmHooks) execWasmMsg(ctx sdk.Context, execMsg *wasmtypes.MsgExecuteContract) (*wasmtypes.MsgExecuteContractResponse, error) { + if err := execMsg.ValidateBasic(); err != nil { + return nil, fmt.Errorf(types.ErrBadExecutionMsg, err.Error()) + } + wasmMsgServer := wasmkeeper.NewMsgServerImpl(h.WasmKeeper) + return wasmMsgServer.ExecuteContract(sdk.WrapSDKContext(ctx), execMsg) +} + +func isIcs20Packet(packet channeltypes.Packet) (isIcs20 bool, ics20data transfertypes.FungibleTokenPacketData) { + var data transfertypes.FungibleTokenPacketData + if err := json.Unmarshal(packet.GetData(), &data); err != nil { + return false, data + } + return true, data +} + +// jsonStringHasKey parses the memo as a json object and checks if it contains the key. +func jsonStringHasKey(memo, key string) (found bool, jsonObject map[string]interface{}) { + jsonObject = make(map[string]interface{}) + + // If there is no memo, the packet was either sent with an earlier version of IBC, or the memo was + // intentionally left blank. Nothing to do here. Ignore the packet and pass it down the stack. + if len(memo) == 0 { + return false, jsonObject + } + + // the jsonObject must be a valid JSON object + err := json.Unmarshal([]byte(memo), &jsonObject) + if err != nil { + return false, jsonObject + } + + // If the key doesn't exist, there's nothing to do on this hook. Continue by passing the packet + // down the stack + _, ok := jsonObject[key] + if !ok { + return false, jsonObject + } + + return true, jsonObject +} + +func ValidateAndParseMemo(memo string, receiver string) (isWasmRouted bool, contractAddr sdk.AccAddress, msgBytes []byte, err error) { + isWasmRouted, metadata := jsonStringHasKey(memo, "wasm") + if !isWasmRouted { + return isWasmRouted, sdk.AccAddress{}, nil, nil + } + + wasmRaw := metadata["wasm"] + + // Make sure the wasm key is a map. If it isn't, ignore this packet + wasm, ok := wasmRaw.(map[string]interface{}) + if !ok { + return isWasmRouted, sdk.AccAddress{}, nil, + fmt.Errorf(types.ErrBadMetadataFormatMsg, memo, "wasm metadata is not a valid JSON map object") + } + + // Get the contract + contract, ok := wasm["contract"].(string) + if !ok { + // The tokens will be returned + return isWasmRouted, sdk.AccAddress{}, nil, + fmt.Errorf(types.ErrBadMetadataFormatMsg, memo, `Could not find key wasm["contract"]`) + } + + contractAddr, err = sdk.AccAddressFromBech32(contract) + if err != nil { + return isWasmRouted, sdk.AccAddress{}, nil, + fmt.Errorf(types.ErrBadMetadataFormatMsg, memo, `wasm["contract"] is not a valid bech32 address`) + } + + // The contract and the receiver should be the same for the packet to be valid + if contract != receiver { + return isWasmRouted, sdk.AccAddress{}, nil, + fmt.Errorf(types.ErrBadMetadataFormatMsg, memo, `wasm["contract"] should be the same as the receiver of the packet`) + } + + // Ensure the message key is provided + if wasm["msg"] == nil { + return isWasmRouted, sdk.AccAddress{}, nil, + fmt.Errorf(types.ErrBadMetadataFormatMsg, memo, `Could not find key wasm["msg"]`) + } + + // Make sure the msg key is a map. If it isn't, return an error + _, ok = wasm["msg"].(map[string]interface{}) + if !ok { + return isWasmRouted, sdk.AccAddress{}, nil, + fmt.Errorf(types.ErrBadMetadataFormatMsg, memo, `wasm["msg"] is not a map object`) + } + + // Get the message string by serializing the map + msgBytes, err = json.Marshal(wasm["msg"]) + if err != nil { + // The tokens will be returned + return isWasmRouted, sdk.AccAddress{}, nil, + fmt.Errorf(types.ErrBadMetadataFormatMsg, memo, err.Error()) + } + + return isWasmRouted, contractAddr, msgBytes, nil +} + +func (h WasmHooks) SendPacketOverride(i ICS4Middleware, ctx sdk.Context, chanCap *capabilitytypes.Capability, packet ibcexported.PacketI) error { + height := clienttypes.Height{ + RevisionNumber: packet.GetTimeoutHeight().GetRevisionHeight(), + RevisionHeight: packet.GetTimeoutHeight().GetRevisionHeight(), + } + + concretePacket, ok := packet.(channeltypes.Packet) + if !ok { + if _, err := i.channel.SendPacket(ctx, chanCap, + packet.GetSourcePort(), + packet.GetSourceChannel(), + height, + packet.GetTimeoutTimestamp(), + packet.GetData()); err != nil { + return err + } + } + + isIcs20, data := isIcs20Packet(concretePacket) + if !isIcs20 { + if _, err := i.channel.SendPacket(ctx, chanCap, + packet.GetSourcePort(), + packet.GetSourceChannel(), + height, + packet.GetTimeoutTimestamp(), + packet.GetData()); err != nil { + return err + } + } + + isCallbackRouted, metadata := jsonStringHasKey(data.GetMemo(), types.IBCCallbackKey) + if !isCallbackRouted { + if _, err := i.channel.SendPacket(ctx, chanCap, + packet.GetSourcePort(), + packet.GetSourceChannel(), + height, + packet.GetTimeoutTimestamp(), + packet.GetData()); err != nil { + return err + } + } + + // We remove the callback metadata from the memo as it has already been processed. + + // If the only available key in the memo is the callback, we should remove the memo + // from the data completely so the packet is sent without it. + // This way receiver chains that are on old versions of IBC will be able to process the packet + + callbackRaw := metadata[types.IBCCallbackKey] // This will be used later. + delete(metadata, types.IBCCallbackKey) + bzMetadata, err := json.Marshal(metadata) + if err != nil { + return errorsmod.Wrap(err, "Send packet with callback error") + } + stringMetadata := string(bzMetadata) + if stringMetadata == "{}" { + data.Memo = "" + } else { + data.Memo = stringMetadata + } + dataBytes, err := json.Marshal(data) + if err != nil { + return errorsmod.Wrap(err, "Send packet with callback error") + } + + packetWithoutCallbackMemo := channeltypes.Packet{ + Sequence: concretePacket.Sequence, + SourcePort: concretePacket.SourcePort, + SourceChannel: concretePacket.SourceChannel, + DestinationPort: concretePacket.DestinationPort, + DestinationChannel: concretePacket.DestinationChannel, + Data: dataBytes, + TimeoutTimestamp: concretePacket.TimeoutTimestamp, + TimeoutHeight: concretePacket.TimeoutHeight, + } + + _, err = i.channel.SendPacket(ctx, chanCap, + packetWithoutCallbackMemo.GetSourcePort(), + packetWithoutCallbackMemo.GetSourceChannel(), + height, + packetWithoutCallbackMemo.GetTimeoutTimestamp(), + packetWithoutCallbackMemo.GetData(), + ) + if err != nil { + return err + } + + // Make sure the callback contract is a string and a valid bech32 addr. If it isn't, ignore this packet + contract, ok := callbackRaw.(string) + if !ok { + return nil + } + _, err = sdk.AccAddressFromBech32(contract) + if err != nil { + return nil + } + + h.ibcHooksKeeper.StorePacketCallback(ctx, packet.GetSourceChannel(), packet.GetSequence(), contract) + return nil +} + +func (h WasmHooks) OnAcknowledgementPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { + err := im.App.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + if err != nil { + return err + } + + if !h.ProperlyConfigured() { + // Not configured. Return from the underlying implementation + return nil + } + + contract := h.ibcHooksKeeper.GetPacketCallback(ctx, packet.GetSourceChannel(), packet.GetSequence()) + if contract == "" { + // No callback configured + return nil + } + + contractAddr, err := sdk.AccAddressFromBech32(contract) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") // The callback configured is not a bech32. Error out + } + + success := "false" + if !osmoutils.IsAckError(acknowledgement) { + success = "true" + } + + // Notify the sender that the ack has been received + ackAsJSON, err := json.Marshal(acknowledgement) + if err != nil { + // If the ack is not a json object, error + return err + } + + sudoMsg := []byte(fmt.Sprintf( + `{"ibc_lifecycle_complete": {"ibc_ack": {"channel": "%s", "sequence": %d, "ack": %s, "success": %s}}}`, + packet.SourceChannel, packet.Sequence, ackAsJSON, success)) + _, err = h.ContractKeeper.Sudo(ctx, contractAddr, sudoMsg) + if err != nil { + // error processing the callback + // ToDo: Open Question: Should we also delete the callback here? + return errorsmod.Wrap(err, "Ack callback error") + } + h.ibcHooksKeeper.DeletePacketCallback(ctx, packet.GetSourceChannel(), packet.GetSequence()) + return nil +} + +func (h WasmHooks) OnTimeoutPacketOverride(im IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { + err := im.App.OnTimeoutPacket(ctx, packet, relayer) + if err != nil { + return err + } + + if !h.ProperlyConfigured() { + // Not configured. Return from the underlying implementation + return nil + } + + contract := h.ibcHooksKeeper.GetPacketCallback(ctx, packet.GetSourceChannel(), packet.GetSequence()) + if contract == "" { + // No callback configured + return nil + } + + contractAddr, err := sdk.AccAddressFromBech32(contract) + if err != nil { + return errorsmod.Wrap(err, "Timeout callback error") // The callback configured is not a bech32. Error out + } + + sudoMsg := []byte(fmt.Sprintf( + `{"ibc_lifecycle_complete": {"ibc_timeout": {"channel": "%s", "sequence": %d}}}`, + packet.SourceChannel, packet.Sequence)) + _, err = h.ContractKeeper.Sudo(ctx, contractAddr, sudoMsg) + if err != nil { + // error processing the callback. This could be because the contract doesn't implement the message type to + // process the callback. Retrying this will not help, so we can delete the callback from storage. + // Since the packet has timed out, we don't expect any other responses that may trigger the callback. + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + "ibc-timeout-callback-error", + sdk.NewAttribute("contract", contractAddr.String()), + sdk.NewAttribute("message", string(sudoMsg)), + sdk.NewAttribute("error", err.Error()), + ), + }) + } + h.ibcHooksKeeper.DeletePacketCallback(ctx, packet.GetSourceChannel(), packet.GetSequence()) + return nil +} diff --git a/x/wasm/Governance.md b/x/wasm/Governance.md deleted file mode 100644 index 8760412..0000000 --- a/x/wasm/Governance.md +++ /dev/null @@ -1,205 +0,0 @@ -# Governance - -This document gives an overview of how the various governance -proposals interact with the CosmWasm contract lifecycle. It is -a high-level, technical introduction meant to provide context before -looking into the code, or constructing proposals. - -## Proposal Types -We have added 9 new wasm specific proposal types that cover the contract's live cycle and authorization: - -* `StoreCodeProposal` - upload a wasm binary -* `InstantiateContractProposal` - instantiate a wasm contract -* `MigrateContractProposal` - migrate a wasm contract to a new code version -* `SudoContractProposal` - call into the protected `sudo` entry point of a contract -* `ExecuteContractProposal` - execute a wasm contract as an arbitrary user -* `UpdateAdminProposal` - set a new admin for a contract -* `ClearAdminProposal` - clear admin for a contract to prevent further migrations -* `PinCodes` - pin the given code ids in cache. This trades memory for reduced startup time and lowers gas cost -* `UnpinCodes` - unpin the given code ids from the cache. This frees up memory and returns to standard speed and gas cost -* `UpdateInstantiateConfigProposal` - update instantiate permissions to a list of given code ids. -* `StoreAndInstantiateContractProposal` - upload and instantiate a wasm contract. - -For details see the proposal type [implementation](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/types/proposal.go) - -### Unit tests -[Proposal type validations](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/types/proposal_test.go) - -## Proposal Handler -The [wasmd proposal_handler](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/keeper/proposal_handler.go) implements the `gov.Handler` function -and executes the wasmd proposal types after a successful tally. - -The proposal handler uses a [`GovAuthorizationPolicy`](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/keeper/authz_policy.go#L29) to bypass the existing contract's authorization policy. - -### Tests -* [Integration: Submit and execute proposal](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/keeper/proposal_integration_test.go) - -## Gov Integration -The wasmd proposal handler can be added to the gov router in the [abci app](https://github.com/terpnetwork/terp-core/blob/master/app/app.go#L306) -to receive proposal execution calls. -```go -govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.wasmKeeper, enabledProposals)) -``` - -## Wasmd Authorization Settings - -Settings via sdk `params` module: -- `code_upload_access` - who can upload a wasm binary: `Nobody`, `Everybody`, `OnlyAddress` -- `instantiate_default_permission` - platform default, who can instantiate a wasm binary when the code owner has not set it - -See [params.go](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/types/params.go) - -### Init Params Via Genesis - -```json - "wasm": { - "params": { - "code_upload_access": { - "permission": "Everybody" - }, - "instantiate_default_permission": "Everybody" - } - }, -``` - -The values can be updated via gov proposal implemented in the `params` module. - -### Update Params Via [ParamChangeProposal](https://github.com/cosmos/cosmos-sdk/blob/v0.45.3/proto/cosmos/params/v1beta1/params.proto#L10) -Example to submit a parameter change gov proposal: -```sh -wasmd tx gov submit-proposal param-change --from validator --chain-id=testing -b block -``` -#### Content examples -* Disable wasm code uploads -```json -{ - "title": "Foo", - "description": "Bar", - "changes": [ - { - "subspace": "wasm", - "key": "uploadAccess", - "value": { - "permission": "Nobody" - } - } - ], - "deposit": "" -} -``` -* Allow wasm code uploads for everybody -```json -{ - "title": "Foo", - "description": "Bar", - "changes": [ - { - "subspace": "wasm", - "key": "uploadAccess", - "value": { - "permission": "Everybody" - } - } - ], - "deposit": "" -} -``` - -* Restrict code uploads to a single address -```json -{ - "title": "Foo", - "description": "Bar", - "changes": [ - { - "subspace": "wasm", - "key": "uploadAccess", - "value": { - "permission": "OnlyAddress", - "address": "cosmos1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq0fr2sh" - } - } - ], - "deposit": "" -} -``` -* Set chain **default** instantiation settings to nobody -```json -{ - "title": "Foo", - "description": "Bar", - "changes": [ - { - "subspace": "wasm", - "key": "instantiateAccess", - "value": "Nobody" - } - ], - "deposit": "" -} -``` -* Set chain **default** instantiation settings to everybody -```json -{ - "title": "Foo", - "description": "Bar", - "changes": [ - { - "subspace": "wasm", - "key": "instantiateAccess", - "value": "Everybody" - } - ], - "deposit": "" -} -``` - -### Enable gov proposals at **compile time**. -As gov proposals bypass the existing authorization policy they are disabled and require to be enabled at compile time. -``` --X github.com/terpnetwork/terp-core/app.ProposalsEnabled=true - enable all x/wasm governance proposals (default false) --X github.com/terpnetwork/terp-core/app.EnableSpecificProposals=MigrateContract,UpdateAdmin,ClearAdmin - enable a subset of the x/wasm governance proposal types (overrides ProposalsEnabled) -``` - -The `ParamChangeProposal` is always enabled. - -### Tests -* [params validation unit tests](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/types/params_test.go) -* [genesis validation tests](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/types/genesis_test.go) -* [policy integration tests](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/keeper/keeper_test.go) - -## CLI - -```shell script - wasmd tx gov submit-proposal [command] - -Available Commands: - wasm-store Submit a wasm binary proposal - instantiate-contract Submit an instantiate wasm contract proposal - migrate-contract Submit a migrate wasm contract to a new code version proposal - set-contract-admin Submit a new admin for a contract proposal - clear-contract-admin Submit a clear admin for a contract to prevent further migrations proposal -... -``` -## Rest -New [`ProposalHandlers`](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/client/proposal_handler.go) - -* Integration -```shell script -gov.NewAppModuleBasic(append(wasmclient.ProposalHandlers, paramsclient.ProposalHandler, distr.ProposalHandler, upgradeclient.ProposalHandler)...), -``` -In [abci app](https://github.com/terpnetwork/terp-core/blob/master/app/app.go#L109) - -### Tests -* [Rest Unit tests](https://github.com/terpnetwork/terp-core/blob/master/x/wasm/client/proposal_handler_test.go) -* [Rest smoke LCD test](https://github.com/terpnetwork/terp-core/blob/master/lcd_test/wasm_test.go) - - - -## Pull requests -* https://github.com/terpnetwork/terp-core/pull/190 -* https://github.com/terpnetwork/terp-core/pull/186 -* https://github.com/terpnetwork/terp-core/pull/183 -* https://github.com/terpnetwork/terp-core/pull/180 -* https://github.com/terpnetwork/terp-core/pull/179 -* https://github.com/terpnetwork/terp-core/pull/173 diff --git a/x/wasm/IBC.md b/x/wasm/IBC.md deleted file mode 100644 index c3cd0a0..0000000 --- a/x/wasm/IBC.md +++ /dev/null @@ -1,137 +0,0 @@ -# IBC specification - -This documents how CosmWasm contracts are expected to interact with IBC. - -## General Concepts - -**IBC Enabled** - when instantiating a contract, we detect if it supports IBC messages. - We require "feature flags" in the contract/vm handshake to ensure compatibility - for features like staking or chain-specific extensions. IBC functionality will require - another "feature flag", and the list of "enabled features" can be returned to the `x/wasm` - module to control conditional IBC behavior. - - If this feature is enabled, it is considered "IBC Enabled", and that info will - be stored in the ContractInfo. (For mock, we assume all contracts are IBC enabled) - -Also, please read the [IBC Docs](https://docs.cosmos.network/master/ibc/overview.html) -for detailed descriptions of the terms *Port*, *Client*, *Connection*, -and *Channel* - -## Overview - -We use "One Port per Contract", which is the most straight-forward mapping, treating each contract -like a module. It does lead to very long portIDs however. Pay special attention to both the Channel establishment -(which should be compatible with standard ICS20 modules without changes on their part), as well -as how contracts can properly identify their counterparty. - -(We considered on port for the `x/wasm` module and multiplexing on it, but [dismissed that idea](#rejected-ideas)) - -* Upon `Instantiate`, if a contract is *IBC Enabled*, we dynamically - bind a port for this contract. The port name is `wasm.`, - eg. `wasm.cosmos1hmdudppzceg27qsuq707tjg8rkgj7g5hnvnw29` -* If a *Channel* is being established with a registered `wasm.xyz` port, - the `x/wasm.Keeper` will handle this and call into the appropriate - contract to determine supported protocol versions during the - [`ChanOpenTry` and `ChanOpenAck` phases](https://docs.cosmos.network/master/ibc/overview.html#channels). - (See [Channel Handshake Version Negotiation](https://docs.cosmos.network/master/ibc/custom.html#channel-handshake-version-negotiation)) -* Both the *Port* and the *Channel* are fully owned by one contract. -* `x/wasm` will allow both *ORDERED* and *UNORDERED* channels and pass that mode - down to the contract in `OnChanOpenTry`, so the contract can decide if it accepts - the mode. We will recommend the contract developers stick with *ORDERED* channels - for custom protocols unless they can reason about async packet timing. -* When sending a packet, the CosmWasm contract must specify the local *ChannelID*. - As there is a unique *PortID* per contract, that is filled in by `x/wasm` - to produce the globally unique `(PortID, ChannelID)` -* When receiving a Packet (or Ack or Timeout), the contracts receives the local - *ChannelID* it came from, as well as the packet that was sent by the counterparty. -* When receiving an Ack or Timeout packet, the contract also receives the - original packet that it sent earlier. -* We do not support multihop packets in this model (they are rejected by `x/wasm`). - They are currently not fully specified nor implemented in IBC 1.0, so let us - simplify our model until this is well established - -## Workflow - -Establishing *Clients* and *Connections* is out of the scope of this -module and must be created by the same means as for `ibc-transfer` -(via the [go cli](https://github.com/cosmos/relayer) or better [ts-relayer](https://github.com/confio/ts-relayer)). -`x/wasm` will bind a unique *Port* for each "IBC Enabled" contract. - -For mocks, all the Packet Handling and Channel Lifecycle Hooks are routed -to some Golang stub handler, but containing the contract address, so we -can perform contract-specific actions for each packet. In a real setting, -we route to the contract that owns the port/channel and call one of it's various -entry points. - -Please refer to the CosmWasm repo for all -[details on the IBC API from the point of view of a CosmWasm contract](https://github.com/CosmWasm/cosmwasm/blob/main/IBC.md). - -## Future Ideas - -Here are some ideas we may add in the future - -### Dynamic Ports and Channels - -* multiple ports per contract -* elastic ports that can be assigned to different contracts -* transfer of channels to another contract - -This is inspired by the Agoric design, but also adds considerable complexity to both the `x/wasm` -implementation as well as the correctness reasoning of any given contract. This will not be -available in the first version of our "IBC Enabled contracts", but we can consider it for later, -if there are concrete user cases that would significantly benefit from this added complexity. - -### Add multihop support - -Once the ICS and IBC specs fully establish how multihop packets work, we should add support for that. -Both on setting up the routes with OpenChannel, as well as acting as an intermediate relayer (if that is possible) - -## Rejected Ideas - -### One Port per Module - -We decided on "one port per contract", especially after the IBC team raised -the max length on port names to allow `wasm-` to be a valid port. -Here are the arguments for "one port for x/wasm" vs "one port per contract". Here -was an alternate proposal: - -In this approach, the `x/wasm` module just binds one port to handle all -modules. This can be well defined name like `wasm`. Since we always -have `(ChannelID, PortID)` for routing messages, we can reuse one port -for all contracts as long as we have a clear way to map the `ChannelID` -to a specific contract when it is being established. - - -* On genesis we bind the port `wasm` for all communication with the `x/wasm` - module. -* The *Port* is fully owned by `x/wasm` -* Each *Channel* is fully owned by one contract. -* `x/wasm` only accepts *ORDERED Channels* for simplicity of contract - correctness. - -To clarify: - -* When a *Channel* is being established with port `wasm`, the - `x/wasm.Keeper` must be able to identify for which contract this - is destined. **how to do so**?? - * One idea: the channel name must be the contract address. This means - (`wasm`, `cosmos13d...`) will map to the given contract in the wasm module. - The problem with this is that if two contracts from chainA want to - connect to the same contracts on chainB, they will want to claim the - same *ChannelID* and *PortID*. Not sure how to differentiate multiple - parties in this way. - * Other ideas: have a special field we send on `OnChanOpenInit` that - specifies the destination contract, and allow any *ChannelID*. - However, looking at [`OnChanOpenInit` function signature](https://docs.cosmos.network/master/ibc/custom.html#implement-ibcmodule-interface-and-callbacks), - I don't see a place to put this extra info, without abusing the version field, - which is a [specified field](https://docs.cosmos.network/master/ibc/custom.html#channel-handshake-version-negotiation): - ``` - Versions must be strings but can implement any versioning structure. - If your application plans to have linear releases then semantic versioning is recommended. - ... - Valid version selection includes selecting a compatible version identifier with a subset - of features supported by your application for that version. - ... - ICS20 currently implements basic string matching with a - single supported version. - ``` \ No newline at end of file diff --git a/x/wasm/README.md b/x/wasm/README.md deleted file mode 100644 index cba9c5c..0000000 --- a/x/wasm/README.md +++ /dev/null @@ -1,219 +0,0 @@ -# Wasm Module - -This should be a brief overview of the functionality - -## Configuration - -You can add the following section to `config/app.toml`: - -```toml -[wasm] -# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries -query_gas_limit = 300000 -# This defines the memory size for Wasm modules that we can keep cached to speed-up instantiation -# The value is in MiB not bytes -memory_cache_size = 300 -``` - -The values can also be set via CLI flags on with the `start` command: -```shell script ---wasm.memory_cache_size uint32 Sets the size in MiB (NOT bytes) of an in-memory cache for wasm modules. Set to 0 to disable. (default 100) ---wasm.query_gas_limit uint Set the max gas that can be spent on executing a query with a Wasm contract (default 3000000) -``` - -## Events - -A number of events are returned to allow good indexing of the transactions from smart contracts. - -Every call to Instantiate or Execute will be tagged with the info on the contract that was executed and who executed it. -It should look something like this (with different addresses). The module is always `wasm`, and `code_id` is only present -when Instantiating a contract, so you can subscribe to new instances, it is omitted on Execute. There is also an `action` tag -which is auto-added by the Cosmos SDK and has a value of either `store-code`, `instantiate` or `execute` depending on which message -was sent: - -```json -{ - "Type": "message", - "Attr": [ - { - "key": "module", - "value": "wasm" - }, - { - "key": "action", - "value": "instantiate" - }, - { - "key": "signer", - "value": "cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x" - }, - { - "key": "code_id", - "value": "1" - }, - { - "key": "_contract_address", - "value": "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr" - } - ] -} -``` - -If any funds were transferred to the contract as part of the message, or if the contract released funds as part of it's executions, -it will receive the typical events associated with sending tokens from bank. In this case, we instantiate the contract and -provide a initial balance in the same `MsgInstantiateContract`. We see the following events in addition to the above one: - -```json -[ - { - "Type": "transfer", - "Attr": [ - { - "key": "recipient", - "value": "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr" - }, - { - "key": "sender", - "value": "cosmos1ffnqn02ft2psvyv4dyr56nnv6plllf9pm2kpmv" - }, - { - "key": "amount", - "value": "100000denom" - } - ] - } -] -``` - -Finally, the contract itself can emit a "custom event" on Execute only (not on Init). -There is one event per contract, so if one contract calls a second contract, you may receive -one event for the original contract and one for the re-invoked contract. All attributes from the contract are passed through verbatim, -and we add a `_contract_address` attribute that contains the actual contract that emitted that event. -Here is an example from the escrow contract successfully releasing funds to the destination address: - -```json -{ - "Type": "wasm", - "Attr": [ - { - "key": "_contract_address", - "value": "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr" - }, - { - "key": "action", - "value": "release" - }, - { - "key": "destination", - "value": "cosmos14k7v7ms4jxkk2etmg9gljxjm4ru3qjdugfsflq" - } - ] -} -``` - -### Pulling this all together - -We will invoke an escrow contract to release to the designated beneficiary. -The escrow was previously loaded with `100000denom` (from the above example). -In this transaction, we send `5000denom` along with the `MsgExecuteContract` -and the contract releases the entire funds (`105000denom`) to the beneficiary. - -We will see all the following events, where you should be able to reconstruct the actions -(remember there are two events for each transfer). We see (1) the initial transfer of funds -to the contract, (2) the contract custom event that it released funds (3) the transfer of funds -from the contract to the beneficiary and (4) the generic x/wasm event stating that the contract -was executed (which always appears, while 2 is optional and has information as reliable as the contract): - -```json -[ - { - "Type": "transfer", - "Attr": [ - { - "key": "recipient", - "value": "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr" - }, - { - "key": "sender", - "value": "cosmos1zm074khx32hqy20hlshlsd423n07pwlu9cpt37" - }, - { - "key": "amount", - "value": "5000denom" - } - ] - }, - { - "Type": "wasm", - "Attr": [ - { - "key": "_contract_address", - "value": "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr" - }, - { - "key": "action", - "value": "release" - }, - { - "key": "destination", - "value": "cosmos14k7v7ms4jxkk2etmg9gljxjm4ru3qjdugfsflq" - } - ] - }, - { - "Type": "transfer", - "Attr": [ - { - "key": "recipient", - "value": "cosmos14k7v7ms4jxkk2etmg9gljxjm4ru3qjdugfsflq" - }, - { - "key": "sender", - "value": "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr" - }, - { - "key": "amount", - "value": "105000denom" - } - ] - }, - { - "Type": "message", - "Attr": [ - { - "key": "module", - "value": "wasm" - }, - { - "key": "action", - "value": "execute" - }, - { - "key": "signer", - "value": "cosmos1zm074khx32hqy20hlshlsd423n07pwlu9cpt37" - }, - { - "key": "_contract_address", - "value": "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr" - } - ] - } -] -``` - -A note on this format. This is what we return from our module. However, it seems to me that many events with the same `Type` -get merged together somewhere along the stack, so in this case, you *may* end up with one "transfer" event with the info for -both transfers. Double check when evaluating the event logs, I will document better with more experience, especially when I -find out the entire path for the events. - -## Messages - -TODO - -## CLI - -TODO - working, but not the nicest interface (json + bash = bleh). Use to upload, but I suggest to focus on frontend / js tooling - -## Rest - -TODO - main supported interface, under rapid change diff --git a/x/wasm/alias.go b/x/wasm/alias.go deleted file mode 100644 index 8f952e2..0000000 --- a/x/wasm/alias.go +++ /dev/null @@ -1,124 +0,0 @@ -// autogenerated code using github.com/rigelrozanski/multitool -// aliases generated for the following subdirectories: -// ALIASGEN: github.com/Cosmwasm/wasmd/x/wasm/types -// ALIASGEN: github.com/terpnetwork/terp-core/x/wasm/keeper -package wasm - -import ( - "github.com/terpnetwork/terp-core/x/wasm/keeper" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -const ( - firstCodeID = 1 - ModuleName = types.ModuleName - StoreKey = types.StoreKey - TStoreKey = types.TStoreKey - QuerierRoute = types.QuerierRoute - RouterKey = types.RouterKey - WasmModuleEventType = types.WasmModuleEventType - AttributeKeyContractAddr = types.AttributeKeyContractAddr - ProposalTypeStoreCode = types.ProposalTypeStoreCode - ProposalTypeInstantiateContract = types.ProposalTypeInstantiateContract - ProposalTypeMigrateContract = types.ProposalTypeMigrateContract - ProposalTypeUpdateAdmin = types.ProposalTypeUpdateAdmin - ProposalTypeClearAdmin = types.ProposalTypeClearAdmin -) - -var ( - // functions aliases - RegisterCodec = types.RegisterLegacyAminoCodec - RegisterInterfaces = types.RegisterInterfaces - ValidateGenesis = types.ValidateGenesis - ConvertToProposals = types.ConvertToProposals - GetCodeKey = types.GetCodeKey - GetContractAddressKey = types.GetContractAddressKey - GetContractStorePrefixKey = types.GetContractStorePrefix - NewCodeInfo = types.NewCodeInfo - NewAbsoluteTxPosition = types.NewAbsoluteTxPosition - NewContractInfo = types.NewContractInfo - NewEnv = types.NewEnv - NewWasmCoins = types.NewWasmCoins - DefaultWasmConfig = types.DefaultWasmConfig - DefaultParams = types.DefaultParams - InitGenesis = keeper.InitGenesis - ExportGenesis = keeper.ExportGenesis - NewMessageHandler = keeper.NewDefaultMessageHandler - DefaultEncoders = keeper.DefaultEncoders - EncodeBankMsg = keeper.EncodeBankMsg - NoCustomMsg = keeper.NoCustomMsg - EncodeStakingMsg = keeper.EncodeStakingMsg - EncodeWasmMsg = keeper.EncodeWasmMsg - NewKeeper = keeper.NewKeeper - DefaultQueryPlugins = keeper.DefaultQueryPlugins - BankQuerier = keeper.BankQuerier - NoCustomQuerier = keeper.NoCustomQuerier - StakingQuerier = keeper.StakingQuerier - WasmQuerier = keeper.WasmQuerier - CreateTestInput = keeper.CreateTestInput - TestHandler = keeper.TestHandler - NewWasmProposalHandler = keeper.NewWasmProposalHandler //nolint:staticcheck - NewQuerier = keeper.Querier - ContractFromPortID = keeper.ContractFromPortID - WithWasmEngine = keeper.WithWasmEngine - NewCountTXDecorator = keeper.NewCountTXDecorator - - // variable aliases - ModuleCdc = types.ModuleCdc - DefaultCodespace = types.DefaultCodespace - ErrCreateFailed = types.ErrCreateFailed - ErrAccountExists = types.ErrAccountExists - ErrInstantiateFailed = types.ErrInstantiateFailed - ErrExecuteFailed = types.ErrExecuteFailed - ErrGasLimit = types.ErrGasLimit - ErrInvalidGenesis = types.ErrInvalidGenesis - ErrNotFound = types.ErrNotFound - ErrQueryFailed = types.ErrQueryFailed - ErrInvalidMsg = types.ErrInvalidMsg - KeyLastCodeID = types.KeyLastCodeID - KeyLastInstanceID = types.KeyLastInstanceID - CodeKeyPrefix = types.CodeKeyPrefix - ContractKeyPrefix = types.ContractKeyPrefix - ContractStorePrefix = types.ContractStorePrefix - EnableAllProposals = types.EnableAllProposals - DisableAllProposals = types.DisableAllProposals -) - -type ( - ProposalType = types.ProposalType - GenesisState = types.GenesisState - Code = types.Code - Contract = types.Contract - MsgStoreCode = types.MsgStoreCode - MsgStoreCodeResponse = types.MsgStoreCodeResponse - MsgInstantiateContract = types.MsgInstantiateContract - MsgInstantiateContract2 = types.MsgInstantiateContract2 - MsgInstantiateContractResponse = types.MsgInstantiateContractResponse - MsgExecuteContract = types.MsgExecuteContract - MsgExecuteContractResponse = types.MsgExecuteContractResponse - MsgMigrateContract = types.MsgMigrateContract - MsgMigrateContractResponse = types.MsgMigrateContractResponse - MsgUpdateAdmin = types.MsgUpdateAdmin - MsgUpdateAdminResponse = types.MsgUpdateAdminResponse - MsgClearAdmin = types.MsgClearAdmin - MsgWasmIBCCall = types.MsgIBCSend - MsgClearAdminResponse = types.MsgClearAdminResponse - MsgServer = types.MsgServer - Model = types.Model - CodeInfo = types.CodeInfo - ContractInfo = types.ContractInfo - CreatedAt = types.AbsoluteTxPosition - Config = types.WasmConfig - CodeInfoResponse = types.CodeInfoResponse - MessageHandler = keeper.SDKMessageHandler - BankEncoder = keeper.BankEncoder - CustomEncoder = keeper.CustomEncoder - StakingEncoder = keeper.StakingEncoder - WasmEncoder = keeper.WasmEncoder - MessageEncoders = keeper.MessageEncoders - Keeper = keeper.Keeper - QueryHandler = keeper.QueryHandler - CustomQuerier = keeper.CustomQuerier - QueryPlugins = keeper.QueryPlugins - Option = keeper.Option -) diff --git a/x/wasm/client/cli/new_tx.go b/x/wasm/client/cli/new_tx.go deleted file mode 100644 index 8ce605a..0000000 --- a/x/wasm/client/cli/new_tx.go +++ /dev/null @@ -1,160 +0,0 @@ -package cli - -import ( - "strconv" - - errorsmod "cosmossdk.io/errors" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/spf13/cobra" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// MigrateContractCmd will migrate a contract to a new code version -func MigrateContractCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "migrate [contract_addr_bech32] [new_code_id_int64] [json_encoded_migration_args]", - Short: "Migrate a wasm contract to a new code version", - Aliases: []string{"update", "mig", "m"}, - Args: cobra.ExactArgs(3), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - msg, err := parseMigrateContractArgs(args, clientCtx.GetFromAddress().String()) - if err != nil { - return err - } - if err := msg.ValidateBasic(); err != nil { - return nil - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - SilenceUsage: true, - } - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -func parseMigrateContractArgs(args []string, sender string) (types.MsgMigrateContract, error) { - // get the id of the code to instantiate - codeID, err := strconv.ParseUint(args[1], 10, 64) - if err != nil { - return types.MsgMigrateContract{}, errorsmod.Wrap(err, "code id") - } - - migrateMsg := args[2] - - msg := types.MsgMigrateContract{ - Sender: sender, - Contract: args[0], - CodeID: codeID, - Msg: []byte(migrateMsg), - } - return msg, nil -} - -// UpdateContractAdminCmd sets an new admin for a contract -func UpdateContractAdminCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "set-contract-admin [contract_addr_bech32] [new_admin_addr_bech32]", - Short: "Set new admin for a contract", - Aliases: []string{"new-admin", "admin", "set-adm", "sa"}, - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - msg := parseUpdateContractAdminArgs(args, clientCtx.GetFromAddress().String()) - if err := msg.ValidateBasic(); err != nil { - return err - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - SilenceUsage: true, - } - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -func parseUpdateContractAdminArgs(args []string, sender string) types.MsgUpdateAdmin { - msg := types.MsgUpdateAdmin{ - Sender: sender, - Contract: args[0], - NewAdmin: args[1], - } - return msg -} - -// ClearContractAdminCmd clears an admin for a contract -func ClearContractAdminCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "clear-contract-admin [contract_addr_bech32]", - Short: "Clears admin for a contract to prevent further migrations", - Aliases: []string{"clear-admin", "clr-adm"}, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - msg := types.MsgClearAdmin{ - Sender: clientCtx.GetFromAddress().String(), - Contract: args[0], - } - if err := msg.ValidateBasic(); err != nil { - return err - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - SilenceUsage: true, - } - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -// UpdateInstantiateConfigCmd updates instantiate config for a smart contract. -func UpdateInstantiateConfigCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "update-instantiate-config [code_id_int64]", - Short: "Update instantiate config for a codeID", - Aliases: []string{"update-instantiate-config"}, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - codeID, err := strconv.ParseUint(args[0], 10, 64) - if err != nil { - return err - } - perm, err := parseAccessConfigFlags(cmd.Flags()) - if err != nil { - return err - } - - msg := types.MsgUpdateInstantiateConfig{ - Sender: string(clientCtx.GetFromAddress()), - CodeID: codeID, - NewInstantiatePermission: perm, - } - if err = msg.ValidateBasic(); err != nil { - return err - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - SilenceUsage: true, - } - - addInstantiatePermissionFlags(cmd) - flags.AddTxFlagsToCmd(cmd) - return cmd -} diff --git a/x/wasm/client/cli/query.go b/x/wasm/client/cli/query.go deleted file mode 100644 index 2971e1e..0000000 --- a/x/wasm/client/cli/query.go +++ /dev/null @@ -1,675 +0,0 @@ -package cli - -import ( - "context" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "os" - "strconv" - - wasmvm "github.com/CosmWasm/wasmvm" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/spf13/cobra" - flag "github.com/spf13/pflag" - - "github.com/terpnetwork/terp-core/x/wasm/keeper" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func GetQueryCmd() *cobra.Command { - queryCmd := &cobra.Command{ - Use: types.ModuleName, - Short: "Querying commands for the wasm module", - DisableFlagParsing: true, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, - SilenceUsage: true, - } - queryCmd.AddCommand( - GetCmdListCode(), - GetCmdListContractByCode(), - GetCmdQueryCode(), - GetCmdQueryCodeInfo(), - GetCmdGetContractInfo(), - GetCmdGetContractHistory(), - GetCmdGetContractState(), - GetCmdListPinnedCode(), - GetCmdLibVersion(), - GetCmdQueryParams(), - GetCmdBuildAddress(), - GetCmdListContractsByCreator(), - ) - return queryCmd -} - -// GetCmdLibVersion gets current libwasmvm version. -func GetCmdLibVersion() *cobra.Command { - cmd := &cobra.Command{ - Use: "libwasmvm-version", - Short: "Get libwasmvm version", - Long: "Get libwasmvm version", - Aliases: []string{"lib-version"}, - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - version, err := wasmvm.LibwasmvmVersion() - if err != nil { - return fmt.Errorf("error retrieving libwasmvm version: %w", err) - } - fmt.Println(version) - return nil - }, - SilenceUsage: true, - } - return cmd -} - -// GetCmdBuildAddress build a contract address -func GetCmdBuildAddress() *cobra.Command { - decoder := newArgDecoder(hex.DecodeString) - cmd := &cobra.Command{ - Use: "build-address [code-hash] [creator-address] [salt-hex-encoded] [json_encoded_init_args (required when set as fixed)]", - Short: "build contract address", - Aliases: []string{"address"}, - Args: cobra.RangeArgs(3, 4), - RunE: func(cmd *cobra.Command, args []string) error { - codeHash, err := hex.DecodeString(args[0]) - if err != nil { - return fmt.Errorf("code-hash: %s", err) - } - creator, err := sdk.AccAddressFromBech32(args[1]) - if err != nil { - return fmt.Errorf("creator: %s", err) - } - salt, err := hex.DecodeString(args[2]) - switch { - case err != nil: - return fmt.Errorf("salt: %s", err) - case len(salt) == 0: - return errors.New("empty salt") - } - - if len(args) == 3 { - cmd.Println(keeper.BuildContractAddressPredictable(codeHash, creator, salt, []byte{}).String()) - return nil - } - msg := types.RawContractMessage(args[3]) - if err := msg.ValidateBasic(); err != nil { - return fmt.Errorf("init message: %s", err) - } - cmd.Println(keeper.BuildContractAddressPredictable(codeHash, creator, salt, msg).String()) - return nil - }, - SilenceUsage: true, - } - decoder.RegisterFlags(cmd.PersistentFlags(), "salt") - return cmd -} - -// GetCmdListCode lists all wasm code uploaded -func GetCmdListCode() *cobra.Command { - cmd := &cobra.Command{ - Use: "list-code", - Short: "List all wasm bytecode on the chain", - Long: "List all wasm bytecode on the chain", - Aliases: []string{"list-codes", "codes", "lco"}, - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.Codes( - context.Background(), - &types.QueryCodesRequest{ - Pagination: pageReq, - }, - ) - if err != nil { - return err - } - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "list codes") - return cmd -} - -// GetCmdListContractByCode lists all wasm code uploaded for given code id -func GetCmdListContractByCode() *cobra.Command { - cmd := &cobra.Command{ - Use: "list-contract-by-code [code_id]", - Short: "List wasm all bytecode on the chain for given code id", - Long: "List wasm all bytecode on the chain for given code id", - Aliases: []string{"list-contracts-by-code", "list-contracts", "contracts", "lca"}, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - codeID, err := strconv.ParseUint(args[0], 10, 64) - if err != nil { - return err - } - if codeID == 0 { - return errors.New("empty code id") - } - - pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.ContractsByCode( - context.Background(), - &types.QueryContractsByCodeRequest{ - CodeId: codeID, - Pagination: pageReq, - }, - ) - if err != nil { - return err - } - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "list contracts by code") - return cmd -} - -// GetCmdQueryCode returns the bytecode for a given contract -func GetCmdQueryCode() *cobra.Command { - cmd := &cobra.Command{ - Use: "code [code_id] [output filename]", - Short: "Downloads wasm bytecode for given code id", - Long: "Downloads wasm bytecode for given code id", - Aliases: []string{"source-code", "source"}, - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - codeID, err := strconv.ParseUint(args[0], 10, 64) - if err != nil { - return err - } - - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.Code( - context.Background(), - &types.QueryCodeRequest{ - CodeId: codeID, - }, - ) - if err != nil { - return err - } - if len(res.Data) == 0 { - return fmt.Errorf("contract not found") - } - - fmt.Printf("Downloading wasm code to %s\n", args[1]) - return os.WriteFile(args[1], res.Data, 0o600) - }, - SilenceUsage: true, - } - flags.AddQueryFlagsToCmd(cmd) - return cmd -} - -// GetCmdQueryCodeInfo returns the code info for a given code id -func GetCmdQueryCodeInfo() *cobra.Command { - cmd := &cobra.Command{ - Use: "code-info [code_id]", - Short: "Prints out metadata of a code id", - Long: "Prints out metadata of a code id", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - codeID, err := strconv.ParseUint(args[0], 10, 64) - if err != nil { - return err - } - - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.Code( - context.Background(), - &types.QueryCodeRequest{ - CodeId: codeID, - }, - ) - if err != nil { - return err - } - if res.CodeInfoResponse == nil { - return fmt.Errorf("contract not found") - } - - return clientCtx.PrintProto(res.CodeInfoResponse) - }, - SilenceUsage: true, - } - flags.AddQueryFlagsToCmd(cmd) - return cmd -} - -// GetCmdGetContractInfo gets details about a given contract -func GetCmdGetContractInfo() *cobra.Command { - cmd := &cobra.Command{ - Use: "contract [bech32_address]", - Short: "Prints out metadata of a contract given its address", - Long: "Prints out metadata of a contract given its address", - Aliases: []string{"meta", "c"}, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - _, err = sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.ContractInfo( - context.Background(), - &types.QueryContractInfoRequest{ - Address: args[0], - }, - ) - if err != nil { - return err - } - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - flags.AddQueryFlagsToCmd(cmd) - return cmd -} - -// GetCmdGetContractState dumps full internal state of a given contract -func GetCmdGetContractState() *cobra.Command { - cmd := &cobra.Command{ - Use: "contract-state", - Short: "Querying commands for the wasm module", - Aliases: []string{"state", "cs", "s"}, - DisableFlagParsing: true, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, - SilenceUsage: true, - } - cmd.AddCommand( - GetCmdGetContractStateAll(), - GetCmdGetContractStateRaw(), - GetCmdGetContractStateSmart(), - ) - return cmd -} - -func GetCmdGetContractStateAll() *cobra.Command { - cmd := &cobra.Command{ - Use: "all [bech32_address]", - Short: "Prints out all internal state of a contract given its address", - Long: "Prints out all internal state of a contract given its address", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - _, err = sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - - pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.AllContractState( - context.Background(), - &types.QueryAllContractStateRequest{ - Address: args[0], - Pagination: pageReq, - }, - ) - if err != nil { - return err - } - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "contract state") - return cmd -} - -func GetCmdGetContractStateRaw() *cobra.Command { - decoder := newArgDecoder(hex.DecodeString) - cmd := &cobra.Command{ - Use: "raw [bech32_address] [key]", - Short: "Prints out internal state for key of a contract given its address", - Long: "Prints out internal state for of a contract given its address", - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - _, err = sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - queryData, err := decoder.DecodeString(args[1]) - if err != nil { - return err - } - - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.RawContractState( - context.Background(), - &types.QueryRawContractStateRequest{ - Address: args[0], - QueryData: queryData, - }, - ) - if err != nil { - return err - } - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - decoder.RegisterFlags(cmd.PersistentFlags(), "key argument") - flags.AddQueryFlagsToCmd(cmd) - return cmd -} - -func GetCmdGetContractStateSmart() *cobra.Command { - decoder := newArgDecoder(asciiDecodeString) - cmd := &cobra.Command{ - Use: "smart [bech32_address] [query]", - Short: "Calls contract with given address with query data and prints the returned result", - Long: "Calls contract with given address with query data and prints the returned result", - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - _, err = sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - if args[1] == "" { - return errors.New("query data must not be empty") - } - - queryData, err := decoder.DecodeString(args[1]) - if err != nil { - return fmt.Errorf("decode query: %s", err) - } - if !json.Valid(queryData) { - return errors.New("query data must be json") - } - - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.SmartContractState( - context.Background(), - &types.QuerySmartContractStateRequest{ - Address: args[0], - QueryData: queryData, - }, - ) - if err != nil { - return err - } - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - decoder.RegisterFlags(cmd.PersistentFlags(), "query argument") - flags.AddQueryFlagsToCmd(cmd) - return cmd -} - -// GetCmdGetContractHistory prints the code history for a given contract -func GetCmdGetContractHistory() *cobra.Command { - cmd := &cobra.Command{ - Use: "contract-history [bech32_address]", - Short: "Prints out the code history for a contract given its address", - Long: "Prints out the code history for a contract given its address", - Aliases: []string{"history", "hist", "ch"}, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - _, err = sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - - pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.ContractHistory( - context.Background(), - &types.QueryContractHistoryRequest{ - Address: args[0], - Pagination: pageReq, - }, - ) - if err != nil { - return err - } - - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - - flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "contract history") - return cmd -} - -// GetCmdListPinnedCode lists all wasm code ids that are pinned -func GetCmdListPinnedCode() *cobra.Command { - cmd := &cobra.Command{ - Use: "pinned", - Short: "List all pinned code ids", - Long: "List all pinned code ids", - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - - pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.PinnedCodes( - context.Background(), - &types.QueryPinnedCodesRequest{ - Pagination: pageReq, - }, - ) - if err != nil { - return err - } - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "list codes") - return cmd -} - -// GetCmdListContractsByCreator lists all contracts by creator -func GetCmdListContractsByCreator() *cobra.Command { - cmd := &cobra.Command{ - Use: "list-contracts-by-creator [creator]", - Short: "List all contracts by creator", - Long: "List all contracts by creator", - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - _, err = sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - pageReq, err := client.ReadPageRequest(withPageKeyDecoded(cmd.Flags())) - if err != nil { - return err - } - - queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.ContractsByCreator( - context.Background(), - &types.QueryContractsByCreatorRequest{ - CreatorAddress: args[0], - Pagination: pageReq, - }, - ) - if err != nil { - return err - } - return clientCtx.PrintProto(res) - }, - SilenceUsage: true, - } - flags.AddQueryFlagsToCmd(cmd) - flags.AddPaginationFlagsToCmd(cmd, "list contracts by creator") - return cmd -} - -type argumentDecoder struct { - // dec is the default decoder - dec func(string) ([]byte, error) - asciiF, hexF, b64F bool -} - -func newArgDecoder(def func(string) ([]byte, error)) *argumentDecoder { - return &argumentDecoder{dec: def} -} - -func (a *argumentDecoder) RegisterFlags(f *flag.FlagSet, argName string) { - f.BoolVar(&a.asciiF, "ascii", false, "ascii encoded "+argName) - f.BoolVar(&a.hexF, "hex", false, "hex encoded "+argName) - f.BoolVar(&a.b64F, "b64", false, "base64 encoded "+argName) -} - -func (a *argumentDecoder) DecodeString(s string) ([]byte, error) { - found := -1 - for i, v := range []*bool{&a.asciiF, &a.hexF, &a.b64F} { - if !*v { - continue - } - if found != -1 { - return nil, errors.New("multiple decoding flags used") - } - found = i - } - switch found { - case 0: - return asciiDecodeString(s) - case 1: - return hex.DecodeString(s) - case 2: - return base64.StdEncoding.DecodeString(s) - default: - return a.dec(s) - } -} - -func asciiDecodeString(s string) ([]byte, error) { - return []byte(s), nil -} - -// sdk ReadPageRequest expects binary but we encoded to base64 in our marshaller -func withPageKeyDecoded(flagSet *flag.FlagSet) *flag.FlagSet { - encoded, err := flagSet.GetString(flags.FlagPageKey) - if err != nil { - panic(err.Error()) - } - raw, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - panic(err.Error()) - } - err = flagSet.Set(flags.FlagPageKey, string(raw)) - if err != nil { - panic(err.Error()) - } - return flagSet -} - -// GetCmdQueryParams implements a command to return the current wasm -// parameters. -func GetCmdQueryParams() *cobra.Command { - cmd := &cobra.Command{ - Use: "params", - Short: "Query the current wasm parameters", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientQueryContext(cmd) - if err != nil { - return err - } - queryClient := types.NewQueryClient(clientCtx) - - params := &types.QueryParamsRequest{} - res, err := queryClient.Params(cmd.Context(), params) - if err != nil { - return err - } - - return clientCtx.PrintProto(&res.Params) - }, - SilenceUsage: true, - } - - flags.AddQueryFlagsToCmd(cmd) - - return cmd -} diff --git a/x/wasm/client/cli/tx.go b/x/wasm/client/cli/tx.go deleted file mode 100644 index 6131620..0000000 --- a/x/wasm/client/cli/tx.go +++ /dev/null @@ -1,566 +0,0 @@ -package cli - -import ( - "encoding/hex" - "errors" - "fmt" - "os" - "strconv" - "time" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/version" - "github.com/cosmos/cosmos-sdk/x/authz" - "github.com/spf13/cobra" - flag "github.com/spf13/pflag" - - "github.com/terpnetwork/terp-core/x/wasm/ioutils" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -const ( - flagAmount = "amount" - flagLabel = "label" - flagSource = "code-source-url" - flagBuilder = "builder" - flagCodeHash = "code-hash" - flagAdmin = "admin" - flagNoAdmin = "no-admin" - flagFixMsg = "fix-msg" - flagRunAs = "run-as" - flagInstantiateByEverybody = "instantiate-everybody" - flagInstantiateNobody = "instantiate-nobody" - flagInstantiateByAddress = "instantiate-only-address" - flagInstantiateByAnyOfAddress = "instantiate-anyof-addresses" - flagUnpinCode = "unpin-code" - flagAllowedMsgKeys = "allow-msg-keys" - flagAllowedRawMsgs = "allow-raw-msgs" - flagExpiration = "expiration" - flagMaxCalls = "max-calls" - flagMaxFunds = "max-funds" - flagAllowAllMsgs = "allow-all-messages" - flagNoTokenTransfer = "no-token-transfer" //nolint:gosec -) - -// GetTxCmd returns the transaction commands for this module -func GetTxCmd() *cobra.Command { - txCmd := &cobra.Command{ - Use: types.ModuleName, - Short: "Wasm transaction subcommands", - DisableFlagParsing: true, - SuggestionsMinimumDistance: 2, - RunE: client.ValidateCmd, - SilenceUsage: true, - } - txCmd.AddCommand( - StoreCodeCmd(), - InstantiateContractCmd(), - InstantiateContract2Cmd(), - ExecuteContractCmd(), - MigrateContractCmd(), - UpdateContractAdminCmd(), - ClearContractAdminCmd(), - GrantAuthorizationCmd(), - UpdateInstantiateConfigCmd(), - ) - return txCmd -} - -// StoreCodeCmd will upload code to be reused. -func StoreCodeCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "store [wasm file]", - Short: "Upload a wasm binary", - Aliases: []string{"upload", "st", "s"}, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - msg, err := parseStoreCodeArgs(args[0], clientCtx.GetFromAddress().String(), cmd.Flags()) - if err != nil { - return err - } - if err = msg.ValidateBasic(); err != nil { - return err - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - SilenceUsage: true, - } - - addInstantiatePermissionFlags(cmd) - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -// Prepares MsgStoreCode object from flags with gzipped wasm byte code field -func parseStoreCodeArgs(file string, sender string, flags *flag.FlagSet) (types.MsgStoreCode, error) { - wasm, err := os.ReadFile(file) - if err != nil { - return types.MsgStoreCode{}, err - } - - // gzip the wasm file - if ioutils.IsWasm(wasm) { - wasm, err = ioutils.GzipIt(wasm) - - if err != nil { - return types.MsgStoreCode{}, err - } - } else if !ioutils.IsGzip(wasm) { - return types.MsgStoreCode{}, fmt.Errorf("invalid input file. Use wasm binary or gzip") - } - - perm, err := parseAccessConfigFlags(flags) - if err != nil { - return types.MsgStoreCode{}, err - } - - msg := types.MsgStoreCode{ - Sender: sender, - WASMByteCode: wasm, - InstantiatePermission: perm, - } - return msg, nil -} - -func parseAccessConfigFlags(flags *flag.FlagSet) (*types.AccessConfig, error) { - addrs, err := flags.GetStringSlice(flagInstantiateByAnyOfAddress) - if err != nil { - return nil, fmt.Errorf("flag any of: %s", err) - } - if len(addrs) != 0 { - acceptedAddrs := make([]sdk.AccAddress, len(addrs)) - for i, v := range addrs { - acceptedAddrs[i], err = sdk.AccAddressFromBech32(v) - if err != nil { - return nil, fmt.Errorf("parse %q: %w", v, err) - } - } - x := types.AccessTypeAnyOfAddresses.With(acceptedAddrs...) - return &x, nil - } - - onlyAddrStr, err := flags.GetString(flagInstantiateByAddress) - if err != nil { - return nil, fmt.Errorf("instantiate by address: %s", err) - } - if onlyAddrStr != "" { - return nil, fmt.Errorf("not supported anymore. Use: %s", flagInstantiateByAnyOfAddress) - } - everybodyStr, err := flags.GetString(flagInstantiateByEverybody) - if err != nil { - return nil, fmt.Errorf("instantiate by everybody: %s", err) - } - if everybodyStr != "" { - ok, err := strconv.ParseBool(everybodyStr) - if err != nil { - return nil, fmt.Errorf("boolean value expected for instantiate by everybody: %s", err) - } - if ok { - return &types.AllowEverybody, nil - } - } - - nobodyStr, err := flags.GetString(flagInstantiateNobody) - if err != nil { - return nil, fmt.Errorf("instantiate by nobody: %s", err) - } - if nobodyStr != "" { - ok, err := strconv.ParseBool(nobodyStr) - if err != nil { - return nil, fmt.Errorf("boolean value expected for instantiate by nobody: %s", err) - } - if ok { - return &types.AllowNobody, nil - } - } - return nil, nil -} - -func addInstantiatePermissionFlags(cmd *cobra.Command) { - cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional") - cmd.Flags().String(flagInstantiateNobody, "", "Nobody except the governance process can instantiate a contract from the code, optional") - cmd.Flags().String(flagInstantiateByAddress, "", fmt.Sprintf("Removed: use %s instead", flagInstantiateByAnyOfAddress)) - cmd.Flags().StringSlice(flagInstantiateByAnyOfAddress, []string{}, "Any of the addresses can instantiate a contract from the code, optional") -} - -// InstantiateContractCmd will instantiate a contract from previously uploaded code. -func InstantiateContractCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "instantiate [code_id_int64] [json_encoded_init_args] --label [text] --admin [address,optional] --amount [coins,optional] ", - Short: "Instantiate a wasm contract", - Long: fmt.Sprintf(`Creates a new instance of an uploaded wasm code with the given 'constructor' message. -Each contract instance has a unique address assigned. -Example: -$ %s tx wasm instantiate 1 '{"foo":"bar"}' --admin="$(%s keys show mykey -a)" \ - --from mykey --amount="100ustake" --label "local0.1.0" -`, version.AppName, version.AppName), - Aliases: []string{"start", "init", "inst", "i"}, - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - msg, err := parseInstantiateArgs(args[0], args[1], clientCtx.Keyring, clientCtx.GetFromAddress().String(), cmd.Flags()) - if err != nil { - return err - } - if err := msg.ValidateBasic(); err != nil { - return err - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - SilenceUsage: true, - } - - cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation") - cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists") - cmd.Flags().String(flagAdmin, "", "Address or key name of an admin") - cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin") - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -// InstantiateContract2Cmd will instantiate a contract from previously uploaded code with predicable address generated -func InstantiateContract2Cmd() *cobra.Command { - decoder := newArgDecoder(hex.DecodeString) - cmd := &cobra.Command{ - Use: "instantiate2 [code_id_int64] [json_encoded_init_args] [salt] --label [text] --admin [address,optional] --amount [coins,optional] " + - "--fix-msg [bool,optional]", - Short: "Instantiate a wasm contract with predictable address", - Long: fmt.Sprintf(`Creates a new instance of an uploaded wasm code with the given 'constructor' message. -Each contract instance has a unique address assigned. They are assigned automatically but in order to have predictable addresses -for special use cases, the given 'salt' argument and '--fix-msg' parameters can be used to generate a custom address. - -Predictable address example (also see '%s query wasm build-address -h'): -$ %s tx wasm instantiate2 1 '{"foo":"bar"}' $(echo -n "testing" | xxd -ps) --admin="$(%s keys show mykey -a)" \ - --from mykey --amount="100ustake" --label "local0.1.0" \ - --fix-msg -`, version.AppName, version.AppName, version.AppName), - Aliases: []string{"start", "init", "inst", "i"}, - Args: cobra.ExactArgs(3), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - salt, err := decoder.DecodeString(args[2]) - if err != nil { - return fmt.Errorf("salt: %w", err) - } - fixMsg, err := cmd.Flags().GetBool(flagFixMsg) - if err != nil { - return fmt.Errorf("fix msg: %w", err) - } - data, err := parseInstantiateArgs(args[0], args[1], clientCtx.Keyring, clientCtx.GetFromAddress().String(), cmd.Flags()) - if err != nil { - return err - } - msg := &types.MsgInstantiateContract2{ - Sender: data.Sender, - Admin: data.Admin, - CodeID: data.CodeID, - Label: data.Label, - Msg: data.Msg, - Funds: data.Funds, - Salt: salt, - FixMsg: fixMsg, - } - if err := msg.ValidateBasic(); err != nil { - return err - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) - }, - SilenceUsage: true, - } - - cmd.Flags().String(flagAmount, "", "Coins to send to the contract during instantiation") - cmd.Flags().String(flagLabel, "", "A human-readable name for this contract in lists") - cmd.Flags().String(flagAdmin, "", "Address or key name of an admin") - cmd.Flags().Bool(flagNoAdmin, false, "You must set this explicitly if you don't want an admin") - cmd.Flags().Bool(flagFixMsg, false, "An optional flag to include the json_encoded_init_args for the predictable address generation mode") - decoder.RegisterFlags(cmd.PersistentFlags(), "salt") - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -func parseInstantiateArgs(rawCodeID, initMsg string, kr keyring.Keyring, sender string, flags *flag.FlagSet) (*types.MsgInstantiateContract, error) { - // get the id of the code to instantiate - codeID, err := strconv.ParseUint(rawCodeID, 10, 64) - if err != nil { - return nil, err - } - - amountStr, err := flags.GetString(flagAmount) - if err != nil { - return nil, fmt.Errorf("amount: %s", err) - } - amount, err := sdk.ParseCoinsNormalized(amountStr) - if err != nil { - return nil, fmt.Errorf("amount: %s", err) - } - label, err := flags.GetString(flagLabel) - if err != nil { - return nil, fmt.Errorf("label: %s", err) - } - if label == "" { - return nil, errors.New("label is required on all contracts") - } - adminStr, err := flags.GetString(flagAdmin) - if err != nil { - return nil, fmt.Errorf("admin: %s", err) - } - - noAdmin, err := flags.GetBool(flagNoAdmin) - if err != nil { - return nil, fmt.Errorf("no-admin: %s", err) - } - - // ensure sensible admin is set (or explicitly immutable) - if adminStr == "" && !noAdmin { - return nil, fmt.Errorf("you must set an admin or explicitly pass --no-admin to make it immutible (wasmd issue #719)") - } - if adminStr != "" && noAdmin { - return nil, fmt.Errorf("you set an admin and passed --no-admin, those cannot both be true") - } - - if adminStr != "" { - addr, err := sdk.AccAddressFromBech32(adminStr) - if err != nil { - info, err := kr.Key(adminStr) - if err != nil { - return nil, fmt.Errorf("admin %s", err) - } - admin, err := info.GetAddress() - if err != nil { - return nil, err - } - adminStr = admin.String() - } else { - adminStr = addr.String() - } - } - - // build and sign the transaction, then broadcast to Tendermint - msg := types.MsgInstantiateContract{ - Sender: sender, - CodeID: codeID, - Label: label, - Funds: amount, - Msg: []byte(initMsg), - Admin: adminStr, - } - return &msg, nil -} - -// ExecuteContractCmd will instantiate a contract from previously uploaded code. -func ExecuteContractCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "execute [contract_addr_bech32] [json_encoded_send_args] --amount [coins,optional]", - Short: "Execute a command on a wasm contract", - Aliases: []string{"run", "call", "exec", "ex", "e"}, - Args: cobra.ExactArgs(2), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - msg, err := parseExecuteArgs(args[0], args[1], clientCtx.GetFromAddress(), cmd.Flags()) - if err != nil { - return err - } - if err := msg.ValidateBasic(); err != nil { - return err - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) - }, - SilenceUsage: true, - } - - cmd.Flags().String(flagAmount, "", "Coins to send to the contract along with command") - flags.AddTxFlagsToCmd(cmd) - return cmd -} - -func parseExecuteArgs(contractAddr string, execMsg string, sender sdk.AccAddress, flags *flag.FlagSet) (types.MsgExecuteContract, error) { - amountStr, err := flags.GetString(flagAmount) - if err != nil { - return types.MsgExecuteContract{}, fmt.Errorf("amount: %s", err) - } - - amount, err := sdk.ParseCoinsNormalized(amountStr) - if err != nil { - return types.MsgExecuteContract{}, err - } - - return types.MsgExecuteContract{ - Sender: sender.String(), - Contract: contractAddr, - Funds: amount, - Msg: []byte(execMsg), - }, nil -} - -func GrantAuthorizationCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "grant [grantee] [message_type=\"execution\"|\"migration\"] [contract_addr_bech32] --allow-raw-msgs [msg1,msg2,...] --allow-msg-keys [key1,key2,...] --allow-all-messages", - Short: "Grant authorization to an address", - Long: fmt.Sprintf(`Grant authorization to an address. -Examples: -$ %s tx grant execution --allow-all-messages --max-calls 1 --no-token-transfer --expiration 1667979596 - -$ %s tx grant execution --allow-all-messages --max-funds 100000uwasm --expiration 1667979596 - -$ %s tx grant execution --allow-all-messages --max-calls 5 --max-funds 100000uwasm --expiration 1667979596 -`, version.AppName, version.AppName, version.AppName), - Args: cobra.ExactArgs(3), - RunE: func(cmd *cobra.Command, args []string) error { - clientCtx, err := client.GetClientTxContext(cmd) - if err != nil { - return err - } - - grantee, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - - contract, err := sdk.AccAddressFromBech32(args[2]) - if err != nil { - return err - } - - msgKeys, err := cmd.Flags().GetStringSlice(flagAllowedMsgKeys) - if err != nil { - return err - } - - rawMsgs, err := cmd.Flags().GetStringSlice(flagAllowedRawMsgs) - if err != nil { - return err - } - - maxFundsStr, err := cmd.Flags().GetString(flagMaxFunds) - if err != nil { - return fmt.Errorf("max funds: %s", err) - } - - maxCalls, err := cmd.Flags().GetUint64(flagMaxCalls) - if err != nil { - return err - } - - exp, err := cmd.Flags().GetInt64(flagExpiration) - if err != nil { - return err - } - if exp == 0 { - return errors.New("expiration must be set") - } - - allowAllMsgs, err := cmd.Flags().GetBool(flagAllowAllMsgs) - if err != nil { - return err - } - - noTokenTransfer, err := cmd.Flags().GetBool(flagNoTokenTransfer) - if err != nil { - return err - } - - var limit types.ContractAuthzLimitX - switch { - case maxFundsStr != "" && maxCalls != 0 && !noTokenTransfer: - maxFunds, err := sdk.ParseCoinsNormalized(maxFundsStr) - if err != nil { - return fmt.Errorf("max funds: %s", err) - } - limit = types.NewCombinedLimit(maxCalls, maxFunds...) - case maxFundsStr != "" && maxCalls == 0 && !noTokenTransfer: - maxFunds, err := sdk.ParseCoinsNormalized(maxFundsStr) - if err != nil { - return fmt.Errorf("max funds: %s", err) - } - limit = types.NewMaxFundsLimit(maxFunds...) - case maxCalls != 0 && noTokenTransfer && maxFundsStr == "": - limit = types.NewMaxCallsLimit(maxCalls) - default: - return errors.New("invalid limit setup") - } - - var filter types.ContractAuthzFilterX - switch { - case allowAllMsgs && len(msgKeys) != 0 || allowAllMsgs && len(rawMsgs) != 0 || len(msgKeys) != 0 && len(rawMsgs) != 0: - return errors.New("cannot set more than one filter within one grant") - case allowAllMsgs: - filter = types.NewAllowAllMessagesFilter() - case len(msgKeys) != 0: - filter = types.NewAcceptedMessageKeysFilter(msgKeys...) - case len(rawMsgs) != 0: - msgs := make([]types.RawContractMessage, len(rawMsgs)) - for i, msg := range rawMsgs { - msgs[i] = types.RawContractMessage(msg) - } - filter = types.NewAcceptedMessagesFilter(msgs...) - default: - return errors.New("invalid filter setup") - } - - grant, err := types.NewContractGrant(contract, limit, filter) - if err != nil { - return err - } - - var authorization authz.Authorization - switch args[1] { - case "execution": - authorization = types.NewContractExecutionAuthorization(*grant) - case "migration": - authorization = types.NewContractMigrationAuthorization(*grant) - default: - return fmt.Errorf("%s authorization type not supported", args[1]) - } - - expire, err := getExpireTime(cmd) - if err != nil { - return err - } - - grantMsg, err := authz.NewMsgGrant(clientCtx.GetFromAddress(), grantee, authorization, expire) - if err != nil { - return err - } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), grantMsg) - }, - } - flags.AddTxFlagsToCmd(cmd) - cmd.Flags().StringSlice(flagAllowedMsgKeys, []string{}, "Allowed msg keys") - cmd.Flags().StringSlice(flagAllowedRawMsgs, []string{}, "Allowed raw msgs") - cmd.Flags().Uint64(flagMaxCalls, 0, "Maximal number of calls to the contract") - cmd.Flags().String(flagMaxFunds, "", "Maximal amount of tokens transferable to the contract.") - cmd.Flags().Int64(flagExpiration, 0, "The Unix timestamp.") - cmd.Flags().Bool(flagAllowAllMsgs, false, "Allow all messages") - cmd.Flags().Bool(flagNoTokenTransfer, false, "Don't allow token transfer") - return cmd -} - -func getExpireTime(cmd *cobra.Command) (*time.Time, error) { - exp, err := cmd.Flags().GetInt64(flagExpiration) - if err != nil { - return nil, err - } - if exp == 0 { - return nil, nil - } - e := time.Unix(exp, 0) - return &e, nil -} diff --git a/x/wasm/client/cli/tx_test.go b/x/wasm/client/cli/tx_test.go deleted file mode 100644 index 0521f5e..0000000 --- a/x/wasm/client/cli/tx_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package cli - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestParseAccessConfigFlags(t *testing.T) { - specs := map[string]struct { - args []string - expCfg *types.AccessConfig - expErr bool - }{ - "nobody": { - args: []string{"--instantiate-nobody=true"}, - expCfg: &types.AccessConfig{Permission: types.AccessTypeNobody}, - }, - "everybody": { - args: []string{"--instantiate-everybody=true"}, - expCfg: &types.AccessConfig{Permission: types.AccessTypeEverybody}, - }, - "only address": { - args: []string{"--instantiate-only-address=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"}, - expErr: true, - }, - "only address - invalid": { - args: []string{"--instantiate-only-address=foo"}, - expErr: true, - }, - "any of address": { - args: []string{"--instantiate-anyof-addresses=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"}, - expCfg: &types.AccessConfig{Permission: types.AccessTypeAnyOfAddresses, Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x", "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"}}, - }, - "any of address - invalid": { - args: []string{"--instantiate-anyof-addresses=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,foo"}, - expErr: true, - }, - "not set": { - args: []string{}, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - flags := StoreCodeCmd().Flags() - require.NoError(t, flags.Parse(spec.args)) - gotCfg, gotErr := parseAccessConfigFlags(flags) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expCfg, gotCfg) - }) - } -} diff --git a/x/wasm/common_test.go b/x/wasm/common_test.go deleted file mode 100644 index 4500abe..0000000 --- a/x/wasm/common_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package wasm - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -// ensure store code returns the expected response -func assertStoreCodeResponse(t *testing.T, data []byte, expected uint64) { - var pStoreResp MsgStoreCodeResponse - require.NoError(t, pStoreResp.Unmarshal(data)) - require.Equal(t, pStoreResp.CodeID, expected) -} - -// ensure execution returns the expected data -func assertExecuteResponse(t *testing.T, data []byte, expected []byte) { - var pExecResp MsgExecuteContractResponse - require.NoError(t, pExecResp.Unmarshal(data)) - require.Equal(t, pExecResp.Data, expected) -} - -// ensures this returns a valid bech32 address and returns it -func parseInitResponse(t *testing.T, data []byte) string { - t.Helper() - var pInstResp MsgInstantiateContractResponse - require.NoError(t, pInstResp.Unmarshal(data)) - require.NotEmpty(t, pInstResp.Address) - addr := pInstResp.Address - // ensure this is a valid sdk address - _, err := sdk.AccAddressFromBech32(addr) - require.NoError(t, err) - return addr -} diff --git a/x/wasm/exported/exported.go b/x/wasm/exported/exported.go deleted file mode 100644 index 000114e..0000000 --- a/x/wasm/exported/exported.go +++ /dev/null @@ -1,18 +0,0 @@ -package exported - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" -) - -type ( - ParamSet = paramtypes.ParamSet - - // Subspace defines an interface that implements the legacy x/params Subspace - // type. - // - // NOTE: This is used solely for migration of x/params managed parameters. - Subspace interface { - GetParamSet(ctx sdk.Context, ps ParamSet) - } -) diff --git a/x/wasm/genesis_test.go b/x/wasm/genesis_test.go deleted file mode 100644 index 83d5db2..0000000 --- a/x/wasm/genesis_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package wasm - -import ( - "encoding/json" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -func TestInitGenesis(t *testing.T) { - data := setupTest(t) - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := data.faucet.NewFundedRandomAccount(data.ctx, deposit.Add(deposit...)...) - fred := data.faucet.NewFundedRandomAccount(data.ctx, topUp...) - - msg := MsgStoreCode{ - Sender: creator.String(), - WASMByteCode: testContract, - } - h := data.msgServiceRouter.Handler(&msg) - q := data.grpcQueryRouter - - err := msg.ValidateBasic() - require.NoError(t, err) - - res, err := h(data.ctx, &msg) - require.NoError(t, err) - assertStoreCodeResponse(t, res.Data, 1) - - bob := keyPubAddr() - initMsg := initMsg{ - Verifier: fred, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - instMsg := MsgInstantiateContract{ - Sender: creator.String(), - CodeID: firstCodeID, - Msg: initMsgBz, - Funds: deposit, - Label: "testing", - } - h = data.msgServiceRouter.Handler(&instMsg) - res, err = h(data.ctx, &instMsg) - require.NoError(t, err) - contractBech32Addr := parseInitResponse(t, res.Data) - - execMsg := MsgExecuteContract{ - Sender: fred.String(), - Contract: contractBech32Addr, - Msg: []byte(`{"release":{}}`), - Funds: topUp, - } - h = data.msgServiceRouter.Handler(&execMsg) - res, err = h(data.ctx, &execMsg) - require.NoError(t, err) - // from https://github.com/CosmWasm/cosmwasm/blob/master/contracts/hackatom/src/contract.rs#L167 - assertExecuteResponse(t, res.Data, []byte{0xf0, 0x0b, 0xaa}) - - // ensure all contract state is as after init - assertCodeList(t, q, data.ctx, 1, data.encConf.Marshaler) - assertCodeBytes(t, q, data.ctx, 1, testContract, data.encConf.Marshaler) - - assertContractList(t, q, data.ctx, 1, []string{contractBech32Addr}, data.encConf.Marshaler) - assertContractInfo(t, q, data.ctx, contractBech32Addr, 1, creator, data.encConf.Marshaler) - assertContractState(t, q, data.ctx, contractBech32Addr, state{ - Verifier: fred.String(), - Beneficiary: bob.String(), - Funder: creator.String(), - }, data.encConf.Marshaler) - - // export into genstate - genState := ExportGenesis(data.ctx, &data.keeper) - - // create new app to import genstate into - newData := setupTest(t) - q2 := newData.grpcQueryRouter - - // initialize new app with genstate - _, err = InitGenesis(newData.ctx, &newData.keeper, *genState) - require.NoError(t, err) - - // run same checks again on newdata, to make sure it was reinitialized correctly - assertCodeList(t, q2, newData.ctx, 1, data.encConf.Marshaler) - assertCodeBytes(t, q2, newData.ctx, 1, testContract, data.encConf.Marshaler) - - assertContractList(t, q2, newData.ctx, 1, []string{contractBech32Addr}, data.encConf.Marshaler) - assertContractInfo(t, q2, newData.ctx, contractBech32Addr, 1, creator, data.encConf.Marshaler) - assertContractState(t, q2, newData.ctx, contractBech32Addr, state{ - Verifier: fred.String(), - Beneficiary: bob.String(), - Funder: creator.String(), - }, data.encConf.Marshaler) -} diff --git a/x/wasm/ibc.go b/x/wasm/ibc.go deleted file mode 100644 index cf06e64..0000000 --- a/x/wasm/ibc.go +++ /dev/null @@ -1,357 +0,0 @@ -package wasm - -import ( - "math" - - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var _ porttypes.IBCModule = IBCHandler{} - -// internal interface that is implemented by ibc middleware -type appVersionGetter interface { - // GetAppVersion returns the application level version with all middleware data stripped out - GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) -} - -type IBCHandler struct { - keeper types.IBCContractKeeper - channelKeeper types.ChannelKeeper - appVersionGetter appVersionGetter -} - -func NewIBCHandler(k types.IBCContractKeeper, ck types.ChannelKeeper, vg appVersionGetter) IBCHandler { - return IBCHandler{keeper: k, channelKeeper: ck, appVersionGetter: vg} -} - -// OnChanOpenInit implements the IBCModule interface -func (i IBCHandler) OnChanOpenInit( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID string, - channelID string, - chanCap *capabilitytypes.Capability, - counterParty channeltypes.Counterparty, - version string, -) (string, error) { - // ensure port, version, capability - if err := ValidateChannelParams(channelID); err != nil { - return "", err - } - contractAddr, err := ContractFromPortID(portID) - if err != nil { - return "", errorsmod.Wrapf(err, "contract port id") - } - - msg := wasmvmtypes.IBCChannelOpenMsg{ - OpenInit: &wasmvmtypes.IBCOpenInit{ - Channel: wasmvmtypes.IBCChannel{ - Endpoint: wasmvmtypes.IBCEndpoint{PortID: portID, ChannelID: channelID}, - CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{PortID: counterParty.PortId, ChannelID: counterParty.ChannelId}, - Order: order.String(), - // DESIGN V3: this may be "" ?? - Version: version, - ConnectionID: connectionHops[0], // At the moment this list must be of length 1. In the future multi-hop channels may be supported. - }, - }, - } - - // Allow contracts to return a version (or default to proposed version if unset) - acceptedVersion, err := i.keeper.OnOpenChannel(ctx, contractAddr, msg) - if err != nil { - return "", err - } - if acceptedVersion == "" { // accept incoming version when nothing returned by contract - acceptedVersion = version - } - - // Claim channel capability passed back by IBC module - if err := i.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return "", errorsmod.Wrap(err, "claim capability") - } - return acceptedVersion, nil -} - -// OnChanOpenTry implements the IBCModule interface -func (i IBCHandler) OnChanOpenTry( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID, channelID string, - chanCap *capabilitytypes.Capability, - counterParty channeltypes.Counterparty, - counterpartyVersion string, -) (string, error) { - // ensure port, version, capability - if err := ValidateChannelParams(channelID); err != nil { - return "", err - } - - contractAddr, err := ContractFromPortID(portID) - if err != nil { - return "", errorsmod.Wrapf(err, "contract port id") - } - - msg := wasmvmtypes.IBCChannelOpenMsg{ - OpenTry: &wasmvmtypes.IBCOpenTry{ - Channel: wasmvmtypes.IBCChannel{ - Endpoint: wasmvmtypes.IBCEndpoint{PortID: portID, ChannelID: channelID}, - CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{PortID: counterParty.PortId, ChannelID: counterParty.ChannelId}, - Order: order.String(), - Version: counterpartyVersion, - ConnectionID: connectionHops[0], // At the moment this list must be of length 1. In the future multi-hop channels may be supported. - }, - CounterpartyVersion: counterpartyVersion, - }, - } - - // Allow contracts to return a version (or default to counterpartyVersion if unset) - version, err := i.keeper.OnOpenChannel(ctx, contractAddr, msg) - if err != nil { - return "", err - } - if version == "" { - version = counterpartyVersion - } - - // Module may have already claimed capability in OnChanOpenInit in the case of crossing hellos - // (ie chainA and chainB both call ChanOpenInit before one of them calls ChanOpenTry) - // If module can already authenticate the capability then module already owns it, so we don't need to claim - // Otherwise, module does not have channel capability, and we must claim it from IBC - if !i.keeper.AuthenticateCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)) { - // Only claim channel capability passed back by IBC module if we do not already own it - if err := i.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { - return "", errorsmod.Wrap(err, "claim capability") - } - } - - return version, nil -} - -// OnChanOpenAck implements the IBCModule interface -func (i IBCHandler) OnChanOpenAck( - ctx sdk.Context, - portID, channelID string, - counterpartyChannelID string, - counterpartyVersion string, -) error { - contractAddr, err := ContractFromPortID(portID) - if err != nil { - return errorsmod.Wrapf(err, "contract port id") - } - channelInfo, ok := i.channelKeeper.GetChannel(ctx, portID, channelID) - if !ok { - return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) - } - channelInfo.Counterparty.ChannelId = counterpartyChannelID - - appVersion, ok := i.appVersionGetter.GetAppVersion(ctx, portID, channelID) - if !ok { - return errorsmod.Wrapf(channeltypes.ErrInvalidChannelVersion, "port ID (%s) channel ID (%s)", portID, channelID) - } - - msg := wasmvmtypes.IBCChannelConnectMsg{ - OpenAck: &wasmvmtypes.IBCOpenAck{ - Channel: toWasmVMChannel(portID, channelID, channelInfo, appVersion), - CounterpartyVersion: counterpartyVersion, - }, - } - return i.keeper.OnConnectChannel(ctx, contractAddr, msg) -} - -// OnChanOpenConfirm implements the IBCModule interface -func (i IBCHandler) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error { - contractAddr, err := ContractFromPortID(portID) - if err != nil { - return errorsmod.Wrapf(err, "contract port id") - } - channelInfo, ok := i.channelKeeper.GetChannel(ctx, portID, channelID) - if !ok { - return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) - } - appVersion, ok := i.appVersionGetter.GetAppVersion(ctx, portID, channelID) - if !ok { - return errorsmod.Wrapf(channeltypes.ErrInvalidChannelVersion, "port ID (%s) channel ID (%s)", portID, channelID) - } - msg := wasmvmtypes.IBCChannelConnectMsg{ - OpenConfirm: &wasmvmtypes.IBCOpenConfirm{ - Channel: toWasmVMChannel(portID, channelID, channelInfo, appVersion), - }, - } - return i.keeper.OnConnectChannel(ctx, contractAddr, msg) -} - -// OnChanCloseInit implements the IBCModule interface -func (i IBCHandler) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error { - contractAddr, err := ContractFromPortID(portID) - if err != nil { - return errorsmod.Wrapf(err, "contract port id") - } - channelInfo, ok := i.channelKeeper.GetChannel(ctx, portID, channelID) - if !ok { - return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) - } - appVersion, ok := i.appVersionGetter.GetAppVersion(ctx, portID, channelID) - if !ok { - return errorsmod.Wrapf(channeltypes.ErrInvalidChannelVersion, "port ID (%s) channel ID (%s)", portID, channelID) - } - - msg := wasmvmtypes.IBCChannelCloseMsg{ - CloseInit: &wasmvmtypes.IBCCloseInit{Channel: toWasmVMChannel(portID, channelID, channelInfo, appVersion)}, - } - err = i.keeper.OnCloseChannel(ctx, contractAddr, msg) - if err != nil { - return err - } - // emit events? - - return err -} - -// OnChanCloseConfirm implements the IBCModule interface -func (i IBCHandler) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error { - // counterparty has closed the channel - contractAddr, err := ContractFromPortID(portID) - if err != nil { - return errorsmod.Wrapf(err, "contract port id") - } - channelInfo, ok := i.channelKeeper.GetChannel(ctx, portID, channelID) - if !ok { - return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) - } - appVersion, ok := i.appVersionGetter.GetAppVersion(ctx, portID, channelID) - if !ok { - return errorsmod.Wrapf(channeltypes.ErrInvalidChannelVersion, "port ID (%s) channel ID (%s)", portID, channelID) - } - - msg := wasmvmtypes.IBCChannelCloseMsg{ - CloseConfirm: &wasmvmtypes.IBCCloseConfirm{Channel: toWasmVMChannel(portID, channelID, channelInfo, appVersion)}, - } - err = i.keeper.OnCloseChannel(ctx, contractAddr, msg) - if err != nil { - return err - } - // emit events? - - return err -} - -func toWasmVMChannel(portID, channelID string, channelInfo channeltypes.Channel, appVersion string) wasmvmtypes.IBCChannel { - return wasmvmtypes.IBCChannel{ - Endpoint: wasmvmtypes.IBCEndpoint{PortID: portID, ChannelID: channelID}, - CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{PortID: channelInfo.Counterparty.PortId, ChannelID: channelInfo.Counterparty.ChannelId}, - Order: channelInfo.Ordering.String(), - Version: appVersion, - ConnectionID: channelInfo.ConnectionHops[0], // At the moment this list must be of length 1. In the future multi-hop channels may be supported. - } -} - -// OnRecvPacket implements the IBCModule interface -func (i IBCHandler) OnRecvPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) ibcexported.Acknowledgement { - contractAddr, err := ContractFromPortID(packet.DestinationPort) - if err != nil { - return channeltypes.NewErrorAcknowledgement(errorsmod.Wrapf(err, "contract port id")) - } - msg := wasmvmtypes.IBCPacketReceiveMsg{Packet: newIBCPacket(packet), Relayer: relayer.String()} - ack, err := i.keeper.OnRecvPacket(ctx, contractAddr, msg) - if err != nil { - return channeltypes.NewErrorAcknowledgement(err) - } - return ContractConfirmStateAck(ack) -} - -var _ ibcexported.Acknowledgement = ContractConfirmStateAck{} - -type ContractConfirmStateAck []byte - -func (w ContractConfirmStateAck) Success() bool { - return true // always commit state -} - -func (w ContractConfirmStateAck) Acknowledgement() []byte { - return w -} - -// OnAcknowledgementPacket implements the IBCModule interface -func (i IBCHandler) OnAcknowledgementPacket( - ctx sdk.Context, - packet channeltypes.Packet, - acknowledgement []byte, - relayer sdk.AccAddress, -) error { - contractAddr, err := ContractFromPortID(packet.SourcePort) - if err != nil { - return errorsmod.Wrapf(err, "contract port id") - } - - err = i.keeper.OnAckPacket(ctx, contractAddr, wasmvmtypes.IBCPacketAckMsg{ - Acknowledgement: wasmvmtypes.IBCAcknowledgement{Data: acknowledgement}, - OriginalPacket: newIBCPacket(packet), - Relayer: relayer.String(), - }) - if err != nil { - return errorsmod.Wrap(err, "on ack") - } - return nil -} - -// OnTimeoutPacket implements the IBCModule interface -func (i IBCHandler) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { - contractAddr, err := ContractFromPortID(packet.SourcePort) - if err != nil { - return errorsmod.Wrapf(err, "contract port id") - } - msg := wasmvmtypes.IBCPacketTimeoutMsg{Packet: newIBCPacket(packet), Relayer: relayer.String()} - err = i.keeper.OnTimeoutPacket(ctx, contractAddr, msg) - if err != nil { - return errorsmod.Wrap(err, "on timeout") - } - return nil -} - -func newIBCPacket(packet channeltypes.Packet) wasmvmtypes.IBCPacket { - timeout := wasmvmtypes.IBCTimeout{ - Timestamp: packet.TimeoutTimestamp, - } - if !packet.TimeoutHeight.IsZero() { - timeout.Block = &wasmvmtypes.IBCTimeoutBlock{ - Height: packet.TimeoutHeight.RevisionHeight, - Revision: packet.TimeoutHeight.RevisionNumber, - } - } - - return wasmvmtypes.IBCPacket{ - Data: packet.Data, - Src: wasmvmtypes.IBCEndpoint{ChannelID: packet.SourceChannel, PortID: packet.SourcePort}, - Dest: wasmvmtypes.IBCEndpoint{ChannelID: packet.DestinationChannel, PortID: packet.DestinationPort}, - Sequence: packet.Sequence, - Timeout: timeout, - } -} - -func ValidateChannelParams(channelID string) error { - // NOTE: for escrow address security only 2^32 channels are allowed to be created - // Issue: https://github.com/cosmos/cosmos-sdk/issues/7737 - channelSequence, err := channeltypes.ParseChannelSequence(channelID) - if err != nil { - return err - } - if channelSequence > math.MaxUint32 { - return errorsmod.Wrapf(types.ErrMaxIBCChannels, "channel sequence %d is greater than max allowed transfer channels %d", channelSequence, math.MaxUint32) - } - return nil -} diff --git a/x/wasm/ibc_integration_test.go b/x/wasm/ibc_integration_test.go deleted file mode 100644 index f348f46..0000000 --- a/x/wasm/ibc_integration_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package wasm_test - -import ( - "testing" - - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - ibctesting "github.com/cosmos/ibc-go/v7/testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - wasmibctesting "github.com/terpnetwork/terp-core/x/wasm/ibctesting" - wasmkeeper "github.com/terpnetwork/terp-core/x/wasm/keeper" - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" -) - -func TestOnChanOpenInitVersion(t *testing.T) { - const startVersion = "v1" - specs := map[string]struct { - contractRsp *wasmvmtypes.IBC3ChannelOpenResponse - expVersion string - }{ - "different version": { - contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{Version: "v2"}, - expVersion: "v2", - }, - "no response": { - expVersion: startVersion, - }, - "empty result": { - contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{}, - expVersion: startVersion, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - myContract := &wasmtesting.MockIBCContractCallbacks{ - IBCChannelOpenFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) { - return spec.contractRsp, 0, nil - }, - } - var ( - chainAOpts = []wasmkeeper.Option{ - wasmkeeper.WithWasmEngine( - wasmtesting.NewIBCContractMockWasmer(myContract)), - } - coordinator = wasmibctesting.NewCoordinator(t, 2, chainAOpts) - chainA = coordinator.GetChain(wasmibctesting.GetChainID(1)) - chainB = coordinator.GetChain(wasmibctesting.GetChainID(2)) - myContractAddr = chainA.SeedNewContractInstance() - contractInfo = chainA.App.WasmKeeper.GetContractInfo(chainA.GetContext(), myContractAddr) - ) - - path := wasmibctesting.NewPath(chainA, chainB) - coordinator.SetupConnections(path) - - path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{ - PortID: contractInfo.IBCPortID, - Version: startVersion, - Order: channeltypes.UNORDERED, - } - require.NoError(t, path.EndpointA.ChanOpenInit()) - assert.Equal(t, spec.expVersion, path.EndpointA.ChannelConfig.Version) - }) - } -} - -func TestOnChanOpenTryVersion(t *testing.T) { - const startVersion = ibctransfertypes.Version - specs := map[string]struct { - contractRsp *wasmvmtypes.IBC3ChannelOpenResponse - expVersion string - }{ - "different version": { - contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{Version: "v2"}, - expVersion: "v2", - }, - "no response": { - expVersion: startVersion, - }, - "empty result": { - contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{}, - expVersion: startVersion, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - myContract := &wasmtesting.MockIBCContractCallbacks{ - IBCChannelOpenFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) { - return spec.contractRsp, 0, nil - }, - } - var ( - chainAOpts = []wasmkeeper.Option{ - wasmkeeper.WithWasmEngine( - wasmtesting.NewIBCContractMockWasmer(myContract)), - } - coordinator = wasmibctesting.NewCoordinator(t, 2, chainAOpts) - chainA = coordinator.GetChain(wasmibctesting.GetChainID(1)) - chainB = coordinator.GetChain(wasmibctesting.GetChainID(2)) - myContractAddr = chainA.SeedNewContractInstance() - contractInfo = chainA.ContractInfo(myContractAddr) - ) - - path := wasmibctesting.NewPath(chainA, chainB) - coordinator.SetupConnections(path) - - path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{ - PortID: contractInfo.IBCPortID, - Version: startVersion, - Order: channeltypes.UNORDERED, - } - path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{ - PortID: ibctransfertypes.PortID, - Version: ibctransfertypes.Version, - Order: channeltypes.UNORDERED, - } - - require.NoError(t, path.EndpointB.ChanOpenInit()) - require.NoError(t, path.EndpointA.ChanOpenTry()) - assert.Equal(t, spec.expVersion, path.EndpointA.ChannelConfig.Version) - }) - } -} diff --git a/x/wasm/ibc_reflect_test.go b/x/wasm/ibc_reflect_test.go deleted file mode 100644 index 7d4e41d..0000000 --- a/x/wasm/ibc_reflect_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package wasm_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - ibctesting "github.com/cosmos/ibc-go/v7/testing" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/stretchr/testify/require" - - wasmibctesting "github.com/terpnetwork/terp-core/x/wasm/ibctesting" - wasmkeeper "github.com/terpnetwork/terp-core/x/wasm/keeper" -) - -func TestIBCReflectContract(t *testing.T) { - // scenario: - // chain A: ibc_reflect_send.wasm - // chain B: reflect.wasm + ibc_reflect.wasm - // - // Chain A "ibc_reflect_send" sends a IBC packet "on channel connect" event to chain B "ibc_reflect" - // "ibc_reflect" sends a submessage to "reflect" which is returned as submessage. - - var ( - coordinator = wasmibctesting.NewCoordinator(t, 2) - chainA = coordinator.GetChain(wasmibctesting.GetChainID(1)) - chainB = coordinator.GetChain(wasmibctesting.GetChainID(2)) - ) - coordinator.CommitBlock(chainA, chainB) - - initMsg := []byte(`{}`) - codeID := chainA.StoreCodeFile("./keeper/testdata/ibc_reflect_send.wasm").CodeID - sendContractAddr := chainA.InstantiateContract(codeID, initMsg) - - reflectID := chainB.StoreCodeFile("./keeper/testdata/reflect.wasm").CodeID - initMsg = wasmkeeper.IBCReflectInitMsg{ - ReflectCodeID: reflectID, - }.GetBytes(t) - codeID = chainB.StoreCodeFile("./keeper/testdata/ibc_reflect.wasm").CodeID - - reflectContractAddr := chainB.InstantiateContract(codeID, initMsg) - var ( - sourcePortID = chainA.ContractInfo(sendContractAddr).IBCPortID - counterpartPortID = chainB.ContractInfo(reflectContractAddr).IBCPortID - ) - coordinator.CommitBlock(chainA, chainB) - coordinator.UpdateTime() - - require.Equal(t, chainA.CurrentHeader.Time, chainB.CurrentHeader.Time) - path := wasmibctesting.NewPath(chainA, chainB) - path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{ - PortID: sourcePortID, - Version: "ibc-reflect-v1", - Order: channeltypes.ORDERED, - } - path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{ - PortID: counterpartPortID, - Version: "ibc-reflect-v1", - Order: channeltypes.ORDERED, - } - - coordinator.SetupConnections(path) - coordinator.CreateChannels(path) - - // TODO: query both contracts directly to ensure they have registered the proper connection - // (and the chainB has created a reflect contract) - - // there should be one packet to relay back and forth (whoami) - // TODO: how do I find the packet that was previously sent by the smart contract? - // Coordinator.RecvPacket requires channeltypes.Packet as input? - // Given the source (portID, channelID), we should be able to count how many packets are pending, query the data - // and submit them to the other side (same with acks). This is what the real relayer does. I guess the test framework doesn't? - - // Update: I dug through the code, especially channel.Keeper.SendPacket, and it only writes a commitment - // only writes I see: https://github.com/cosmos/cosmos-sdk/blob/31fdee0228bd6f3e787489c8e4434aabc8facb7d/x/ibc/core/04-channel/keeper/packet.go#L115-L116 - // commitment is hashed packet: https://github.com/cosmos/cosmos-sdk/blob/31fdee0228bd6f3e787489c8e4434aabc8facb7d/x/ibc/core/04-channel/types/packet.go#L14-L34 - // how is the relayer supposed to get the original packet data?? - // eg. ibctransfer doesn't store the packet either: https://github.com/cosmos/cosmos-sdk/blob/master/x/ibc/applications/transfer/keeper/relay.go#L145-L162 - // ... or I guess the original packet data is only available in the event logs???? - // https://github.com/cosmos/cosmos-sdk/blob/31fdee0228bd6f3e787489c8e4434aabc8facb7d/x/ibc/core/04-channel/keeper/packet.go#L121-L132 - - // ensure the expected packet was prepared, and relay it - require.Equal(t, 1, len(chainA.PendingSendPackets)) - require.Equal(t, 0, len(chainB.PendingSendPackets)) - err := coordinator.RelayAndAckPendingPackets(path) - require.NoError(t, err) - require.Equal(t, 0, len(chainA.PendingSendPackets)) - require.Equal(t, 0, len(chainB.PendingSendPackets)) - - // let's query the source contract and make sure it registered an address - query := ReflectSendQueryMsg{Account: &AccountQuery{ChannelID: path.EndpointA.ChannelID}} - var account AccountResponse - err = chainA.SmartQuery(sendContractAddr.String(), query, &account) - require.NoError(t, err) - require.NotEmpty(t, account.RemoteAddr) - require.Empty(t, account.RemoteBalance) - - // close channel - coordinator.CloseChannel(path) - - // let's query the source contract and make sure it registered an address - account = AccountResponse{} - err = chainA.SmartQuery(sendContractAddr.String(), query, &account) - require.Error(t, err) - assert.Contains(t, err.Error(), "not found") -} - -type ReflectSendQueryMsg struct { - Admin *struct{} `json:"admin,omitempty"` - ListAccounts *struct{} `json:"list_accounts,omitempty"` - Account *AccountQuery `json:"account,omitempty"` -} - -type AccountQuery struct { - ChannelID string `json:"channel_id"` -} - -type AccountResponse struct { - LastUpdateTime uint64 `json:"last_update_time,string"` - RemoteAddr string `json:"remote_addr"` - RemoteBalance wasmvmtypes.Coins `json:"remote_balance"` -} diff --git a/x/wasm/ibc_test.go b/x/wasm/ibc_test.go deleted file mode 100644 index a92d451..0000000 --- a/x/wasm/ibc_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package wasm - -import ( - "testing" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/stretchr/testify/assert" -) - -func TestMapToWasmVMIBCPacket(t *testing.T) { - var myTimestamp uint64 = 1 - specs := map[string]struct { - src channeltypes.Packet - exp wasmvmtypes.IBCPacket - }{ - "with height timeout": { - src: IBCPacketFixture(), - exp: wasmvmtypes.IBCPacket{ - Data: []byte("myData"), - Src: wasmvmtypes.IBCEndpoint{PortID: "srcPort", ChannelID: "channel-1"}, - Dest: wasmvmtypes.IBCEndpoint{PortID: "destPort", ChannelID: "channel-2"}, - Sequence: 1, - Timeout: wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Height: 1, Revision: 2}}, - }, - }, - "with time timeout": { - src: IBCPacketFixture(func(p *channeltypes.Packet) { - p.TimeoutTimestamp = myTimestamp - p.TimeoutHeight = clienttypes.Height{} - }), - exp: wasmvmtypes.IBCPacket{ - Data: []byte("myData"), - Src: wasmvmtypes.IBCEndpoint{PortID: "srcPort", ChannelID: "channel-1"}, - Dest: wasmvmtypes.IBCEndpoint{PortID: "destPort", ChannelID: "channel-2"}, - Sequence: 1, - Timeout: wasmvmtypes.IBCTimeout{Timestamp: myTimestamp}, - }, - }, "with time and height timeout": { - src: IBCPacketFixture(func(p *channeltypes.Packet) { - p.TimeoutTimestamp = myTimestamp - }), - exp: wasmvmtypes.IBCPacket{ - Data: []byte("myData"), - Src: wasmvmtypes.IBCEndpoint{PortID: "srcPort", ChannelID: "channel-1"}, - Dest: wasmvmtypes.IBCEndpoint{PortID: "destPort", ChannelID: "channel-2"}, - Sequence: 1, - Timeout: wasmvmtypes.IBCTimeout{ - Block: &wasmvmtypes.IBCTimeoutBlock{Height: 1, Revision: 2}, - Timestamp: myTimestamp, - }, - }, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - got := newIBCPacket(spec.src) - assert.Equal(t, spec.exp, got) - }) - } -} - -func IBCPacketFixture(mutators ...func(p *channeltypes.Packet)) channeltypes.Packet { - r := channeltypes.Packet{ - Sequence: 1, - SourcePort: "srcPort", - SourceChannel: "channel-1", - DestinationPort: "destPort", - DestinationChannel: "channel-2", - Data: []byte("myData"), - TimeoutHeight: clienttypes.Height{ - RevisionHeight: 1, - RevisionNumber: 2, - }, - TimeoutTimestamp: 0, - } - for _, m := range mutators { - m(&r) - } - return r -} diff --git a/x/wasm/ibctesting/README.md b/x/wasm/ibctesting/README.md deleted file mode 100644 index 1c92869..0000000 --- a/x/wasm/ibctesting/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# testing package for ibc -Customized version of cosmos-sdk x/ibc/testing \ No newline at end of file diff --git a/x/wasm/ibctesting/chain.go b/x/wasm/ibctesting/chain.go deleted file mode 100644 index 844befc..0000000 --- a/x/wasm/ibctesting/chain.go +++ /dev/null @@ -1,611 +0,0 @@ -package ibctesting - -import ( - "fmt" - "testing" - "time" - - errorsmod "cosmossdk.io/errors" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - - "github.com/terpnetwork/terp-core/app" - "github.com/terpnetwork/terp-core/x/wasm" - - // simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/crypto/tmhash" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - tmprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" - tmtypes "github.com/cometbft/cometbft/types" - tmversion "github.com/cometbft/cometbft/version" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - "github.com/cosmos/cosmos-sdk/x/staking/testutil" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - "github.com/cosmos/ibc-go/v7/modules/core/exported" - "github.com/cosmos/ibc-go/v7/modules/core/types" - ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v7/testing" - "github.com/cosmos/ibc-go/v7/testing/mock" - "github.com/stretchr/testify/require" -) - -var MaxAccounts = 10 - -type SenderAccount struct { - SenderPrivKey cryptotypes.PrivKey - SenderAccount authtypes.AccountI -} - -// TestChain is a testing struct that wraps a simapp with the last TM Header, the current ABCI -// header and the validators of the TestChain. It also contains a field called ChainID. This -// is the clientID that *other* chains use to refer to this TestChain. The SenderAccount -// is used for delivering transactions through the application state. -// NOTE: the actual application uses an empty chain-id for ease of testing. -type TestChain struct { - t *testing.T - - Coordinator *Coordinator - App *app.TerpApp - ChainID string - LastHeader *ibctm.Header // header for last block height committed - CurrentHeader tmproto.Header // header for current block height - QueryServer types.QueryServer - TxConfig client.TxConfig - Codec codec.BinaryCodec - - Vals *tmtypes.ValidatorSet - NextVals *tmtypes.ValidatorSet - - // Signers is a map from validator address to the PrivValidator - // The map is converted into an array that is the same order as the validators right before signing commit - // This ensures that signers will always be in correct order even as validator powers change. - // If a test adds a new validator after chain creation, then the signer map must be updated to include - // the new PrivValidator entry. - Signers map[string]tmtypes.PrivValidator - - // autogenerated sender private key - SenderPrivKey cryptotypes.PrivKey - SenderAccount authtypes.AccountI - SenderAccounts []SenderAccount - - PendingSendPackets []channeltypes.Packet -} - -type PacketAck struct { - Packet channeltypes.Packet - Ack []byte -} - -// NewTestChain initializes a new test chain with a default of 4 validators -// Use this function if the tests do not need custom control over the validator set -func NewTestChain(t *testing.T, coord *Coordinator, chainID string, opts ...wasm.Option) *TestChain { - // generate validators private/public key - var ( - validatorsPerChain = 4 - validators = make([]*tmtypes.Validator, 0, validatorsPerChain) - signersByAddress = make(map[string]tmtypes.PrivValidator, validatorsPerChain) - ) - - for i := 0; i < validatorsPerChain; i++ { - privVal := mock.NewPV() - pubKey, err := privVal.GetPubKey() - require.NoError(t, err) - validators = append(validators, tmtypes.NewValidator(pubKey, 1)) - signersByAddress[pubKey.Address().String()] = privVal - } - - // construct validator set; - // Note that the validators are sorted by voting power - // or, if equal, by address lexical order - valSet := tmtypes.NewValidatorSet(validators) - - return NewTestChainWithValSet(t, coord, chainID, valSet, signersByAddress, opts...) -} - -// NewTestChainWithValSet initializes a new TestChain instance with the given validator set -// and signer array. It also initializes 10 Sender accounts with a balance of 10000000000000000000 coins of -// bond denom to use for tests. -// -// The first block height is committed to state in order to allow for client creations on -// counterparty chains. The TestChain will return with a block height starting at 2. -// -// Time management is handled by the Coordinator in order to ensure synchrony between chains. -// Each update of any chain increments the block header time for all chains by 5 seconds. -// -// NOTE: to use a custom sender privkey and account for testing purposes, replace and modify this -// constructor function. -// -// CONTRACT: Validator array must be provided in the order expected by Tendermint. -// i.e. sorted first by power and then lexicographically by address. -func NewTestChainWithValSet(t *testing.T, coord *Coordinator, chainID string, valSet *tmtypes.ValidatorSet, signers map[string]tmtypes.PrivValidator, opts ...wasm.Option) *TestChain { - genAccs := []authtypes.GenesisAccount{} - genBals := []banktypes.Balance{} - senderAccs := []SenderAccount{} - - // generate genesis accounts - for i := 0; i < MaxAccounts; i++ { - senderPrivKey := secp256k1.GenPrivKey() - acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), uint64(i), 0) - amount, ok := sdk.NewIntFromString("10000000000000000000") - require.True(t, ok) - - // add sender account - balance := banktypes.Balance{ - Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), - } - - genAccs = append(genAccs, acc) - genBals = append(genBals, balance) - - senderAcc := SenderAccount{ - SenderAccount: acc, - SenderPrivKey: senderPrivKey, - } - - senderAccs = append(senderAccs, senderAcc) - } - - wasmApp := app.SetupWithGenesisValSet(t, valSet, genAccs, chainID, opts, genBals...) - - // create current header and call begin block - header := tmproto.Header{ - ChainID: chainID, - Height: 1, - Time: coord.CurrentTime.UTC(), - } - - txConfig := wasmApp.TxConfig() - - // create an account to send transactions from - chain := &TestChain{ - t: t, - Coordinator: coord, - ChainID: chainID, - App: wasmApp, - CurrentHeader: header, - QueryServer: wasmApp.IBCKeeper, - TxConfig: txConfig, - Codec: wasmApp.AppCodec(), - Vals: valSet, - NextVals: valSet, - Signers: signers, - SenderPrivKey: senderAccs[0].SenderPrivKey, - SenderAccount: senderAccs[0].SenderAccount, - SenderAccounts: senderAccs, - } - - coord.CommitBlock(chain) - - return chain -} - -// GetContext returns the current context for the application. -func (chain *TestChain) GetContext() sdk.Context { - return chain.App.BaseApp.NewContext(false, chain.CurrentHeader) -} - -// QueryProof performs an abci query with the given key and returns the proto encoded merkle proof -// for the query and the height at which the proof will succeed on a tendermint verifier. -func (chain *TestChain) QueryProof(key []byte) ([]byte, clienttypes.Height) { - return chain.QueryProofAtHeight(key, chain.App.LastBlockHeight()) -} - -// QueryProofAtHeight performs an abci query with the given key and returns the proto encoded merkle proof -// for the query and the height at which the proof will succeed on a tendermint verifier. Only the IBC -// store is supported -func (chain *TestChain) QueryProofAtHeight(key []byte, height int64) ([]byte, clienttypes.Height) { - return chain.QueryProofForStore(exported.StoreKey, key, height) -} - -// QueryProofForStore performs an abci query with the given key and returns the proto encoded merkle proof -// for the query and the height at which the proof will succeed on a tendermint verifier. -func (chain *TestChain) QueryProofForStore(storeKey string, key []byte, height int64) ([]byte, clienttypes.Height) { - res := chain.App.Query(abci.RequestQuery{ - Path: fmt.Sprintf("store/%s/key", storeKey), - Height: height - 1, - Data: key, - Prove: true, - }) - - merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) - require.NoError(chain.t, err) - - proof, err := chain.App.AppCodec().Marshal(&merkleProof) - require.NoError(chain.t, err) - - revision := clienttypes.ParseChainID(chain.ChainID) - - // proof height + 1 is returned as the proof created corresponds to the height the proof - // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it - // have heights 1 above the IAVL tree. Thus we return proof height + 1 - return proof, clienttypes.NewHeight(revision, uint64(res.Height)+1) -} - -// QueryUpgradeProof performs an abci query with the given key and returns the proto encoded merkle proof -// for the query and the height at which the proof will succeed on a tendermint verifier. -func (chain *TestChain) QueryUpgradeProof(key []byte, height uint64) ([]byte, clienttypes.Height) { - res := chain.App.Query(abci.RequestQuery{ - Path: "store/upgrade/key", - Height: int64(height - 1), - Data: key, - Prove: true, - }) - - merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) - require.NoError(chain.t, err) - - proof, err := chain.App.AppCodec().Marshal(&merkleProof) - require.NoError(chain.t, err) - - revision := clienttypes.ParseChainID(chain.ChainID) - - // proof height + 1 is returned as the proof created corresponds to the height the proof - // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it - // have heights 1 above the IAVL tree. Thus we return proof height + 1 - return proof, clienttypes.NewHeight(revision, uint64(res.Height+1)) -} - -// QueryConsensusStateProof performs an abci query for a consensus state -// stored on the given clientID. The proof and consensusHeight are returned. -func (chain *TestChain) QueryConsensusStateProof(clientID string) ([]byte, clienttypes.Height) { - clientState := chain.GetClientState(clientID) - - consensusHeight := clientState.GetLatestHeight().(clienttypes.Height) - consensusKey := host.FullConsensusStateKey(clientID, consensusHeight) - proofConsensus, _ := chain.QueryProof(consensusKey) - - return proofConsensus, consensusHeight -} - -// NextBlock sets the last header to the current header and increments the current header to be -// at the next block height. It does not update the time as that is handled by the Coordinator. -// It will call Endblock and Commit and apply the validator set changes to the next validators -// of the next block being created. This follows the Tendermint protocol of applying valset changes -// returned on block `n` to the validators of block `n+2`. -// It calls BeginBlock with the new block created before returning. -func (chain *TestChain) NextBlock() { - res := chain.App.EndBlock(abci.RequestEndBlock{Height: chain.CurrentHeader.Height}) - - chain.App.Commit() - - // set the last header to the current header - // use nil trusted fields - chain.LastHeader = chain.CurrentTMClientHeader() - - // val set changes returned from previous block get applied to the next validators - // of this block. See tendermint spec for details. - chain.Vals = chain.NextVals - chain.NextVals = ibctesting.ApplyValSetChanges(chain.t, chain.Vals, res.ValidatorUpdates) - - // increment the current header - chain.CurrentHeader = tmproto.Header{ - ChainID: chain.ChainID, - Height: chain.App.LastBlockHeight() + 1, - AppHash: chain.App.LastCommitID().Hash, - // NOTE: the time is increased by the coordinator to maintain time synchrony amongst - // chains. - Time: chain.CurrentHeader.Time, - ValidatorsHash: chain.Vals.Hash(), - NextValidatorsHash: chain.NextVals.Hash(), - ProposerAddress: chain.CurrentHeader.ProposerAddress, - } - - chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) -} - -// sendMsgs delivers a transaction through the application without returning the result. -func (chain *TestChain) sendMsgs(msgs ...sdk.Msg) error { - _, err := chain.SendMsgs(msgs...) - return err -} - -// SendMsgs delivers a transaction through the application. It updates the senders sequence -// number and updates the TestChain's headers. It returns the result and error if one -// occurred. -func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*sdk.Result, error) { - // ensure the chain has the latest time - chain.Coordinator.UpdateTimeForChain(chain) - - _, r, err := app.SignAndDeliverWithoutCommit( - chain.t, - chain.TxConfig, - chain.App.BaseApp, - chain.GetContext().BlockHeader(), - msgs, - chain.ChainID, - []uint64{chain.SenderAccount.GetAccountNumber()}, - []uint64{chain.SenderAccount.GetSequence()}, - chain.SenderPrivKey, - ) - if err != nil { - return nil, err - } - - // NextBlock calls app.Commit() - chain.NextBlock() - - // increment sequence for successful transaction execution - err = chain.SenderAccount.SetSequence(chain.SenderAccount.GetSequence() + 1) - if err != nil { - return nil, err - } - - chain.Coordinator.IncrementTime() - - chain.CaptureIBCEvents(r) - - return r, nil -} - -func (chain *TestChain) CaptureIBCEvents(r *sdk.Result) { - toSend := getSendPackets(r.Events) - if len(toSend) > 0 { - // Keep a queue on the chain that we can relay in tests - chain.PendingSendPackets = append(chain.PendingSendPackets, toSend...) - } -} - -// GetClientState retrieves the client state for the provided clientID. The client is -// expected to exist otherwise testing will fail. -func (chain *TestChain) GetClientState(clientID string) exported.ClientState { - clientState, found := chain.App.IBCKeeper.ClientKeeper.GetClientState(chain.GetContext(), clientID) - require.True(chain.t, found) - - return clientState -} - -// GetConsensusState retrieves the consensus state for the provided clientID and height. -// It will return a success boolean depending on if consensus state exists or not. -func (chain *TestChain) GetConsensusState(clientID string, height exported.Height) (exported.ConsensusState, bool) { - return chain.App.IBCKeeper.ClientKeeper.GetClientConsensusState(chain.GetContext(), clientID, height) -} - -// GetValsAtHeight will return the validator set of the chain at a given height. It will return -// a success boolean depending on if the validator set exists or not at that height. -func (chain *TestChain) GetValsAtHeight(height int64) (*tmtypes.ValidatorSet, bool) { - histInfo, ok := chain.App.StakingKeeper.GetHistoricalInfo(chain.GetContext(), height) - if !ok { - return nil, false - } - - valSet := stakingtypes.Validators(histInfo.Valset) - - tmValidators, err := testutil.ToTmValidators(valSet, sdk.DefaultPowerReduction) - if err != nil { - panic(err) - } - return tmtypes.NewValidatorSet(tmValidators), true -} - -// GetAcknowledgement retrieves an acknowledgement for the provided packet. If the -// acknowledgement does not exist then testing will fail. -func (chain *TestChain) GetAcknowledgement(packet exported.PacketI) []byte { - ack, found := chain.App.IBCKeeper.ChannelKeeper.GetPacketAcknowledgement(chain.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - require.True(chain.t, found) - - return ack -} - -// GetPrefix returns the prefix for used by a chain in connection creation -func (chain *TestChain) GetPrefix() commitmenttypes.MerklePrefix { - return commitmenttypes.NewMerklePrefix(chain.App.IBCKeeper.ConnectionKeeper.GetCommitmentPrefix().Bytes()) -} - -// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the -// light client on the source chain. -func (chain *TestChain) ConstructUpdateTMClientHeader(counterparty *TestChain, clientID string) (*ibctm.Header, error) { - return chain.ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty, clientID, clienttypes.ZeroHeight()) -} - -// ConstructUpdateTMClientHeader will construct a valid 07-tendermint Header to update the -// light client on the source chain. -func (chain *TestChain) ConstructUpdateTMClientHeaderWithTrustedHeight(counterparty *TestChain, clientID string, trustedHeight clienttypes.Height) (*ibctm.Header, error) { - header := counterparty.LastHeader - // Relayer must query for LatestHeight on client to get TrustedHeight if the trusted height is not set - if trustedHeight.IsZero() { - trustedHeight = chain.GetClientState(clientID).GetLatestHeight().(clienttypes.Height) - } - var ( - tmTrustedVals *tmtypes.ValidatorSet - ok bool - ) - // Once we get TrustedHeight from client, we must query the validators from the counterparty chain - // If the LatestHeight == LastHeader.Height, then TrustedValidators are current validators - // If LatestHeight < LastHeader.Height, we can query the historical validator set from HistoricalInfo - if trustedHeight == counterparty.LastHeader.GetHeight() { - tmTrustedVals = counterparty.Vals - } else { - // NOTE: We need to get validators from counterparty at height: trustedHeight+1 - // since the last trusted validators for a header at height h - // is the NextValidators at h+1 committed to in header h by - // NextValidatorsHash - tmTrustedVals, ok = counterparty.GetValsAtHeight(int64(trustedHeight.RevisionHeight + 1)) - if !ok { - return nil, errorsmod.Wrapf(ibctm.ErrInvalidHeaderHeight, "could not retrieve trusted validators at trustedHeight: %d", trustedHeight) - } - } - // inject trusted fields into last header - // for now assume revision number is 0 - header.TrustedHeight = trustedHeight - - trustedVals, err := tmTrustedVals.ToProto() - if err != nil { - return nil, err - } - header.TrustedValidators = trustedVals - - return header, nil -} - -// ExpireClient fast forwards the chain's block time by the provided amount of time which will -// expire any clients with a trusting period less than or equal to this amount of time. -func (chain *TestChain) ExpireClient(amount time.Duration) { - chain.Coordinator.IncrementTimeBy(amount) -} - -// CurrentTMClientHeader creates a TM header using the current header parameters -// on the chain. The trusted fields in the header are set to nil. -func (chain *TestChain) CurrentTMClientHeader() *ibctm.Header { - return chain.CreateTMClientHeader(chain.ChainID, chain.CurrentHeader.Height, clienttypes.Height{}, chain.CurrentHeader.Time, chain.Vals, chain.NextVals, nil, chain.Signers) -} - -// CreateTMClientHeader creates a TM header to update the TM client. Args are passed in to allow -// caller flexibility to use params that differ from the chain. -func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, nextVals, tmTrustedVals *tmtypes.ValidatorSet, signers map[string]tmtypes.PrivValidator) *ibctm.Header { - var ( - valSet *tmproto.ValidatorSet - trustedVals *tmproto.ValidatorSet - ) - require.NotNil(chain.t, tmValSet) - - vsetHash := tmValSet.Hash() - nextValHash := nextVals.Hash() - - tmHeader := tmtypes.Header{ - Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: 2}, - ChainID: chainID, - Height: blockHeight, - Time: timestamp, - LastBlockID: MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), - LastCommitHash: chain.App.LastCommitID().Hash, - DataHash: tmhash.Sum([]byte("data_hash")), - ValidatorsHash: vsetHash, - NextValidatorsHash: nextValHash, - ConsensusHash: tmhash.Sum([]byte("consensus_hash")), - AppHash: chain.CurrentHeader.AppHash, - LastResultsHash: tmhash.Sum([]byte("last_results_hash")), - EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck // SA5011: possible nil pointer dereference - } - - hhash := tmHeader.Hash() - blockID := MakeBlockID(hhash, 3, tmhash.Sum([]byte("part_set"))) - voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) - // MakeCommit expects a signer array in the same order as the validator array. - // Thus we iterate over the ordered validator set and construct a signer array - // from the signer map in the same order. - signerArr := make([]tmtypes.PrivValidator, len(tmValSet.Validators)) //nolint:staticcheck - for i, v := range tmValSet.Validators { //nolint:staticcheck - signerArr[i] = signers[v.Address.String()] - } - - commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signerArr, timestamp) - require.NoError(chain.t, err) - - signedHeader := &tmproto.SignedHeader{ - Header: tmHeader.ToProto(), - Commit: commit.ToProto(), - } - - if tmValSet != nil { //nolint:staticcheck - valSet, err = tmValSet.ToProto() - require.NoError(chain.t, err) - } - - if tmTrustedVals != nil { - trustedVals, err = tmTrustedVals.ToProto() - require.NoError(chain.t, err) - } - - // The trusted fields may be nil. They may be filled before relaying messages to a client. - // The relayer is responsible for querying client and injecting appropriate trusted fields. - return &ibctm.Header{ - SignedHeader: signedHeader, - ValidatorSet: valSet, - TrustedHeight: trustedHeight, - TrustedValidators: trustedVals, - } -} - -// MakeBlockID copied unimported test functions from tmtypes to use them here -func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { - return tmtypes.BlockID{ - Hash: hash, - PartSetHeader: tmtypes.PartSetHeader{ - Total: partSetSize, - Hash: partSetHash, - }, - } -} - -// CreatePortCapability binds and claims a capability for the given portID if it does not -// already exist. This function will fail testing on any resulting error. -// NOTE: only creation of a capability for a transfer or mock port is supported -// Other applications must bind to the port in InitGenesis or modify this code. -func (chain *TestChain) CreatePortCapability(scopedKeeper capabilitykeeper.ScopedKeeper, portID string) { - // check if the portId is already binded, if not bind it - _, ok := chain.App.ScopedIBCKeeper.GetCapability(chain.GetContext(), host.PortPath(portID)) - if !ok { - // create capability using the IBC capability keeper - portCap, err := chain.App.ScopedIBCKeeper.NewCapability(chain.GetContext(), host.PortPath(portID)) - require.NoError(chain.t, err) - - // claim capability using the scopedKeeper - err = scopedKeeper.ClaimCapability(chain.GetContext(), portCap, host.PortPath(portID)) - require.NoError(chain.t, err) - } - - chain.NextBlock() -} - -// GetPortCapability returns the port capability for the given portID. The capability must -// exist, otherwise testing will fail. -func (chain *TestChain) GetPortCapability(portID string) *capabilitytypes.Capability { - portCap, ok := chain.App.ScopedIBCKeeper.GetCapability(chain.GetContext(), host.PortPath(portID)) - require.True(chain.t, ok) - - return portCap -} - -// CreateChannelCapability binds and claims a capability for the given portID and channelID -// if it does not already exist. This function will fail testing on any resulting error. The -// scoped keeper passed in will claim the new capability. -func (chain *TestChain) CreateChannelCapability(scopedKeeper capabilitykeeper.ScopedKeeper, portID, channelID string) { - capName := host.ChannelCapabilityPath(portID, channelID) - // check if the portId is already binded, if not bind it - _, ok := chain.App.ScopedIBCKeeper.GetCapability(chain.GetContext(), capName) - if !ok { - portCap, err := chain.App.ScopedIBCKeeper.NewCapability(chain.GetContext(), capName) - require.NoError(chain.t, err) - err = scopedKeeper.ClaimCapability(chain.GetContext(), portCap, capName) - require.NoError(chain.t, err) - } - - chain.NextBlock() -} - -// GetChannelCapability returns the channel capability for the given portID and channelID. -// The capability must exist, otherwise testing will fail. -func (chain *TestChain) GetChannelCapability(portID, channelID string) *capabilitytypes.Capability { - chanCap, ok := chain.App.ScopedIBCKeeper.GetCapability(chain.GetContext(), host.ChannelCapabilityPath(portID, channelID)) - require.True(chain.t, ok) - - return chanCap -} - -// GetTimeoutHeight is a convenience function which returns a IBC packet timeout height -// to be used for testing. It returns the current IBC height + 100 blocks -func (chain *TestChain) GetTimeoutHeight() clienttypes.Height { - return clienttypes.NewHeight(clienttypes.ParseChainID(chain.ChainID), uint64(chain.GetContext().BlockHeight())+100) -} - -func (chain *TestChain) Balance(acc sdk.AccAddress, denom string) sdk.Coin { - return chain.App.BankKeeper.GetBalance(chain.GetContext(), acc, denom) -} - -func (chain *TestChain) AllBalances(acc sdk.AccAddress) sdk.Coins { - return chain.App.BankKeeper.GetAllBalances(chain.GetContext(), acc) -} diff --git a/x/wasm/ibctesting/coordinator.go b/x/wasm/ibctesting/coordinator.go deleted file mode 100644 index 4f691ff..0000000 --- a/x/wasm/ibctesting/coordinator.go +++ /dev/null @@ -1,311 +0,0 @@ -package ibctesting - -import ( - "fmt" - "testing" - "time" - - abci "github.com/cometbft/cometbft/abci/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - ibctesting "github.com/cosmos/ibc-go/v7/testing" - "github.com/stretchr/testify/require" - - wasmkeeper "github.com/terpnetwork/terp-core/x/wasm/keeper" -) - -var ( - TimeIncrement = time.Second * 5 - globalStartTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) -) - -// Coordinator is a testing struct which contains N TestChain's. It handles keeping all chains -// in sync with regards to time. -type Coordinator struct { - t *testing.T - - CurrentTime time.Time - Chains map[string]*TestChain -} - -// NewCoordinator initializes Coordinator with N TestChain's -func NewCoordinator(t *testing.T, n int, opts ...[]wasmkeeper.Option) *Coordinator { - t.Helper() - chains := make(map[string]*TestChain) - coord := &Coordinator{ - t: t, - CurrentTime: globalStartTime, - } - - for i := 1; i <= n; i++ { - chainID := GetChainID(i) - var x []wasmkeeper.Option - if len(opts) > (i - 1) { - x = opts[i-1] - } - chains[chainID] = NewTestChain(t, coord, chainID, x...) - } - coord.Chains = chains - - return coord -} - -// IncrementTime iterates through all the TestChain's and increments their current header time -// by 5 seconds. -// -// CONTRACT: this function must be called after every Commit on any TestChain. -func (coord *Coordinator) IncrementTime() { - coord.IncrementTimeBy(TimeIncrement) -} - -// IncrementTimeBy iterates through all the TestChain's and increments their current header time -// by specified time. -func (coord *Coordinator) IncrementTimeBy(increment time.Duration) { - coord.CurrentTime = coord.CurrentTime.Add(increment).UTC() - coord.UpdateTime() -} - -// UpdateTime updates all clocks for the TestChains to the current global time. -func (coord *Coordinator) UpdateTime() { - for _, chain := range coord.Chains { - coord.UpdateTimeForChain(chain) - } -} - -// UpdateTimeForChain updates the clock for a specific chain. -func (coord *Coordinator) UpdateTimeForChain(chain *TestChain) { - chain.CurrentHeader.Time = coord.CurrentTime.UTC() - chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) -} - -// Setup constructs a TM client, connection, and channel on both chains provided. It will -// fail if any error occurs. The clientID's, TestConnections, and TestChannels are returned -// for both chains. The channels created are connected to the ibc-transfer application. -func (coord *Coordinator) Setup(path *Path) { - coord.SetupConnections(path) - - // channels can also be referenced through the returned connections - coord.CreateChannels(path) -} - -// SetupClients is a helper function to create clients on both chains. It assumes the -// caller does not anticipate any errors. -func (coord *Coordinator) SetupClients(path *Path) { - err := path.EndpointA.CreateClient() - require.NoError(coord.t, err) - - err = path.EndpointB.CreateClient() - require.NoError(coord.t, err) -} - -// SetupClientConnections is a helper function to create clients and the appropriate -// connections on both the source and counterparty chain. It assumes the caller does not -// anticipate any errors. -func (coord *Coordinator) SetupConnections(path *Path) { - coord.SetupClients(path) - - coord.CreateConnections(path) -} - -// CreateConnection constructs and executes connection handshake messages in order to create -// OPEN channels on chainA and chainB. The connection information of for chainA and chainB -// are returned within a TestConnection struct. The function expects the connections to be -// successfully opened otherwise testing will fail. -func (coord *Coordinator) CreateConnections(path *Path) { - err := path.EndpointA.ConnOpenInit() - require.NoError(coord.t, err) - - err = path.EndpointB.ConnOpenTry() - require.NoError(coord.t, err) - - err = path.EndpointA.ConnOpenAck() - require.NoError(coord.t, err) - - err = path.EndpointB.ConnOpenConfirm() - require.NoError(coord.t, err) - - // ensure counterparty is up to date - err = path.EndpointA.UpdateClient() - require.NoError(coord.t, err) -} - -// CreateMockChannels constructs and executes channel handshake messages to create OPEN -// channels that use a mock application module that returns nil on all callbacks. This -// function is expects the channels to be successfully opened otherwise testing will -// fail. -func (coord *Coordinator) CreateMockChannels(path *Path) { - path.EndpointA.ChannelConfig.PortID = ibctesting.MockPort - path.EndpointB.ChannelConfig.PortID = ibctesting.MockPort - - coord.CreateChannels(path) -} - -// CreateTransferChannels constructs and executes channel handshake messages to create OPEN -// ibc-transfer channels on chainA and chainB. The function expects the channels to be -// successfully opened otherwise testing will fail. -func (coord *Coordinator) CreateTransferChannels(path *Path) { - path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort - path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort - - coord.CreateChannels(path) -} - -// CreateChannel constructs and executes channel handshake messages in order to create -// OPEN channels on chainA and chainB. The function expects the channels to be successfully -// opened otherwise testing will fail. -func (coord *Coordinator) CreateChannels(path *Path) { - err := path.EndpointA.ChanOpenInit() - require.NoError(coord.t, err) - - err = path.EndpointB.ChanOpenTry() - require.NoError(coord.t, err) - - err = path.EndpointA.ChanOpenAck() - require.NoError(coord.t, err) - - err = path.EndpointB.ChanOpenConfirm() - require.NoError(coord.t, err) - - // ensure counterparty is up to date - err = path.EndpointA.UpdateClient() - require.NoError(coord.t, err) -} - -// GetChain returns the TestChain using the given chainID and returns an error if it does -// not exist. -func (coord *Coordinator) GetChain(chainID string) *TestChain { - chain, found := coord.Chains[chainID] - require.True(coord.t, found, fmt.Sprintf("%s chain does not exist", chainID)) - return chain -} - -// GetChainID returns the chainID used for the provided index. -func GetChainID(index int) string { - return ibctesting.GetChainID(index) -} - -// CommitBlock commits a block on the provided indexes and then increments the global time. -// -// CONTRACT: the passed in list of indexes must not contain duplicates -func (coord *Coordinator) CommitBlock(chains ...*TestChain) { - for _, chain := range chains { - chain.NextBlock() - } - coord.IncrementTime() -} - -// CommitNBlocks commits n blocks to state and updates the block height by 1 for each commit. -func (coord *Coordinator) CommitNBlocks(chain *TestChain, n uint64) { - for i := uint64(0); i < n; i++ { - chain.App.BeginBlock(abci.RequestBeginBlock{Header: chain.CurrentHeader}) - chain.NextBlock() - coord.IncrementTime() - } -} - -// ConnOpenInitOnBothChains initializes a connection on both endpoints with the state INIT -// using the OpenInit handshake call. -func (coord *Coordinator) ConnOpenInitOnBothChains(path *Path) error { - if err := path.EndpointA.ConnOpenInit(); err != nil { - return err - } - - if err := path.EndpointB.ConnOpenInit(); err != nil { - return err - } - - if err := path.EndpointA.UpdateClient(); err != nil { - return err - } - - err := path.EndpointB.UpdateClient() - - return err -} - -// ChanOpenInitOnBothChains initializes a channel on the source chain and counterparty chain -// with the state INIT using the OpenInit handshake call. -func (coord *Coordinator) ChanOpenInitOnBothChains(path *Path) error { - // NOTE: only creation of a capability for a transfer or mock port is supported - // Other applications must bind to the port in InitGenesis or modify this code. - - if err := path.EndpointA.ChanOpenInit(); err != nil { - return err - } - - if err := path.EndpointB.ChanOpenInit(); err != nil { - return err - } - - if err := path.EndpointA.UpdateClient(); err != nil { - return err - } - - err := path.EndpointB.UpdateClient() - - return err -} - -// RelayAndAckPendingPackets sends pending packages from path.EndpointA to the counterparty chain and acks -func (coord *Coordinator) RelayAndAckPendingPackets(path *Path) error { - // get all the packet to relay src->dest - src := path.EndpointA - coord.t.Logf("Relay: %d Packets A->B, %d Packets B->A\n", len(src.Chain.PendingSendPackets), len(path.EndpointB.Chain.PendingSendPackets)) - for i, v := range src.Chain.PendingSendPackets { - err := path.RelayPacket(v, nil) - if err != nil { - return err - } - src.Chain.PendingSendPackets = append(src.Chain.PendingSendPackets[0:i], src.Chain.PendingSendPackets[i+1:]...) - } - - src = path.EndpointB - for i, v := range src.Chain.PendingSendPackets { - err := path.RelayPacket(v, nil) - if err != nil { - return err - } - src.Chain.PendingSendPackets = append(src.Chain.PendingSendPackets[0:i], src.Chain.PendingSendPackets[i+1:]...) - } - return nil -} - -// TimeoutPendingPackets returns the package to source chain to let the IBC app revert any operation. -// from A to A -func (coord *Coordinator) TimeoutPendingPackets(path *Path) error { - src := path.EndpointA - dest := path.EndpointB - - toSend := src.Chain.PendingSendPackets - coord.t.Logf("Timeout %d Packets A->A\n", len(toSend)) - - if err := src.UpdateClient(); err != nil { - return err - } - // Increment time and commit block so that 5 second delay period passes between send and receive - coord.IncrementTime() - coord.CommitBlock(src.Chain, dest.Chain) - for _, packet := range toSend { - // get proof of packet unreceived on dest - packetKey := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - proofUnreceived, proofHeight := dest.QueryProof(packetKey) - timeoutMsg := channeltypes.NewMsgTimeout(packet, packet.Sequence, proofUnreceived, proofHeight, src.Chain.SenderAccount.GetAddress().String()) - err := src.Chain.sendMsgs(timeoutMsg) - if err != nil { - return err - } - } - src.Chain.PendingSendPackets = nil - return nil -} - -// CloseChannel close channel on both sides -func (coord *Coordinator) CloseChannel(path *Path) { - err := path.EndpointA.ChanCloseInit() - require.NoError(coord.t, err) - coord.IncrementTime() - err = path.EndpointB.UpdateClient() - require.NoError(coord.t, err) - err = path.EndpointB.ChanCloseConfirm() - require.NoError(coord.t, err) -} diff --git a/x/wasm/ibctesting/endpoint.go b/x/wasm/ibctesting/endpoint.go deleted file mode 100644 index 4eb47ba..0000000 --- a/x/wasm/ibctesting/endpoint.go +++ /dev/null @@ -1,657 +0,0 @@ -package ibctesting - -import ( - "fmt" - "strings" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - "github.com/cosmos/ibc-go/v7/modules/core/exported" - ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v7/testing" - "github.com/stretchr/testify/require" -) - -// Endpoint is a which represents a channel endpoint and its associated -// client and connections. It contains client, connection, and channel -// configuration parameters. Endpoint functions will utilize the parameters -// set in the configuration structs when executing IBC messages. -type Endpoint struct { - Chain *TestChain - Counterparty *Endpoint - ClientID string - ConnectionID string - ChannelID string - - ClientConfig ibctesting.ClientConfig - ConnectionConfig *ibctesting.ConnectionConfig - ChannelConfig *ibctesting.ChannelConfig -} - -// NewEndpoint constructs a new endpoint without the counterparty. -// CONTRACT: the counterparty endpoint must be set by the caller. -func NewEndpoint( - chain *TestChain, clientConfig ibctesting.ClientConfig, - connectionConfig *ibctesting.ConnectionConfig, channelConfig *ibctesting.ChannelConfig, -) *Endpoint { - return &Endpoint{ - Chain: chain, - ClientConfig: clientConfig, - ConnectionConfig: connectionConfig, - ChannelConfig: channelConfig, - } -} - -// NewDefaultEndpoint constructs a new endpoint using default values. -// CONTRACT: the counterparty endpoitn must be set by the caller. -func NewDefaultEndpoint(chain *TestChain) *Endpoint { - return &Endpoint{ - Chain: chain, - ClientConfig: ibctesting.NewTendermintConfig(), - ConnectionConfig: ibctesting.NewConnectionConfig(), - ChannelConfig: ibctesting.NewChannelConfig(), - } -} - -// QueryProof queries proof associated with this endpoint using the lastest client state -// height on the counterparty chain. -func (endpoint *Endpoint) QueryProof(key []byte) ([]byte, clienttypes.Height) { - // obtain the counterparty client representing the chain associated with the endpoint - clientState := endpoint.Counterparty.Chain.GetClientState(endpoint.Counterparty.ClientID) - - // query proof on the counterparty using the latest height of the IBC client - return endpoint.QueryProofAtHeight(key, clientState.GetLatestHeight().GetRevisionHeight()) -} - -// QueryProofAtHeight queries proof associated with this endpoint using the proof height -// provided -func (endpoint *Endpoint) QueryProofAtHeight(key []byte, height uint64) ([]byte, clienttypes.Height) { - // query proof on the counterparty using the latest height of the IBC client - return endpoint.Chain.QueryProofAtHeight(key, int64(height)) -} - -// CreateClient creates an IBC client on the endpoint. It will update the -// clientID for the endpoint if the message is successfully executed. -// NOTE: a solo machine client will be created with an empty diversifier. -func (endpoint *Endpoint) CreateClient() (err error) { - // ensure counterparty has committed state - endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - - var ( - clientState exported.ClientState - consensusState exported.ConsensusState - ) - - switch endpoint.ClientConfig.GetClientType() { - case exported.Tendermint: - tmConfig, ok := endpoint.ClientConfig.(*ibctesting.TendermintConfig) - require.True(endpoint.Chain.t, ok) - - height := endpoint.Counterparty.Chain.LastHeader.GetHeight().(clienttypes.Height) - clientState = ibctm.NewClientState( - endpoint.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, - height, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) - consensusState = endpoint.Counterparty.Chain.LastHeader.ConsensusState() - case exported.Solomachine: - // TODO - // solo := NewSolomachine(chain.t, endpoint.Chain.Codec, clientID, "", 1) - // clientState = solo.ClientState() - // consensusState = solo.ConsensusState() - - default: - err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) - } - - if err != nil { - return err - } - - msg, err := clienttypes.NewMsgCreateClient( - clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), - ) - require.NoError(endpoint.Chain.t, err) - - res, err := endpoint.Chain.SendMsgs(msg) - if err != nil { - return err - } - - endpoint.ClientID, err = ibctesting.ParseClientIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) - - return nil -} - -// UpdateClient updates the IBC client associated with the endpoint. -func (endpoint *Endpoint) UpdateClient() (err error) { - // ensure counterparty has committed state - endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) - - var header exported.ClientMessage - - switch endpoint.ClientConfig.GetClientType() { - case exported.Tendermint: - header, err = endpoint.Chain.ConstructUpdateTMClientHeader(endpoint.Counterparty.Chain, endpoint.ClientID) - - default: - err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) - } - - if err != nil { - return err - } - - msg, err := clienttypes.NewMsgUpdateClient( - endpoint.ClientID, header, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - require.NoError(endpoint.Chain.t, err) - - return endpoint.Chain.sendMsgs(msg) -} - -// UpgradeChain will upgrade a chain's chainID to the next revision number. -// It will also update the counterparty client. -// TODO: implement actual upgrade chain functionality via scheduling an upgrade -// and upgrading the client via MsgUpgradeClient -// see reference https://github.com/cosmos/ibc-go/pull/1169 -func (endpoint *Endpoint) UpgradeChain() error { - if strings.TrimSpace(endpoint.Counterparty.ClientID) == "" { - return fmt.Errorf("cannot upgrade chain if there is no counterparty client") - } - - clientState := endpoint.Counterparty.GetClientState().(*ibctm.ClientState) - - // increment revision number in chainID - - oldChainID := clientState.ChainId - if !clienttypes.IsRevisionFormat(oldChainID) { - return fmt.Errorf("cannot upgrade chain which is not of revision format: %s", oldChainID) - } - - revisionNumber := clienttypes.ParseChainID(oldChainID) - newChainID, err := clienttypes.SetRevisionNumber(oldChainID, revisionNumber+1) - if err != nil { - return err - } - - // update chain - baseapp.SetChainID(newChainID)(endpoint.Chain.App.BaseApp) - endpoint.Chain.ChainID = newChainID - endpoint.Chain.CurrentHeader.ChainID = newChainID - endpoint.Chain.NextBlock() // commit changes - - // update counterparty client manually - clientState.ChainId = newChainID - clientState.LatestHeight = clienttypes.NewHeight(revisionNumber+1, clientState.LatestHeight.GetRevisionHeight()+1) - endpoint.Counterparty.SetClientState(clientState) - - consensusState := &ibctm.ConsensusState{ - Timestamp: endpoint.Chain.LastHeader.GetTime(), - Root: commitmenttypes.NewMerkleRoot(endpoint.Chain.LastHeader.Header.GetAppHash()), - NextValidatorsHash: endpoint.Chain.LastHeader.Header.NextValidatorsHash, - } - endpoint.Counterparty.SetConsensusState(consensusState, clientState.GetLatestHeight()) - - // ensure the next update isn't identical to the one set in state - endpoint.Chain.Coordinator.IncrementTime() - endpoint.Chain.NextBlock() - - if err = endpoint.Counterparty.UpdateClient(); err != nil { - return err - } - - return nil -} - -// ConnOpenInit will construct and execute a MsgConnectionOpenInit on the associated endpoint. -func (endpoint *Endpoint) ConnOpenInit() error { - msg := connectiontypes.NewMsgConnectionOpenInit( - endpoint.ClientID, - endpoint.Counterparty.ClientID, - endpoint.Counterparty.Chain.GetPrefix(), ibctesting.DefaultOpenInitVersion, endpoint.ConnectionConfig.DelayPeriod, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - res, err := endpoint.Chain.SendMsgs(msg) - if err != nil { - return err - } - - endpoint.ConnectionID, err = ibctesting.ParseConnectionIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) - - return nil -} - -// ConnOpenTry will construct and execute a MsgConnectionOpenTry on the associated endpoint. -func (endpoint *Endpoint) ConnOpenTry() error { - err := endpoint.UpdateClient() - require.NoError(endpoint.Chain.t, err) - - counterpartyClient, proofClient, proofConsensus, consensusHeight, proofInit, proofHeight := endpoint.QueryConnectionHandshakeProof() - - msg := connectiontypes.NewMsgConnectionOpenTry( - endpoint.ClientID, endpoint.Counterparty.ConnectionID, endpoint.Counterparty.ClientID, - counterpartyClient, endpoint.Counterparty.Chain.GetPrefix(), []*connectiontypes.Version{ibctesting.ConnectionVersion}, endpoint.ConnectionConfig.DelayPeriod, - proofInit, proofClient, proofConsensus, - proofHeight, consensusHeight, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - res, err := endpoint.Chain.SendMsgs(msg) - if err != nil { - return err - } - - if endpoint.ConnectionID == "" { - endpoint.ConnectionID, err = ibctesting.ParseConnectionIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) - } - - return nil -} - -// ConnOpenAck will construct and execute a MsgConnectionOpenAck on the associated endpoint. -func (endpoint *Endpoint) ConnOpenAck() error { - err := endpoint.UpdateClient() - require.NoError(endpoint.Chain.t, err) - - counterpartyClient, proofClient, proofConsensus, consensusHeight, proofTry, proofHeight := endpoint.QueryConnectionHandshakeProof() - - msg := connectiontypes.NewMsgConnectionOpenAck( - endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, counterpartyClient, // testing doesn't use flexible selection - proofTry, proofClient, proofConsensus, - proofHeight, consensusHeight, - ibctesting.ConnectionVersion, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - return endpoint.Chain.sendMsgs(msg) -} - -// ConnOpenConfirm will construct and execute a MsgConnectionOpenConfirm on the associated endpoint. -func (endpoint *Endpoint) ConnOpenConfirm() error { - err := endpoint.UpdateClient() - require.NoError(endpoint.Chain.t, err) - - connectionKey := host.ConnectionKey(endpoint.Counterparty.ConnectionID) - proof, height := endpoint.Counterparty.Chain.QueryProof(connectionKey) - - msg := connectiontypes.NewMsgConnectionOpenConfirm( - endpoint.ConnectionID, - proof, height, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - return endpoint.Chain.sendMsgs(msg) -} - -// QueryConnectionHandshakeProof returns all the proofs necessary to execute OpenTry or Open Ack of -// the connection handshakes. It returns the counterparty client state, proof of the counterparty -// client state, proof of the counterparty consensus state, the consensus state height, proof of -// the counterparty connection, and the proof height for all the proofs returned. -func (endpoint *Endpoint) QueryConnectionHandshakeProof() ( - clientState exported.ClientState, proofClient, - proofConsensus []byte, consensusHeight clienttypes.Height, - proofConnection []byte, proofHeight clienttypes.Height, -) { - // obtain the client state on the counterparty chain - clientState = endpoint.Counterparty.Chain.GetClientState(endpoint.Counterparty.ClientID) - - // query proof for the client state on the counterparty - clientKey := host.FullClientStateKey(endpoint.Counterparty.ClientID) - proofClient, proofHeight = endpoint.Counterparty.QueryProof(clientKey) - - consensusHeight = clientState.GetLatestHeight().(clienttypes.Height) - - // query proof for the consensus state on the counterparty - consensusKey := host.FullConsensusStateKey(endpoint.Counterparty.ClientID, consensusHeight) - proofConsensus, _ = endpoint.Counterparty.QueryProofAtHeight(consensusKey, proofHeight.GetRevisionHeight()) - - // query proof for the connection on the counterparty - connectionKey := host.ConnectionKey(endpoint.Counterparty.ConnectionID) - proofConnection, _ = endpoint.Counterparty.QueryProofAtHeight(connectionKey, proofHeight.GetRevisionHeight()) - - return -} - -// ChanOpenInit will construct and execute a MsgChannelOpenInit on the associated endpoint. -func (endpoint *Endpoint) ChanOpenInit() error { - msg := channeltypes.NewMsgChannelOpenInit( - endpoint.ChannelConfig.PortID, - endpoint.ChannelConfig.Version, endpoint.ChannelConfig.Order, []string{endpoint.ConnectionID}, - endpoint.Counterparty.ChannelConfig.PortID, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - res, err := endpoint.Chain.SendMsgs(msg) - if err != nil { - return err - } - - endpoint.ChannelID, err = ibctesting.ParseChannelIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) - - // update version to selected app version - // NOTE: this update must be performed after SendMsgs() - endpoint.ChannelConfig.Version = endpoint.GetChannel().Version - - return nil -} - -// ChanOpenTry will construct and execute a MsgChannelOpenTry on the associated endpoint. -func (endpoint *Endpoint) ChanOpenTry() error { - err := endpoint.UpdateClient() - require.NoError(endpoint.Chain.t, err) - - channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) - proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) - - msg := channeltypes.NewMsgChannelOpenTry( - endpoint.ChannelConfig.PortID, - endpoint.ChannelConfig.Version, endpoint.ChannelConfig.Order, []string{endpoint.ConnectionID}, - endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID, endpoint.Counterparty.ChannelConfig.Version, - proof, height, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - res, err := endpoint.Chain.SendMsgs(msg) - if err != nil { - return err - } - - if endpoint.ChannelID == "" { - endpoint.ChannelID, err = ibctesting.ParseChannelIDFromEvents(res.GetEvents()) - require.NoError(endpoint.Chain.t, err) - } - - // update version to selected app version - // NOTE: this update must be performed after the endpoint channelID is set - endpoint.ChannelConfig.Version = endpoint.GetChannel().Version - - return nil -} - -// ChanOpenAck will construct and execute a MsgChannelOpenAck on the associated endpoint. -func (endpoint *Endpoint) ChanOpenAck() error { - err := endpoint.UpdateClient() - require.NoError(endpoint.Chain.t, err) - - channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) - proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) - - msg := channeltypes.NewMsgChannelOpenAck( - endpoint.ChannelConfig.PortID, endpoint.ChannelID, - endpoint.Counterparty.ChannelID, endpoint.Counterparty.ChannelConfig.Version, // testing doesn't use flexible selection - proof, height, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - if err := endpoint.Chain.sendMsgs(msg); err != nil { - return err - } - - endpoint.ChannelConfig.Version = endpoint.GetChannel().Version - return nil -} - -// ChanOpenConfirm will construct and execute a MsgChannelOpenConfirm on the associated endpoint. -func (endpoint *Endpoint) ChanOpenConfirm() error { - err := endpoint.UpdateClient() - require.NoError(endpoint.Chain.t, err) - - channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) - proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) - - msg := channeltypes.NewMsgChannelOpenConfirm( - endpoint.ChannelConfig.PortID, endpoint.ChannelID, - proof, height, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - return endpoint.Chain.sendMsgs(msg) -} - -// ChanCloseInit will construct and execute a MsgChannelCloseInit on the associated endpoint. -// -// NOTE: does not work with ibc-transfer module -func (endpoint *Endpoint) ChanCloseInit() error { - msg := channeltypes.NewMsgChannelCloseInit( - endpoint.ChannelConfig.PortID, endpoint.ChannelID, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - return endpoint.Chain.sendMsgs(msg) -} - -// ChanCloseConfirm will construct and execute a NewMsgChannelCloseConfirm on the associated endpoint. -func (endpoint *Endpoint) ChanCloseConfirm() error { - channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) - proof, proofHeight := endpoint.Counterparty.QueryProof(channelKey) - - msg := channeltypes.NewMsgChannelCloseConfirm( - endpoint.ChannelConfig.PortID, endpoint.ChannelID, - proof, proofHeight, - endpoint.Chain.SenderAccount.GetAddress().String(), - ) - return endpoint.Chain.sendMsgs(msg) -} - -// SendPacket sends a packet through the channel keeper using the associated endpoint -// The counterparty client is updated so proofs can be sent to the counterparty chain. -// The packet sequence generated for the packet to be sent is returned. An error -// is returned if one occurs. -func (endpoint *Endpoint) SendPacket( - timeoutHeight clienttypes.Height, - timeoutTimestamp uint64, - data []byte, -) (uint64, error) { - channelCap := endpoint.Chain.GetChannelCapability(endpoint.ChannelConfig.PortID, endpoint.ChannelID) - - // no need to send message, acting as a module - sequence, err := endpoint.Chain.App.IBCKeeper.ChannelKeeper.SendPacket(endpoint.Chain.GetContext(), channelCap, endpoint.ChannelConfig.PortID, endpoint.ChannelID, timeoutHeight, timeoutTimestamp, data) - if err != nil { - return 0, err - } - - // commit changes since no message was sent - endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) - - err = endpoint.Counterparty.UpdateClient() - if err != nil { - return 0, err - } - - return sequence, nil -} - -// RecvPacket receives a packet on the associated endpoint. -// The counterparty client is updated. -func (endpoint *Endpoint) RecvPacket(packet channeltypes.Packet) error { - _, err := endpoint.RecvPacketWithResult(packet) - if err != nil { - return err - } - - return nil -} - -// RecvPacketWithResult receives a packet on the associated endpoint and the result -// of the transaction is returned. The counterparty client is updated. -func (endpoint *Endpoint) RecvPacketWithResult(packet channeltypes.Packet) (*sdk.Result, error) { - // get proof of packet commitment on source - packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - proof, proofHeight := endpoint.Counterparty.Chain.QueryProof(packetKey) - - recvMsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) - - // receive on counterparty and update source client - res, err := endpoint.Chain.SendMsgs(recvMsg) - if err != nil { - return nil, err - } - - if err := endpoint.Counterparty.UpdateClient(); err != nil { - return nil, err - } - - return res, nil -} - -// WriteAcknowledgement writes an acknowledgement on the channel associated with the endpoint. -// The counterparty client is updated. -func (endpoint *Endpoint) WriteAcknowledgement(ack exported.Acknowledgement, packet exported.PacketI) error { - channelCap := endpoint.Chain.GetChannelCapability(packet.GetDestPort(), packet.GetDestChannel()) - - // no need to send message, acting as a handler - err := endpoint.Chain.App.IBCKeeper.ChannelKeeper.WriteAcknowledgement(endpoint.Chain.GetContext(), channelCap, packet, ack) - if err != nil { - return err - } - - // commit changes since no message was sent - endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) - - return endpoint.Counterparty.UpdateClient() -} - -// AcknowledgePacket sends a MsgAcknowledgement to the channel associated with the endpoint. -func (endpoint *Endpoint) AcknowledgePacket(packet channeltypes.Packet, ack []byte) error { - // get proof of acknowledgement on counterparty - packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) - - ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) - - return endpoint.Chain.sendMsgs(ackMsg) -} - -// TimeoutPacket sends a MsgTimeout to the channel associated with the endpoint. -func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { - // get proof for timeout based on channel order - var packetKey []byte - - switch endpoint.ChannelConfig.Order { - case channeltypes.ORDERED: - packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) - case channeltypes.UNORDERED: - packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - default: - return fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) - } - - proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) - nextSeqRecv, found := endpoint.Counterparty.Chain.App.IBCKeeper.ChannelKeeper.GetNextSequenceRecv(endpoint.Counterparty.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) - require.True(endpoint.Chain.t, found) - - timeoutMsg := channeltypes.NewMsgTimeout( - packet, nextSeqRecv, - proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), - ) - - return endpoint.Chain.sendMsgs(timeoutMsg) -} - -// TimeoutOnClose sends a MsgTimeoutOnClose to the channel associated with the endpoint. -func (endpoint *Endpoint) TimeoutOnClose(packet channeltypes.Packet) error { - // get proof for timeout based on channel order - var packetKey []byte - - switch endpoint.ChannelConfig.Order { - case channeltypes.ORDERED: - packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) - case channeltypes.UNORDERED: - packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) - default: - return fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) - } - - proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) - - channelKey := host.ChannelKey(packet.GetDestPort(), packet.GetDestChannel()) - proofClosed, _ := endpoint.Counterparty.QueryProof(channelKey) - - nextSeqRecv, found := endpoint.Counterparty.Chain.App.IBCKeeper.ChannelKeeper.GetNextSequenceRecv(endpoint.Counterparty.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) - require.True(endpoint.Chain.t, found) - - timeoutOnCloseMsg := channeltypes.NewMsgTimeoutOnClose( - packet, nextSeqRecv, - proof, proofClosed, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), - ) - - return endpoint.Chain.sendMsgs(timeoutOnCloseMsg) -} - -// SetChannelClosed sets a channel state to CLOSED. -func (endpoint *Endpoint) SetChannelClosed() error { - channel := endpoint.GetChannel() - - channel.State = channeltypes.CLOSED - endpoint.Chain.App.IBCKeeper.ChannelKeeper.SetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, channel) - - endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) - - return endpoint.Counterparty.UpdateClient() -} - -// GetClientState retrieves the Client State for this endpoint. The -// client state is expected to exist otherwise testing will fail. -func (endpoint *Endpoint) GetClientState() exported.ClientState { - return endpoint.Chain.GetClientState(endpoint.ClientID) -} - -// SetClientState sets the client state for this endpoint. -func (endpoint *Endpoint) SetClientState(clientState exported.ClientState) { - endpoint.Chain.App.IBCKeeper.ClientKeeper.SetClientState(endpoint.Chain.GetContext(), endpoint.ClientID, clientState) -} - -// GetConsensusState retrieves the Consensus State for this endpoint at the provided height. -// The consensus state is expected to exist otherwise testing will fail. -func (endpoint *Endpoint) GetConsensusState(height exported.Height) exported.ConsensusState { - consensusState, found := endpoint.Chain.GetConsensusState(endpoint.ClientID, height) - require.True(endpoint.Chain.t, found) - - return consensusState -} - -// SetConsensusState sets the consensus state for this endpoint. -func (endpoint *Endpoint) SetConsensusState(consensusState exported.ConsensusState, height exported.Height) { - endpoint.Chain.App.IBCKeeper.ClientKeeper.SetClientConsensusState(endpoint.Chain.GetContext(), endpoint.ClientID, height, consensusState) -} - -// GetConnection retrieves an IBC Connection for the endpoint. The -// connection is expected to exist otherwise testing will fail. -func (endpoint *Endpoint) GetConnection() connectiontypes.ConnectionEnd { - connection, found := endpoint.Chain.App.IBCKeeper.ConnectionKeeper.GetConnection(endpoint.Chain.GetContext(), endpoint.ConnectionID) - require.True(endpoint.Chain.t, found) - - return connection -} - -// SetConnection sets the connection for this endpoint. -func (endpoint *Endpoint) SetConnection(connection connectiontypes.ConnectionEnd) { - endpoint.Chain.App.IBCKeeper.ConnectionKeeper.SetConnection(endpoint.Chain.GetContext(), endpoint.ConnectionID, connection) -} - -// GetChannel retrieves an IBC Channel for the endpoint. The channel -// is expected to exist otherwise testing will fail. -func (endpoint *Endpoint) GetChannel() channeltypes.Channel { - channel, found := endpoint.Chain.App.IBCKeeper.ChannelKeeper.GetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) - require.True(endpoint.Chain.t, found) - - return channel -} - -// SetChannel sets the channel for this endpoint. -func (endpoint *Endpoint) SetChannel(channel channeltypes.Channel) { - endpoint.Chain.App.IBCKeeper.ChannelKeeper.SetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, channel) -} - -// QueryClientStateProof performs and abci query for a client stat associated -// with this endpoint and returns the ClientState along with the proof. -func (endpoint *Endpoint) QueryClientStateProof() (exported.ClientState, []byte) { - // retrieve client state to provide proof for - clientState := endpoint.GetClientState() - - clientKey := host.FullClientStateKey(endpoint.ClientID) - proofClient, _ := endpoint.QueryProof(clientKey) - - return clientState, proofClient -} diff --git a/x/wasm/ibctesting/event_utils.go b/x/wasm/ibctesting/event_utils.go deleted file mode 100644 index a376901..0000000 --- a/x/wasm/ibctesting/event_utils.go +++ /dev/null @@ -1,118 +0,0 @@ -package ibctesting - -import ( - "encoding/hex" - "fmt" - "strconv" - "strings" - - sdk "github.com/cosmos/cosmos-sdk/types" - - abci "github.com/cometbft/cometbft/abci/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" -) - -func getSendPackets(evts []abci.Event) []channeltypes.Packet { - var res []channeltypes.Packet - for _, evt := range evts { - if evt.Type == channeltypes.EventTypeSendPacket { - packet := parsePacketFromEvent(evt) - res = append(res, packet) - } - } - return res -} - -// Used for various debug statements above when needed... do not remove -// func showEvent(evt abci.Event) { -// fmt.Printf("evt.Type: %s\n", evt.Type) -// for _, attr := range evt.Attributes { -// fmt.Printf(" %s = %s\n", string(attr.Key), string(attr.Value)) -// } -//} - -func parsePacketFromEvent(evt abci.Event) channeltypes.Packet { - return channeltypes.Packet{ - Sequence: getUintField(evt, channeltypes.AttributeKeySequence), - SourcePort: getField(evt, channeltypes.AttributeKeySrcPort), - SourceChannel: getField(evt, channeltypes.AttributeKeySrcChannel), - DestinationPort: getField(evt, channeltypes.AttributeKeyDstPort), - DestinationChannel: getField(evt, channeltypes.AttributeKeyDstChannel), - Data: getHexField(evt, channeltypes.AttributeKeyDataHex), - TimeoutHeight: parseTimeoutHeight(getField(evt, channeltypes.AttributeKeyTimeoutHeight)), - TimeoutTimestamp: getUintField(evt, channeltypes.AttributeKeyTimeoutTimestamp), - } -} - -func getHexField(evt abci.Event, key string) []byte { - got := getField(evt, key) - if got == "" { - return nil - } - bz, err := hex.DecodeString(got) - if err != nil { - panic(err) - } - return bz -} - -// return the value for the attribute with the given name -func getField(evt abci.Event, key string) string { - for _, attr := range evt.Attributes { - if attr.Key == key { - return attr.Value - } - } - return "" -} - -func getUintField(evt abci.Event, key string) uint64 { - raw := getField(evt, key) - return toUint64(raw) -} - -func toUint64(raw string) uint64 { - if raw == "" { - return 0 - } - i, err := strconv.ParseUint(raw, 10, 64) - if err != nil { - panic(err) - } - return i -} - -func parseTimeoutHeight(raw string) clienttypes.Height { - chunks := strings.Split(raw, "-") - return clienttypes.Height{ - RevisionNumber: toUint64(chunks[0]), - RevisionHeight: toUint64(chunks[1]), - } -} - -func ParsePortIDFromEvents(events sdk.Events) (string, error) { - for _, ev := range events { - if ev.Type == channeltypes.EventTypeChannelOpenInit || ev.Type == channeltypes.EventTypeChannelOpenTry { - for _, attr := range ev.Attributes { - if attr.Key == channeltypes.AttributeKeyPortID { - return attr.Value, nil - } - } - } - } - return "", fmt.Errorf("port id event attribute not found") -} - -func ParseChannelVersionFromEvents(events sdk.Events) (string, error) { - for _, ev := range events { - if ev.Type == channeltypes.EventTypeChannelOpenInit || ev.Type == channeltypes.EventTypeChannelOpenTry { - for _, attr := range ev.Attributes { - if attr.Key == channeltypes.AttributeVersion { - return attr.Value, nil - } - } - } - } - return "", fmt.Errorf("version event attribute not found") -} diff --git a/x/wasm/ibctesting/faucet.go b/x/wasm/ibctesting/faucet.go deleted file mode 100644 index 46da6c3..0000000 --- a/x/wasm/ibctesting/faucet.go +++ /dev/null @@ -1,53 +0,0 @@ -package ibctesting - -import ( - "cosmossdk.io/math" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/app" -) - -// Fund an address with the given amount in default denom -func (chain *TestChain) Fund(addr sdk.AccAddress, amount math.Int) { - require.NoError(chain.t, chain.sendMsgs(&banktypes.MsgSend{ - FromAddress: chain.SenderAccount.GetAddress().String(), - ToAddress: addr.String(), - Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), - })) -} - -// SendNonDefaultSenderMsgs delivers a transaction through the application. It returns the result and error if one -// occurred. -func (chain *TestChain) SendNonDefaultSenderMsgs(senderPrivKey cryptotypes.PrivKey, msgs ...sdk.Msg) (*sdk.Result, error) { - require.NotEqual(chain.t, chain.SenderPrivKey, senderPrivKey, "use SendMsgs method") - - // ensure the chain has the latest time - chain.Coordinator.UpdateTimeForChain(chain) - - addr := sdk.AccAddress(senderPrivKey.PubKey().Address().Bytes()) - account := chain.App.AccountKeeper.GetAccount(chain.GetContext(), addr) - require.NotNil(chain.t, account) - _, r, err := app.SignAndDeliverWithoutCommit( - chain.t, - chain.TxConfig, - chain.App.BaseApp, - chain.GetContext().BlockHeader(), - msgs, - chain.ChainID, - []uint64{account.GetAccountNumber()}, - []uint64{account.GetSequence()}, - senderPrivKey, - ) - - // SignAndDeliverWithoutCommit calls app.Commit() - chain.NextBlock() - chain.Coordinator.IncrementTime() - if err != nil { - return r, err - } - chain.CaptureIBCEvents(r) - return r, nil -} diff --git a/x/wasm/ibctesting/path.go b/x/wasm/ibctesting/path.go deleted file mode 100644 index c7f1af8..0000000 --- a/x/wasm/ibctesting/path.go +++ /dev/null @@ -1,109 +0,0 @@ -package ibctesting - -import ( - "bytes" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - ibctesting "github.com/cosmos/ibc-go/v7/testing" -) - -// Path contains two endpoints representing two chains connected over IBC -type Path struct { - EndpointA *Endpoint - EndpointB *Endpoint -} - -// NewPath constructs an endpoint for each chain using the default values -// for the endpoints. Each endpoint is updated to have a pointer to the -// counterparty endpoint. -func NewPath(chainA, chainB *TestChain) *Path { - endpointA := NewDefaultEndpoint(chainA) - endpointB := NewDefaultEndpoint(chainB) - - endpointA.Counterparty = endpointB - endpointB.Counterparty = endpointA - - return &Path{ - EndpointA: endpointA, - EndpointB: endpointB, - } -} - -// SetChannelOrdered sets the channel order for both endpoints to ORDERED. -func (path *Path) SetChannelOrdered() { - path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED - path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED -} - -// RelayPacket attempts to relay the packet first on EndpointA and then on EndpointB -// if EndpointA does not contain a packet commitment for that packet. An error is returned -// if a relay step fails or the packet commitment does not exist on either endpoint. -func (path *Path) RelayPacket(packet channeltypes.Packet, _ []byte) error { - pc := path.EndpointA.Chain.App.IBCKeeper.ChannelKeeper.GetPacketCommitment(path.EndpointA.Chain.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - if bytes.Equal(pc, channeltypes.CommitPacket(path.EndpointA.Chain.App.AppCodec(), packet)) { - - // packet found, relay from A to B - if err := path.EndpointB.UpdateClient(); err != nil { - return err - } - - res, err := path.EndpointB.RecvPacketWithResult(packet) - if err != nil { - return err - } - - ack, err := ibctesting.ParseAckFromEvents(res.GetEvents()) - if err != nil { - return err - } - - err = path.EndpointA.AcknowledgePacket(packet, ack) - - return err - } - - pc = path.EndpointB.Chain.App.IBCKeeper.ChannelKeeper.GetPacketCommitment(path.EndpointB.Chain.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) - if bytes.Equal(pc, channeltypes.CommitPacket(path.EndpointB.Chain.App.AppCodec(), packet)) { - - // packet found, relay B to A - if err := path.EndpointA.UpdateClient(); err != nil { - return err - } - - res, err := path.EndpointA.RecvPacketWithResult(packet) - if err != nil { - return err - } - - ack, err := ibctesting.ParseAckFromEvents(res.GetEvents()) - if err != nil { - return err - } - - err = path.EndpointB.AcknowledgePacket(packet, ack) - return err - } - - return fmt.Errorf("packet commitment does not exist on either endpoint for provided packet") -} - -// SendMsg delivers the provided messages to the chain. The counterparty -// client is updated with the new source consensus state. -func (path *Path) SendMsg(msgs ...sdk.Msg) error { - if err := path.EndpointA.Chain.sendMsgs(msgs...); err != nil { - return err - } - if err := path.EndpointA.UpdateClient(); err != nil { - return err - } - return path.EndpointB.UpdateClient() -} - -func (path *Path) Invert() *Path { - return &Path{ - EndpointA: path.EndpointB, - EndpointB: path.EndpointA, - } -} diff --git a/x/wasm/ibctesting/wasm.go b/x/wasm/ibctesting/wasm.go deleted file mode 100644 index d8e5b50..0000000 --- a/x/wasm/ibctesting/wasm.go +++ /dev/null @@ -1,129 +0,0 @@ -package ibctesting - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "fmt" - "os" - "strings" - - ibctesting "github.com/cosmos/ibc-go/v7/testing" - - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/libs/rand" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var wasmIdent = []byte("\x00\x61\x73\x6D") - -// SeedNewContractInstance stores some wasm code and instantiates a new contract on this chain. -// This method can be called to prepare the store with some valid CodeInfo and ContractInfo. The returned -// Address is the contract address for this instance. Test should make use of this data and/or use NewIBCContractMockWasmer -// for using a contract mock in Go. -func (chain *TestChain) SeedNewContractInstance() sdk.AccAddress { - pInstResp := chain.StoreCode(append(wasmIdent, rand.Bytes(10)...)) - codeID := pInstResp.CodeID - - anyAddressStr := chain.SenderAccount.GetAddress().String() - initMsg := []byte(fmt.Sprintf(`{"verifier": %q, "beneficiary": %q}`, anyAddressStr, anyAddressStr)) - return chain.InstantiateContract(codeID, initMsg) -} - -func (chain *TestChain) StoreCodeFile(filename string) types.MsgStoreCodeResponse { - wasmCode, err := os.ReadFile(filename) - require.NoError(chain.t, err) - if strings.HasSuffix(filename, "wasm") { // compress for gas limit - var buf bytes.Buffer - gz := gzip.NewWriter(&buf) - _, err := gz.Write(wasmCode) - require.NoError(chain.t, err) - err = gz.Close() - require.NoError(chain.t, err) - wasmCode = buf.Bytes() - } - return chain.StoreCode(wasmCode) -} - -func (chain *TestChain) StoreCode(byteCode []byte) types.MsgStoreCodeResponse { - storeMsg := &types.MsgStoreCode{ - Sender: chain.SenderAccount.GetAddress().String(), - WASMByteCode: byteCode, - } - r, err := chain.SendMsgs(storeMsg) - require.NoError(chain.t, err) - // unmarshal protobuf response from data - require.Len(chain.t, r.MsgResponses, 1) - require.NotEmpty(chain.t, r.MsgResponses[0].GetCachedValue()) - pInstResp := r.MsgResponses[0].GetCachedValue().(*types.MsgStoreCodeResponse) - require.NotEmpty(chain.t, pInstResp.CodeID) - require.NotEmpty(chain.t, pInstResp.Checksum) - return *pInstResp -} - -func (chain *TestChain) InstantiateContract(codeID uint64, initMsg []byte) sdk.AccAddress { - instantiateMsg := &types.MsgInstantiateContract{ - Sender: chain.SenderAccount.GetAddress().String(), - Admin: chain.SenderAccount.GetAddress().String(), - CodeID: codeID, - Label: "ibc-test", - Msg: initMsg, - Funds: sdk.Coins{ibctesting.TestCoin}, - } - - r, err := chain.SendMsgs(instantiateMsg) - require.NoError(chain.t, err) - require.Len(chain.t, r.MsgResponses, 1) - require.NotEmpty(chain.t, r.MsgResponses[0].GetCachedValue()) - pExecResp := r.MsgResponses[0].GetCachedValue().(*types.MsgInstantiateContractResponse) - a, err := sdk.AccAddressFromBech32(pExecResp.Address) - require.NoError(chain.t, err) - return a -} - -// SmartQuery This will serialize the query message and submit it to the contract. -// The response is parsed into the provided interface. -// Usage: SmartQuery(addr, QueryMsg{Foo: 1}, &response) -func (chain *TestChain) SmartQuery(contractAddr string, queryMsg interface{}, response interface{}) error { - msg, err := json.Marshal(queryMsg) - if err != nil { - return err - } - - req := types.QuerySmartContractStateRequest{ - Address: contractAddr, - QueryData: msg, - } - reqBin, err := proto.Marshal(&req) - if err != nil { - return err - } - - // TODO: what is the query? - res := chain.App.Query(abci.RequestQuery{ - Path: "/cosmwasm.wasm.v1.Query/SmartContractState", - Data: reqBin, - }) - - if res.Code != 0 { - return fmt.Errorf("query failed: (%d) %s", res.Code, res.Log) - } - - // unpack protobuf - var resp types.QuerySmartContractStateResponse - err = proto.Unmarshal(res.Value, &resp) - if err != nil { - return err - } - // unpack json content - return json.Unmarshal(resp.Data, response) -} - -// ContractInfo is a helper function to returns the ContractInfo for the given contract address -func (chain *TestChain) ContractInfo(contractAddr sdk.AccAddress) *types.ContractInfo { - return chain.App.WasmKeeper.GetContractInfo(chain.GetContext(), contractAddr) -} diff --git a/x/wasm/ioutils/ioutil.go b/x/wasm/ioutils/ioutil.go deleted file mode 100644 index 93adb4f..0000000 --- a/x/wasm/ioutils/ioutil.go +++ /dev/null @@ -1,41 +0,0 @@ -package ioutils - -import ( - "bytes" - "compress/gzip" - "io" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// Uncompress expects a valid gzip source to unpack or fails. See IsGzip -func Uncompress(gzipSrc []byte, limit uint64) ([]byte, error) { - if uint64(len(gzipSrc)) > limit { - return nil, types.ErrLimit - } - zr, err := gzip.NewReader(bytes.NewReader(gzipSrc)) - if err != nil { - return nil, err - } - zr.Multistream(false) - defer zr.Close() - return io.ReadAll(LimitReader(zr, int64(limit))) -} - -// LimitReader returns a Reader that reads from r -// but stops with types.ErrLimit after n bytes. -// The underlying implementation is a *io.LimitedReader. -func LimitReader(r io.Reader, n int64) io.Reader { - return &LimitedReader{r: &io.LimitedReader{R: r, N: n}} -} - -type LimitedReader struct { - r *io.LimitedReader -} - -func (l *LimitedReader) Read(p []byte) (n int, err error) { - if l.r.N <= 0 { - return 0, types.ErrLimit - } - return l.r.Read(p) -} diff --git a/x/wasm/ioutils/ioutil_test.go b/x/wasm/ioutils/ioutil_test.go deleted file mode 100644 index 50f1a81..0000000 --- a/x/wasm/ioutils/ioutil_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package ioutils - -import ( - "bytes" - "compress/gzip" - "errors" - "io" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestUncompress(t *testing.T) { - wasmRaw, err := os.ReadFile("../keeper/testdata/hackatom.wasm") - require.NoError(t, err) - - wasmGzipped, err := os.ReadFile("../keeper/testdata/hackatom.wasm.gzip") - require.NoError(t, err) - - const maxSize = 400_000 - - specs := map[string]struct { - src []byte - expError error - expResult []byte - }{ - "handle wasm compressed": { - src: wasmGzipped, - expResult: wasmRaw, - }, - "handle gzip identifier only": { - src: gzipIdent, - expError: io.ErrUnexpectedEOF, - }, - "handle broken gzip": { - src: append(gzipIdent, byte(0x1)), - expError: io.ErrUnexpectedEOF, - }, - "handle incomplete gzip": { - src: wasmGzipped[:len(wasmGzipped)-5], - expError: io.ErrUnexpectedEOF, - }, - "handle limit gzip output": { - src: asGzip(bytes.Repeat([]byte{0x1}, maxSize)), - expResult: bytes.Repeat([]byte{0x1}, maxSize), - }, - "handle big gzip output": { - src: asGzip(bytes.Repeat([]byte{0x1}, maxSize+1)), - expError: types.ErrLimit, - }, - "handle other big gzip output": { - src: asGzip(bytes.Repeat([]byte{0x1}, 2*maxSize)), - expError: types.ErrLimit, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - r, err := Uncompress(spec.src, maxSize) - require.True(t, errors.Is(spec.expError, err), "exp %v got %+v", spec.expError, err) - if spec.expError != nil { - return - } - assert.Equal(t, spec.expResult, r) - }) - } -} - -func asGzip(src []byte) []byte { - var buf bytes.Buffer - zipper := gzip.NewWriter(&buf) - if _, err := io.Copy(zipper, bytes.NewReader(src)); err != nil { - panic(err) - } - if err := zipper.Close(); err != nil { - panic(err) - } - return buf.Bytes() -} diff --git a/x/wasm/ioutils/utils.go b/x/wasm/ioutils/utils.go deleted file mode 100644 index 9c9cd38..0000000 --- a/x/wasm/ioutils/utils.go +++ /dev/null @@ -1,45 +0,0 @@ -package ioutils - -import ( - "bytes" - "compress/gzip" -) - -// Note: []byte can never be const as they are inherently mutable -var ( - // magic bytes to identify gzip. - // See https://www.ietf.org/rfc/rfc1952.txt - // and https://github.com/golang/go/blob/master/src/net/http/sniff.go#L186 - gzipIdent = []byte("\x1F\x8B\x08") - - // magic number for Wasm is "\0asm" - // See https://webassembly.github.io/spec/core/binary/modules.html#binary-module - wasmIdent = []byte("\x00\x61\x73\x6D") -) - -// IsGzip returns checks if the file contents are gzip compressed -func IsGzip(input []byte) bool { - return len(input) >= 3 && bytes.Equal(gzipIdent, input[0:3]) -} - -// IsWasm checks if the file contents are of wasm binary -func IsWasm(input []byte) bool { - return bytes.Equal(input[:4], wasmIdent) -} - -// GzipIt compresses the input ([]byte) -func GzipIt(input []byte) ([]byte, error) { - // Create gzip writer. - var b bytes.Buffer - w := gzip.NewWriter(&b) - _, err := w.Write(input) - if err != nil { - return nil, err - } - err = w.Close() // You must close this first to flush the bytes to the buffer. - if err != nil { - return nil, err - } - - return b.Bytes(), nil -} diff --git a/x/wasm/ioutils/utils_test.go b/x/wasm/ioutils/utils_test.go deleted file mode 100644 index 2dea0c5..0000000 --- a/x/wasm/ioutils/utils_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package ioutils - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func GetTestData() ([]byte, []byte, []byte, error) { - wasmCode, err := os.ReadFile("../keeper/testdata/hackatom.wasm") - if err != nil { - return nil, nil, nil, err - } - - gzipData, err := GzipIt(wasmCode) - if err != nil { - return nil, nil, nil, err - } - - someRandomStr := []byte("hello world") - - return wasmCode, someRandomStr, gzipData, nil -} - -func TestIsWasm(t *testing.T) { - wasmCode, someRandomStr, gzipData, err := GetTestData() - require.NoError(t, err) - - t.Log("should return false for some random string data") - require.False(t, IsWasm(someRandomStr)) - t.Log("should return false for gzip data") - require.False(t, IsWasm(gzipData)) - t.Log("should return true for exact wasm") - require.True(t, IsWasm(wasmCode)) -} - -func TestIsGzip(t *testing.T) { - wasmCode, someRandomStr, gzipData, err := GetTestData() - require.NoError(t, err) - - require.False(t, IsGzip(wasmCode)) - require.False(t, IsGzip(someRandomStr)) - require.False(t, IsGzip(nil)) - require.True(t, IsGzip(gzipData[0:3])) - require.True(t, IsGzip(gzipData)) -} - -func TestGzipIt(t *testing.T) { - wasmCode, someRandomStr, _, err := GetTestData() - originalGzipData := []byte{ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 202, 72, 205, 201, 201, 87, 40, 207, 47, 202, 73, 1, - 4, 0, 0, 255, 255, 133, 17, 74, 13, 11, 0, 0, 0, - } - - require.NoError(t, err) - - t.Log("gzip wasm with no error") - _, err = GzipIt(wasmCode) - require.NoError(t, err) - - t.Log("gzip of a string should return exact gzip data") - strToGzip, err := GzipIt(someRandomStr) - - require.True(t, IsGzip(strToGzip)) - require.NoError(t, err) - require.Equal(t, originalGzipData, strToGzip) -} diff --git a/x/wasm/keeper/addresses.go b/x/wasm/keeper/addresses.go deleted file mode 100644 index 80f8ab5..0000000 --- a/x/wasm/keeper/addresses.go +++ /dev/null @@ -1,76 +0,0 @@ -package keeper - -import ( - "encoding/binary" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// AddressGenerator abstract address generator to be used for a single contract address -type AddressGenerator func(ctx sdk.Context, codeID uint64, checksum []byte) sdk.AccAddress - -// ClassicAddressGenerator generates a contract address using codeID and instanceID sequence -func (k Keeper) ClassicAddressGenerator() AddressGenerator { - return func(ctx sdk.Context, codeID uint64, _ []byte) sdk.AccAddress { - instanceID := k.autoIncrementID(ctx, types.KeyLastInstanceID) - return BuildContractAddressClassic(codeID, instanceID) - } -} - -// PredicableAddressGenerator generates a predictable contract address -func PredicableAddressGenerator(creator sdk.AccAddress, salt []byte, msg []byte, fixMsg bool) AddressGenerator { - return func(ctx sdk.Context, _ uint64, checksum []byte) sdk.AccAddress { - if !fixMsg { // clear msg to not be included in the address generation - msg = []byte{} - } - return BuildContractAddressPredictable(checksum, creator, salt, msg) - } -} - -// BuildContractAddressClassic builds an sdk account address for a contract. -func BuildContractAddressClassic(codeID, instanceID uint64) sdk.AccAddress { - contractID := make([]byte, 16) - binary.BigEndian.PutUint64(contractID[:8], codeID) - binary.BigEndian.PutUint64(contractID[8:], instanceID) - return address.Module(types.ModuleName, contractID)[:types.ContractAddrLen] -} - -// BuildContractAddressPredictable generates a contract address for the wasm module with len = types.ContractAddrLen using the -// Cosmos SDK address.Module function. -// Internally a key is built containing: -// (len(checksum) | checksum | len(sender_address) | sender_address | len(salt) | salt| len(initMsg) | initMsg). -// -// All method parameter values must be valid and not nil. -func BuildContractAddressPredictable(checksum []byte, creator sdk.AccAddress, salt, initMsg types.RawContractMessage) sdk.AccAddress { - if len(checksum) != 32 { - panic("invalid checksum") - } - if err := sdk.VerifyAddressFormat(creator); err != nil { - panic(fmt.Sprintf("creator: %s", err)) - } - if err := types.ValidateSalt(salt); err != nil { - panic(fmt.Sprintf("salt: %s", err)) - } - if err := initMsg.ValidateBasic(); len(initMsg) != 0 && err != nil { - panic(fmt.Sprintf("initMsg: %s", err)) - } - checksum = UInt64LengthPrefix(checksum) - creator = UInt64LengthPrefix(creator) - salt = UInt64LengthPrefix(salt) - initMsg = UInt64LengthPrefix(initMsg) - key := make([]byte, len(checksum)+len(creator)+len(salt)+len(initMsg)) - copy(key[0:], checksum) - copy(key[len(checksum):], creator) - copy(key[len(checksum)+len(creator):], salt) - copy(key[len(checksum)+len(creator)+len(salt):], initMsg) - return address.Module(types.ModuleName, key)[:types.ContractAddrLen] -} - -// UInt64LengthPrefix prepend big endian encoded byte length -func UInt64LengthPrefix(bz []byte) []byte { - return append(sdk.Uint64ToBigEndian(uint64(len(bz))), bz...) -} diff --git a/x/wasm/keeper/addresses_test.go b/x/wasm/keeper/addresses_test.go deleted file mode 100644 index 399c3e7..0000000 --- a/x/wasm/keeper/addresses_test.go +++ /dev/null @@ -1,432 +0,0 @@ -package keeper - -import ( - "encoding/json" - "fmt" - "testing" - - tmbytes "github.com/cometbft/cometbft/libs/bytes" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" -) - -func TestBuildContractAddress(t *testing.T) { - x, y := sdk.GetConfig().GetBech32AccountAddrPrefix(), sdk.GetConfig().GetBech32AccountPubPrefix() - t.Cleanup(func() { - sdk.GetConfig().SetBech32PrefixForAccount(x, y) - }) - sdk.GetConfig().SetBech32PrefixForAccount("purple", "purple") - - // test vectors generated via cosmjs: https://github.com/cosmos/cosmjs/pull/1253/files - type Spec struct { - In struct { - Checksum tmbytes.HexBytes `json:"checksum"` - Creator sdk.AccAddress `json:"creator"` - Salt tmbytes.HexBytes `json:"salt"` - Msg string `json:"msg"` - } `json:"in"` - Out struct { - Address sdk.AccAddress `json:"address"` - } `json:"out"` - } - var specs []Spec - require.NoError(t, json.Unmarshal([]byte(goldenMasterPredictableContractAddr), &specs)) - require.NotEmpty(t, specs) - for i, spec := range specs { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { - // when - gotAddr := BuildContractAddressPredictable(spec.In.Checksum, spec.In.Creator, spec.In.Salt.Bytes(), []byte(spec.In.Msg)) - - require.Equal(t, spec.Out.Address.String(), gotAddr.String()) - require.NoError(t, sdk.VerifyAddressFormat(gotAddr)) - }) - } -} - -const goldenMasterPredictableContractAddr = `[ - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "61", - "msg": null - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000001610000000000000000", - "addressData": "5e865d3e45ad3e961f77fd77d46543417ced44d924dc3e079b5415ff6775f847" - }, - "out": { - "address": "purple1t6r960j945lfv8mhl4mage2rg97w63xeynwrupum2s2l7em4lprs9ce5hk" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "61", - "msg": "{}" - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc00000000000000016100000000000000027b7d", - "addressData": "0995499608947a5281e2c7ebd71bdb26a1ad981946dad57f6c4d3ee35de77835" - }, - "out": { - "address": "purple1px25n9sgj3a99q0zcl4awx7my6s6mxqegmdd2lmvf5lwxh080q6suttktr" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "61", - "msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}" - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc000000000000000161000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d", - "addressData": "83326e554723b15bac664ceabc8a5887e27003abe9fbd992af8c7bcea4745167" - }, - "out": { - "address": "purple1svexu428ywc4htrxfn4tezjcsl38qqata8aany4033auafr529ns4v254c" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": null - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae0000000000000000", - "addressData": "9384c6248c0bb171e306fd7da0993ec1e20eba006452a3a9e078883eb3594564" - }, - "out": { - "address": "purple1jwzvvfyvpwchrccxl476pxf7c83qawsqv3f2820q0zyrav6eg4jqdcq7gc" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": "{}" - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae00000000000000027b7d", - "addressData": "9a8d5f98fb186825401a26206158e7a1213311a9b6a87944469913655af52ffb" - }, - "out": { - "address": "purple1n2x4lx8mrp5z2sq6ycsxzk885ysnxydfk658j3zxnyfk2kh49lasesxf6j" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}" - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d", - "addressData": "932f07bc53f7d0b0b43cb5a54ac3e245b205e6ae6f7c1d991dc6af4a2ff9ac18" - }, - "out": { - "address": "purple1jvhs00zn7lgtpdpukkj54slzgkeqte4wda7pmxgac6h55tle4svq8cmp60" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "61", - "msg": null - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000001610000000000000000", - "addressData": "9725e94f528d8b78d33c25f3dfcd60e6142d8be60ab36f6a5b59036fd51560db" - }, - "out": { - "address": "purple1juj7jn6j3k9h35euyhealntquc2zmzlxp2ek76jmtypkl4g4vrdsfwmwxk" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "61", - "msg": "{}" - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff00000000000000016100000000000000027b7d", - "addressData": "b056e539bbaf447ba18f3f13b792970111fc78933eb6700f4d227b5216d63658" - }, - "out": { - "address": "purple1kptw2wdm4az8hgv08ufm0y5hqyglc7yn86m8qr6dyfa4y9kkxevqmkm9q3" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "61", - "msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}" - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff000000000000000161000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d", - "addressData": "6c98434180f052294ff89fb6d2dae34f9f4468b0b8e6e7c002b2a44adee39abd" - }, - "out": { - "address": "purple1djvyxsvq7pfzjnlcn7md9khrf705g69shrnw0sqzk2jy4hhrn27sjh2ysy" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": null - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae0000000000000000", - "addressData": "0aaf1c31c5d529d21d898775bc35b3416f47bfd99188c334c6c716102cbd3101" - }, - "out": { - "address": "purple1p2h3cvw9655ay8vfsa6mcddng9h5007ejxyvxdxxcutpqt9axyqsagmmay" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": "{}" - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae00000000000000027b7d", - "addressData": "36fe6ab732187cdfed46290b448b32eb7f4798e7a4968b0537de8a842cbf030e" - }, - "out": { - "address": "purple1xmlx4dejrp7dlm2x9y95fzejadl50x885jtgkpfhm69ggt9lqv8qk3vn4f" - } - }, - { - "in": { - "checksum": "13a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a5", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}" - }, - "intermediate": { - "key": "7761736d00000000000000002013a1fc994cc6d1c81b746ee0c0ff6f90043875e0bf1d9be6b7d779fc978dc2a500000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d", - "addressData": "a0d0c942adac6f3e5e7c23116c4c42a24e96e0ab75f53690ec2d3de16067c751" - }, - "out": { - "address": "purple15rgvjs4d43hnuhnuyvgkcnzz5f8fdc9twh6ndy8v9577zcr8cags40l9dt" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "61", - "msg": null - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000001610000000000000000", - "addressData": "b95c467218d408a0f93046f227b6ece7fe18133ff30113db4d2a7becdfeca141" - }, - "out": { - "address": "purple1h9wyvusc6sy2p7fsgmez0dhvullpsyel7vq38k6d9fa7ehlv59qsvnyh36" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "61", - "msg": "{}" - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc00000000000000016100000000000000027b7d", - "addressData": "23fe45dbbd45dc6cd25244a74b6e99e7a65bf0bac2f2842a05049d37555a3ae6" - }, - "out": { - "address": "purple1y0lytkaaghwxe5jjgjn5km5eu7n9hu96ctegg2s9qjwnw4268tnqxhg60a" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "61", - "msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}" - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc000000000000000161000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d", - "addressData": "6faea261ed63baa65b05726269e83b217fa6205dc7d9fb74f9667d004a69c082" - }, - "out": { - "address": "purple1d7h2yc0dvwa2vkc9wf3xn6pmy9l6vgzaclvlka8eve7sqjnfczpqqsdnwu" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": null - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae0000000000000000", - "addressData": "67a3ab6384729925fdb144574628ce96836fe098d2c6be4e84ac106b2728d96c" - }, - "out": { - "address": "purple1v736kcuyw2vjtld3g3t5v2xwj6pklcyc6trtun5y4sgxkfegm9kq7vgpnt" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": "{}" - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae00000000000000027b7d", - "addressData": "23a121263bfce05c144f4af86f3d8a9f87dc56f9dc48dbcffc8c5a614da4c661" - }, - "out": { - "address": "purple1ywsjzf3mlns9c9z0ftux70v2n7rac4hem3ydhnlu33dxzndycesssc7x2m" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvhxf2py", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbcccccccccc", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}" - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000149999999999aaaaaaaaaabbbbbbbbbbcccccccccc0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d", - "addressData": "dd90dba6d6fcd5fb9c9c8f536314eb1bb29cb5aa084b633c5806b926a5636b58" - }, - "out": { - "address": "purple1mkgdhfkkln2lh8yu3afkx98trwefedd2pp9kx0zcq6ujdftrddvq50esay" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "61", - "msg": null - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000001610000000000000000", - "addressData": "547a743022f4f1af05b102f57bf1c1c7d7ee81bae427dc20d36b2c4ec57612ae" - }, - "out": { - "address": "purple123a8gvpz7nc67pd3qt6hhuwpclt7aqd6usnacgxndvkya3tkz2hq5hz38f" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "61", - "msg": "{}" - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff00000000000000016100000000000000027b7d", - "addressData": "416e169110e4b411bc53162d7503b2bbf14d6b36b1413a4f4c9af622696e9665" - }, - "out": { - "address": "purple1g9hpdygsuj6pr0znzckh2qajh0c566ekk9qn5n6vntmzy6twjejsrl9alk" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "61", - "msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}" - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff000000000000000161000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d", - "addressData": "619a0988b92d8796cea91dea63cbb1f1aefa4a6b6ee5c5d1e937007252697220" - }, - "out": { - "address": "purple1vxdqnz9e9kredn4frh4x8ja37xh05jntdmjut50fxuq8y5nfwgsquu9mxh" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": null - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae0000000000000000", - "addressData": "d8af856a6a04852d19b647ad6d4336eb26e077f740aef1a0331db34d299a885a" - }, - "out": { - "address": "purple1mzhc26n2qjzj6xdkg7kk6sekavnwqalhgzh0rgpnrke562v63pdq8grp8q" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": "{}" - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae00000000000000027b7d", - "addressData": "c7fb7bea96daab23e416c4fcf328215303005e1d0d5424257335568e5381e33c" - }, - "out": { - "address": "purple1clahh65km24j8eqkcn70x2pp2vpsqhsap42zgftnx4tgu5upuv7q9ywjws" - } - }, - { - "in": { - "checksum": "1da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b", - "creator": "purple1nxvenxve42424242hwamhwamenxvenxvmhwamhwaamhwamhwlllsatsy6m", - "creatorData": "9999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff", - "salt": "aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae", - "msg": "{\"some\":123,\"structure\":{\"nested\":[\"ok\",true]}}" - }, - "intermediate": { - "key": "7761736d0000000000000000201da6c16de2cbaf7ad8cbb66f0925ba33f5c278cb2491762d04658c1480ea229b00000000000000209999999999aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffff0000000000000040aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbccddeeffffeeddbbccddaa66551155aaaabbcc787878789900aabbbbcc221100acadae000000000000002f7b22736f6d65223a3132332c22737472756374757265223a7b226e6573746564223a5b226f6b222c747275655d7d7d", - "addressData": "ccdf9dea141a6c2475870529ab38fae9dec30df28e005894fe6578b66133ab4a" - }, - "out": { - "address": "purple1en0em6s5rfkzgav8q556kw86a80vxr0j3cq93987v4utvcfn4d9q0tql4w" - } - } -] -` diff --git a/x/wasm/keeper/ante.go b/x/wasm/keeper/ante.go deleted file mode 100644 index 27dd580..0000000 --- a/x/wasm/keeper/ante.go +++ /dev/null @@ -1,97 +0,0 @@ -package keeper - -import ( - "encoding/binary" - - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// CountTXDecorator ante handler to count the tx position in a block. -type CountTXDecorator struct { - storeKey storetypes.StoreKey -} - -// NewCountTXDecorator constructor -func NewCountTXDecorator(storeKey storetypes.StoreKey) *CountTXDecorator { - return &CountTXDecorator{storeKey: storeKey} -} - -// AnteHandle handler stores a tx counter with current height encoded in the store to let the app handle -// global rollback behavior instead of keeping state in the handler itself. -// The ante handler passes the counter value via sdk.Context upstream. See `types.TXCounter(ctx)` to read the value. -// Simulations don't get a tx counter value assigned. -func (a CountTXDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - if simulate { - return next(ctx, tx, simulate) - } - store := ctx.KVStore(a.storeKey) - currentHeight := ctx.BlockHeight() - - var txCounter uint32 // start with 0 - // load counter when exists - if bz := store.Get(types.TXCounterPrefix); bz != nil { - lastHeight, val := decodeHeightCounter(bz) - if currentHeight == lastHeight { - // then use stored counter - txCounter = val - } // else use `0` from above to start with - } - // store next counter value for current height - store.Set(types.TXCounterPrefix, encodeHeightCounter(currentHeight, txCounter+1)) - - return next(types.WithTXCounter(ctx, txCounter), tx, simulate) -} - -func encodeHeightCounter(height int64, counter uint32) []byte { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, counter) - return append(sdk.Uint64ToBigEndian(uint64(height)), b...) -} - -func decodeHeightCounter(bz []byte) (int64, uint32) { - return int64(sdk.BigEndianToUint64(bz[0:8])), binary.BigEndian.Uint32(bz[8:]) -} - -// LimitSimulationGasDecorator ante decorator to limit gas in simulation calls -type LimitSimulationGasDecorator struct { - gasLimit *sdk.Gas -} - -// NewLimitSimulationGasDecorator constructor accepts nil value to fallback to block gas limit. -func NewLimitSimulationGasDecorator(gasLimit *sdk.Gas) *LimitSimulationGasDecorator { - if gasLimit != nil && *gasLimit == 0 { - panic("gas limit must not be zero") - } - return &LimitSimulationGasDecorator{gasLimit: gasLimit} -} - -// AnteHandle that limits the maximum gas available in simulations only. -// A custom max value can be configured and will be applied when set. The value should not -// exceed the max block gas limit. -// Different values on nodes are not consensus breaking as they affect only -// simulations but may have effect on client user experience. -// -// When no custom value is set then the max block gas is used as default limit. -func (d LimitSimulationGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - if !simulate { - // Wasm code is not executed in checkTX so that we don't need to limit it further. - // Tendermint rejects the TX afterwards when the tx.gas > max block gas. - // On deliverTX we rely on the tendermint/sdk mechanics that ensure - // tx has gas set and gas < max block gas - return next(ctx, tx, simulate) - } - - // apply custom node gas limit - if d.gasLimit != nil { - return next(ctx.WithGasMeter(sdk.NewGasMeter(*d.gasLimit)), tx, simulate) - } - - // default to max block gas when set, to be on the safe side - if maxGas := ctx.ConsensusParams().GetBlock().MaxGas; maxGas > 0 { - return next(ctx.WithGasMeter(sdk.NewGasMeter(sdk.Gas(maxGas))), tx, simulate) - } - return next(ctx, tx, simulate) -} diff --git a/x/wasm/keeper/ante_test.go b/x/wasm/keeper/ante_test.go deleted file mode 100644 index 0fa3221..0000000 --- a/x/wasm/keeper/ante_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package keeper_test - -import ( - "testing" - "time" - - dbm "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestCountTxDecorator(t *testing.T) { - keyWasm := sdk.NewKVStoreKey(types.StoreKey) - db := dbm.NewMemDB() - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(keyWasm, storetypes.StoreTypeIAVL, db) - require.NoError(t, ms.LoadLatestVersion()) - const myCurrentBlockHeight = 100 - - specs := map[string]struct { - setupDB func(t *testing.T, ctx sdk.Context) - simulate bool - nextAssertAnte func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) - expErr bool - }{ - "no initial counter set": { - setupDB: func(t *testing.T, ctx sdk.Context) {}, - nextAssertAnte: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { - gotCounter, ok := types.TXCounter(ctx) - require.True(t, ok) - assert.Equal(t, uint32(0), gotCounter) - // and stored +1 - bz := ctx.MultiStore().GetKVStore(keyWasm).Get(types.TXCounterPrefix) - assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, myCurrentBlockHeight, 0, 0, 0, 1}, bz) - return ctx, nil - }, - }, - "persistent counter incremented - big endian": { - setupDB: func(t *testing.T, ctx sdk.Context) { - bz := []byte{0, 0, 0, 0, 0, 0, 0, myCurrentBlockHeight, 1, 0, 0, 2} - ctx.MultiStore().GetKVStore(keyWasm).Set(types.TXCounterPrefix, bz) - }, - nextAssertAnte: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { - gotCounter, ok := types.TXCounter(ctx) - require.True(t, ok) - assert.Equal(t, uint32(1<<24+2), gotCounter) - // and stored +1 - bz := ctx.MultiStore().GetKVStore(keyWasm).Get(types.TXCounterPrefix) - assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, myCurrentBlockHeight, 1, 0, 0, 3}, bz) - return ctx, nil - }, - }, - "old height counter replaced": { - setupDB: func(t *testing.T, ctx sdk.Context) { - previousHeight := byte(myCurrentBlockHeight - 1) - bz := []byte{0, 0, 0, 0, 0, 0, 0, previousHeight, 0, 0, 0, 1} - ctx.MultiStore().GetKVStore(keyWasm).Set(types.TXCounterPrefix, bz) - }, - nextAssertAnte: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { - gotCounter, ok := types.TXCounter(ctx) - require.True(t, ok) - assert.Equal(t, uint32(0), gotCounter) - // and stored +1 - bz := ctx.MultiStore().GetKVStore(keyWasm).Get(types.TXCounterPrefix) - assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, myCurrentBlockHeight, 0, 0, 0, 1}, bz) - return ctx, nil - }, - }, - "simulation not persisted": { - setupDB: func(t *testing.T, ctx sdk.Context) { - }, - simulate: true, - nextAssertAnte: func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { - _, ok := types.TXCounter(ctx) - assert.False(t, ok) - require.True(t, simulate) - // and not stored - assert.False(t, ctx.MultiStore().GetKVStore(keyWasm).Has(types.TXCounterPrefix)) - return ctx, nil - }, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - ctx := sdk.NewContext(ms.CacheMultiStore(), tmproto.Header{ - Height: myCurrentBlockHeight, - Time: time.Date(2021, time.September, 27, 12, 0, 0, 0, time.UTC), - }, false, log.NewNopLogger()) - - spec.setupDB(t, ctx) - var anyTx sdk.Tx - - // when - ante := keeper.NewCountTXDecorator(keyWasm) - _, gotErr := ante.AnteHandle(ctx, anyTx, spec.simulate, spec.nextAssertAnte) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - }) - } -} - -func TestLimitSimulationGasDecorator(t *testing.T) { - var ( - hundred sdk.Gas = 100 - zero sdk.Gas = 0 - ) - specs := map[string]struct { - customLimit *sdk.Gas - consumeGas sdk.Gas - maxBlockGas int64 - simulation bool - expErr interface{} - }{ - "custom limit set": { - customLimit: &hundred, - consumeGas: hundred + 1, - maxBlockGas: -1, - simulation: true, - expErr: sdk.ErrorOutOfGas{Descriptor: "testing"}, - }, - "block limit set": { - maxBlockGas: 100, - consumeGas: hundred + 1, - simulation: true, - expErr: sdk.ErrorOutOfGas{Descriptor: "testing"}, - }, - "no limits set": { - maxBlockGas: -1, - consumeGas: hundred + 1, - simulation: true, - }, - "both limits set, custom applies": { - customLimit: &hundred, - consumeGas: hundred - 1, - maxBlockGas: 10, - simulation: true, - }, - "not a simulation": { - customLimit: &hundred, - consumeGas: hundred + 1, - simulation: false, - }, - "zero custom limit": { - customLimit: &zero, - simulation: true, - expErr: "gas limit must not be zero", - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - nextAnte := consumeGasAnteHandler(spec.consumeGas) - ctx := sdk.Context{}. - WithGasMeter(sdk.NewInfiniteGasMeter()). - WithConsensusParams(&tmproto.ConsensusParams{ - Block: &tmproto.BlockParams{MaxGas: spec.maxBlockGas}, - }) - // when - if spec.expErr != nil { - require.PanicsWithValue(t, spec.expErr, func() { - ante := keeper.NewLimitSimulationGasDecorator(spec.customLimit) - _, err := ante.AnteHandle(ctx, nil, spec.simulation, nextAnte) - require.NoError(t, err) - }) - return - } - ante := keeper.NewLimitSimulationGasDecorator(spec.customLimit) - _, err := ante.AnteHandle(ctx, nil, spec.simulation, nextAnte) - require.NoError(t, err) - }) - } -} - -func consumeGasAnteHandler(gasToConsume sdk.Gas) sdk.AnteHandler { - return func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { - ctx.GasMeter().ConsumeGas(gasToConsume, "testing") - return ctx, nil - } -} diff --git a/x/wasm/keeper/api.go b/x/wasm/keeper/api.go deleted file mode 100644 index 88f3b82..0000000 --- a/x/wasm/keeper/api.go +++ /dev/null @@ -1,43 +0,0 @@ -package keeper - -import ( - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -const ( - // DefaultGasCostHumanAddress is how much SDK gas we charge to convert to a human address format - DefaultGasCostHumanAddress = 5 - // DefaultGasCostCanonicalAddress is how much SDK gas we charge to convert to a canonical address format - DefaultGasCostCanonicalAddress = 4 - - // DefaultDeserializationCostPerByte The formula should be `len(data) * deserializationCostPerByte` - DefaultDeserializationCostPerByte = 1 -) - -var ( - costHumanize = DefaultGasCostHumanAddress * DefaultGasMultiplier - costCanonical = DefaultGasCostCanonicalAddress * DefaultGasMultiplier - costJSONDeserialization = wasmvmtypes.UFraction{ - Numerator: DefaultDeserializationCostPerByte * DefaultGasMultiplier, - Denominator: 1, - } -) - -func humanAddress(canon []byte) (string, uint64, error) { - if err := sdk.VerifyAddressFormat(canon); err != nil { - return "", costHumanize, err - } - return sdk.AccAddress(canon).String(), costHumanize, nil -} - -func canonicalAddress(human string) ([]byte, uint64, error) { - bz, err := sdk.AccAddressFromBech32(human) - return bz, costCanonical, err -} - -var cosmwasmAPI = wasmvm.GoAPI{ - HumanAddress: humanAddress, - CanonicalAddress: canonicalAddress, -} diff --git a/x/wasm/keeper/authz_policy.go b/x/wasm/keeper/authz_policy.go deleted file mode 100644 index 36308a7..0000000 --- a/x/wasm/keeper/authz_policy.go +++ /dev/null @@ -1,63 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// ChainAccessConfigs chain settings -type ChainAccessConfigs struct { - Upload types.AccessConfig - Instantiate types.AccessConfig -} - -// NewChainAccessConfigs constructor -func NewChainAccessConfigs(upload types.AccessConfig, instantiate types.AccessConfig) ChainAccessConfigs { - return ChainAccessConfigs{Upload: upload, Instantiate: instantiate} -} - -type AuthorizationPolicy interface { - CanCreateCode(chainConfigs ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool - CanInstantiateContract(c types.AccessConfig, actor sdk.AccAddress) bool - CanModifyContract(admin, actor sdk.AccAddress) bool - CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool -} - -type DefaultAuthorizationPolicy struct{} - -func (p DefaultAuthorizationPolicy) CanCreateCode(chainConfigs ChainAccessConfigs, actor sdk.AccAddress, contractConfig types.AccessConfig) bool { - return chainConfigs.Upload.Allowed(actor) && - contractConfig.IsSubset(chainConfigs.Instantiate) -} - -func (p DefaultAuthorizationPolicy) CanInstantiateContract(config types.AccessConfig, actor sdk.AccAddress) bool { - return config.Allowed(actor) -} - -func (p DefaultAuthorizationPolicy) CanModifyContract(admin, actor sdk.AccAddress) bool { - return admin != nil && admin.Equals(actor) -} - -func (p DefaultAuthorizationPolicy) CanModifyCodeAccessConfig(creator, actor sdk.AccAddress, isSubset bool) bool { - return creator != nil && creator.Equals(actor) && isSubset -} - -type GovAuthorizationPolicy struct{} - -// CanCreateCode implements AuthorizationPolicy.CanCreateCode to allow gov actions. Always returns true. -func (p GovAuthorizationPolicy) CanCreateCode(ChainAccessConfigs, sdk.AccAddress, types.AccessConfig) bool { - return true -} - -func (p GovAuthorizationPolicy) CanInstantiateContract(types.AccessConfig, sdk.AccAddress) bool { - return true -} - -func (p GovAuthorizationPolicy) CanModifyContract(sdk.AccAddress, sdk.AccAddress) bool { - return true -} - -func (p GovAuthorizationPolicy) CanModifyCodeAccessConfig(sdk.AccAddress, sdk.AccAddress, bool) bool { - return true -} diff --git a/x/wasm/keeper/authz_policy_test.go b/x/wasm/keeper/authz_policy_test.go deleted file mode 100644 index 0e9c725..0000000 --- a/x/wasm/keeper/authz_policy_test.go +++ /dev/null @@ -1,345 +0,0 @@ -package keeper - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestDefaultAuthzPolicyCanCreateCode(t *testing.T) { - myActorAddress := RandomAccountAddress(t) - otherAddress := RandomAccountAddress(t) - specs := map[string]struct { - chainConfigs ChainAccessConfigs - contractInstConf types.AccessConfig - actor sdk.AccAddress - exp bool - panics bool - }{ - "upload nobody": { - chainConfigs: NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody), - contractInstConf: types.AllowEverybody, - exp: false, - }, - "upload everybody": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), - contractInstConf: types.AllowEverybody, - exp: true, - }, - "upload only address - same": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeOnlyAddress.With(myActorAddress), types.AllowEverybody), - contractInstConf: types.AllowEverybody, - exp: true, - }, - "upload only address - different": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeOnlyAddress.With(otherAddress), types.AllowEverybody), - contractInstConf: types.AllowEverybody, - exp: false, - }, - "upload any address - included": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody), - contractInstConf: types.AllowEverybody, - exp: true, - }, - "upload any address - not included": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody), - contractInstConf: types.AllowEverybody, - exp: false, - }, - "contract config - subtype": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), - contractInstConf: types.AccessTypeAnyOfAddresses.With(myActorAddress), - exp: true, - }, - "contract config - not subtype": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody), - contractInstConf: types.AllowEverybody, - exp: false, - }, - "upload undefined config - panics": { - chainConfigs: NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody), - contractInstConf: types.AllowEverybody, - panics: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - policy := DefaultAuthorizationPolicy{} - if !spec.panics { - got := policy.CanCreateCode(spec.chainConfigs, myActorAddress, spec.contractInstConf) - assert.Equal(t, spec.exp, got) - return - } - assert.Panics(t, func() { - policy.CanCreateCode(spec.chainConfigs, myActorAddress, spec.contractInstConf) - }) - }) - } -} - -func TestDefaultAuthzPolicyCanInstantiateContract(t *testing.T) { - myActorAddress := RandomAccountAddress(t) - otherAddress := RandomAccountAddress(t) - specs := map[string]struct { - config types.AccessConfig - actor sdk.AccAddress - exp bool - panics bool - }{ - "nobody": { - config: types.AllowNobody, - exp: false, - }, - "everybody": { - config: types.AllowEverybody, - exp: true, - }, - "only address - same": { - config: types.AccessTypeOnlyAddress.With(myActorAddress), - exp: true, - }, - "only address - different": { - config: types.AccessTypeOnlyAddress.With(otherAddress), - exp: false, - }, - "any address - included": { - config: types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), - exp: true, - }, - "any address - not included": { - config: types.AccessTypeAnyOfAddresses.With(otherAddress), - exp: false, - }, - "undefined config - panics": { - config: types.AccessConfig{}, - panics: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - policy := DefaultAuthorizationPolicy{} - if !spec.panics { - got := policy.CanInstantiateContract(spec.config, myActorAddress) - assert.Equal(t, spec.exp, got) - return - } - assert.Panics(t, func() { - policy.CanInstantiateContract(spec.config, myActorAddress) - }) - }) - } -} - -func TestDefaultAuthzPolicyCanModifyContract(t *testing.T) { - myActorAddress := RandomAccountAddress(t) - otherAddress := RandomAccountAddress(t) - - specs := map[string]struct { - admin sdk.AccAddress - exp bool - }{ - "same as actor": { - admin: myActorAddress, - exp: true, - }, - "different admin": { - admin: otherAddress, - exp: false, - }, - "no admin": { - exp: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - policy := DefaultAuthorizationPolicy{} - got := policy.CanModifyContract(spec.admin, myActorAddress) - assert.Equal(t, spec.exp, got) - }) - } -} - -func TestDefaultAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) { - myActorAddress := RandomAccountAddress(t) - otherAddress := RandomAccountAddress(t) - - specs := map[string]struct { - admin sdk.AccAddress - subset bool - exp bool - }{ - "same as actor - subset": { - admin: myActorAddress, - subset: true, - exp: true, - }, - "same as actor - not subset": { - admin: myActorAddress, - subset: false, - exp: false, - }, - "different admin": { - admin: otherAddress, - exp: false, - }, - "no admin": { - exp: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - policy := DefaultAuthorizationPolicy{} - got := policy.CanModifyCodeAccessConfig(spec.admin, myActorAddress, spec.subset) - assert.Equal(t, spec.exp, got) - }) - } -} - -func TestGovAuthzPolicyCanCreateCode(t *testing.T) { - myActorAddress := RandomAccountAddress(t) - otherAddress := RandomAccountAddress(t) - specs := map[string]struct { - chainConfigs ChainAccessConfigs - contractInstConf types.AccessConfig - actor sdk.AccAddress - }{ - "upload nobody": { - chainConfigs: NewChainAccessConfigs(types.AllowNobody, types.AllowEverybody), - contractInstConf: types.AllowEverybody, - }, - "upload everybody": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), - contractInstConf: types.AllowEverybody, - }, - "upload only address - same": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeOnlyAddress.With(myActorAddress), types.AllowEverybody), - contractInstConf: types.AllowEverybody, - }, - "upload only address - different": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeOnlyAddress.With(otherAddress), types.AllowEverybody), - contractInstConf: types.AllowEverybody, - }, - "upload any address - included": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), types.AllowEverybody), - contractInstConf: types.AllowEverybody, - }, - "upload any address - not included": { - chainConfigs: NewChainAccessConfigs(types.AccessTypeAnyOfAddresses.With(otherAddress), types.AllowEverybody), - contractInstConf: types.AllowEverybody, - }, - "contract config - subtype": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowEverybody), - contractInstConf: types.AccessTypeAnyOfAddresses.With(myActorAddress), - }, - "contract config - not subtype": { - chainConfigs: NewChainAccessConfigs(types.AllowEverybody, types.AllowNobody), - contractInstConf: types.AllowEverybody, - }, - "upload undefined config - not panics": { - chainConfigs: NewChainAccessConfigs(types.AccessConfig{}, types.AllowEverybody), - contractInstConf: types.AllowEverybody, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - policy := GovAuthorizationPolicy{} - got := policy.CanCreateCode(spec.chainConfigs, myActorAddress, spec.contractInstConf) - assert.True(t, got) - }) - } -} - -func TestGovAuthzPolicyCanInstantiateContract(t *testing.T) { - myActorAddress := RandomAccountAddress(t) - otherAddress := RandomAccountAddress(t) - specs := map[string]struct { - config types.AccessConfig - actor sdk.AccAddress - }{ - "nobody": { - config: types.AllowNobody, - }, - "everybody": { - config: types.AllowEverybody, - }, - "only address - same": { - config: types.AccessTypeOnlyAddress.With(myActorAddress), - }, - "only address - different": { - config: types.AccessTypeOnlyAddress.With(otherAddress), - }, - "any address - included": { - config: types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress), - }, - "any address - not included": { - config: types.AccessTypeAnyOfAddresses.With(otherAddress), - }, - "undefined config - panics": { - config: types.AccessConfig{}, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - policy := GovAuthorizationPolicy{} - got := policy.CanInstantiateContract(spec.config, myActorAddress) - assert.True(t, got) - }) - } -} - -func TestGovAuthzPolicyCanModifyContract(t *testing.T) { - myActorAddress := RandomAccountAddress(t) - otherAddress := RandomAccountAddress(t) - - specs := map[string]struct { - admin sdk.AccAddress - }{ - "same as actor": { - admin: myActorAddress, - }, - "different admin": { - admin: otherAddress, - }, - "no admin": {}, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - policy := GovAuthorizationPolicy{} - got := policy.CanModifyContract(spec.admin, myActorAddress) - assert.True(t, got) - }) - } -} - -func TestGovAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) { - myActorAddress := RandomAccountAddress(t) - otherAddress := RandomAccountAddress(t) - - specs := map[string]struct { - admin sdk.AccAddress - subset bool - }{ - "same as actor - subset": { - admin: myActorAddress, - subset: true, - }, - "same as actor - not subset": { - admin: myActorAddress, - subset: false, - }, - "different admin": { - admin: otherAddress, - }, - "no admin": {}, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - policy := GovAuthorizationPolicy{} - got := policy.CanModifyCodeAccessConfig(spec.admin, myActorAddress, spec.subset) - assert.True(t, got) - }) - } -} diff --git a/x/wasm/keeper/bench_test.go b/x/wasm/keeper/bench_test.go deleted file mode 100644 index 2780665..0000000 --- a/x/wasm/keeper/bench_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package keeper - -import ( - "os" - "testing" - - dbm "github.com/cometbft/cometbft-db" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// BenchmarkVerification benchmarks secp256k1 verification which is 1000 gas based on cpu time. -// -// Just this function is copied from -// https://github.com/cosmos/cosmos-sdk/blob/90e9370bd80d9a3d41f7203ddb71166865561569/crypto/keys/internal/benchmarking/bench.go#L48-L62 -// And thus under the GO license (BSD style) -func BenchmarkGasNormalization(b *testing.B) { - priv := secp256k1.GenPrivKey() - pub := priv.PubKey() - - // use a short message, so this time doesn't get dominated by hashing. - message := []byte("Hello, world!") - signature, err := priv.Sign(message) - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - pub.VerifySignature(message, signature) - } -} - -// By comparing the timing for queries on pinned vs unpinned, the difference gives us the overhead of -// instantiating an unpinned contract. That value can be used to determine a reasonable gas price -// for the InstantiationCost -func BenchmarkInstantiationOverhead(b *testing.B) { - specs := map[string]struct { - pinned bool - db func() dbm.DB - }{ - "unpinned, memory db": { - db: func() dbm.DB { return dbm.NewMemDB() }, - }, - "pinned, memory db": { - db: func() dbm.DB { return dbm.NewMemDB() }, - pinned: true, - }, - } - for name, spec := range specs { - b.Run(name, func(b *testing.B) { - wasmConfig := types.WasmConfig{MemoryCacheSize: 0} - ctx, keepers := createTestInput(b, false, AvailableCapabilities, wasmConfig, spec.db()) - example := InstantiateHackatomExampleContract(b, ctx, keepers) - if spec.pinned { - require.NoError(b, keepers.ContractKeeper.PinCode(ctx, example.CodeID)) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := keepers.WasmKeeper.QuerySmart(ctx, example.Contract, []byte(`{"verifier":{}}`)) - require.NoError(b, err) - } - }) - } -} - -// Calculate the time it takes to compile some wasm code the first time. -// This will help us adjust pricing for UploadCode -func BenchmarkCompilation(b *testing.B) { - specs := map[string]struct { - wasmFile string - }{ - "hackatom": { - wasmFile: "./testdata/hackatom.wasm", - }, - "burner": { - wasmFile: "./testdata/burner.wasm", - }, - "ibc_reflect": { - wasmFile: "./testdata/ibc_reflect.wasm", - }, - } - - for name, spec := range specs { - b.Run(name, func(b *testing.B) { - wasmConfig := types.WasmConfig{MemoryCacheSize: 0} - db := dbm.NewMemDB() - ctx, keepers := createTestInput(b, false, AvailableCapabilities, wasmConfig, db) - - // print out code size for comparisons - code, err := os.ReadFile(spec.wasmFile) - require.NoError(b, err) - b.Logf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b(size: %d) ", len(code)) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = StoreExampleContract(b, ctx, keepers, spec.wasmFile) - } - }) - } -} diff --git a/x/wasm/keeper/contract_keeper.go b/x/wasm/keeper/contract_keeper.go deleted file mode 100644 index 2a74039..0000000 --- a/x/wasm/keeper/contract_keeper.go +++ /dev/null @@ -1,130 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var _ types.ContractOpsKeeper = PermissionedKeeper{} - -// decoratedKeeper contains a subset of the wasm keeper that are already or can be guarded by an authorization policy in the future -type decoratedKeeper interface { - create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, checksum []byte, err error) - - instantiate( - ctx sdk.Context, - codeID uint64, - creator, admin sdk.AccAddress, - initMsg []byte, - label string, - deposit sdk.Coins, - addressGenerator AddressGenerator, - authZ AuthorizationPolicy, - ) (sdk.AccAddress, []byte, error) - - migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) ([]byte, error) - setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ AuthorizationPolicy) error - pinCode(ctx sdk.Context, codeID uint64) error - unpinCode(ctx sdk.Context, codeID uint64) error - execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) - Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) - setContractInfoExtension(ctx sdk.Context, contract sdk.AccAddress, extra types.ContractInfoExtension) error - setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, autz AuthorizationPolicy) error - ClassicAddressGenerator() AddressGenerator -} - -type PermissionedKeeper struct { - authZPolicy AuthorizationPolicy - nested decoratedKeeper -} - -func NewPermissionedKeeper(nested decoratedKeeper, authZPolicy AuthorizationPolicy) *PermissionedKeeper { - return &PermissionedKeeper{authZPolicy: authZPolicy, nested: nested} -} - -func NewGovPermissionKeeper(nested decoratedKeeper) *PermissionedKeeper { - return NewPermissionedKeeper(nested, GovAuthorizationPolicy{}) -} - -func NewDefaultPermissionKeeper(nested decoratedKeeper) *PermissionedKeeper { - return NewPermissionedKeeper(nested, DefaultAuthorizationPolicy{}) -} - -func (p PermissionedKeeper) Create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig) (codeID uint64, checksum []byte, err error) { - return p.nested.create(ctx, creator, wasmCode, instantiateAccess, p.authZPolicy) -} - -// Instantiate creates an instance of a WASM contract using the classic sequence based address generator -func (p PermissionedKeeper) Instantiate( - ctx sdk.Context, - codeID uint64, - creator, admin sdk.AccAddress, - initMsg []byte, - label string, - deposit sdk.Coins, -) (sdk.AccAddress, []byte, error) { - return p.nested.instantiate(ctx, codeID, creator, admin, initMsg, label, deposit, p.nested.ClassicAddressGenerator(), p.authZPolicy) -} - -// Instantiate2 creates an instance of a WASM contract using the predictable address generator -func (p PermissionedKeeper) Instantiate2( - ctx sdk.Context, - codeID uint64, - creator, admin sdk.AccAddress, - initMsg []byte, - label string, - deposit sdk.Coins, - salt []byte, - fixMsg bool, -) (sdk.AccAddress, []byte, error) { - return p.nested.instantiate( - ctx, - codeID, - creator, - admin, - initMsg, - label, - deposit, - PredicableAddressGenerator(creator, salt, initMsg, fixMsg), - p.authZPolicy, - ) -} - -func (p PermissionedKeeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) { - return p.nested.execute(ctx, contractAddress, caller, msg, coins) -} - -func (p PermissionedKeeper) Migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte) ([]byte, error) { - return p.nested.migrate(ctx, contractAddress, caller, newCodeID, msg, p.authZPolicy) -} - -func (p PermissionedKeeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) { - return p.nested.Sudo(ctx, contractAddress, msg) -} - -func (p PermissionedKeeper) UpdateContractAdmin(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newAdmin sdk.AccAddress) error { - return p.nested.setContractAdmin(ctx, contractAddress, caller, newAdmin, p.authZPolicy) -} - -func (p PermissionedKeeper) ClearContractAdmin(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress) error { - return p.nested.setContractAdmin(ctx, contractAddress, caller, nil, p.authZPolicy) -} - -func (p PermissionedKeeper) PinCode(ctx sdk.Context, codeID uint64) error { - return p.nested.pinCode(ctx, codeID) -} - -func (p PermissionedKeeper) UnpinCode(ctx sdk.Context, codeID uint64) error { - return p.nested.unpinCode(ctx, codeID) -} - -// SetContractInfoExtension updates the extra attributes that can be stored with the contract info -func (p PermissionedKeeper) SetContractInfoExtension(ctx sdk.Context, contract sdk.AccAddress, extra types.ContractInfoExtension) error { - return p.nested.setContractInfoExtension(ctx, contract, extra) -} - -// SetAccessConfig updates the access config of a code id. -func (p PermissionedKeeper) SetAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig) error { - return p.nested.setAccessConfig(ctx, codeID, caller, newConfig, p.authZPolicy) -} diff --git a/x/wasm/keeper/contract_keeper_test.go b/x/wasm/keeper/contract_keeper_test.go deleted file mode 100644 index aa660d1..0000000 --- a/x/wasm/keeper/contract_keeper_test.go +++ /dev/null @@ -1,169 +0,0 @@ -package keeper - -import ( - "encoding/json" - "fmt" - "math" - "strings" - "testing" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestInstantiate2(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - parentCtx = parentCtx.WithGasMeter(sdk.NewInfiniteGasMeter()) - - example := StoreHackatomExampleContract(t, parentCtx, keepers) - otherExample := StoreReflectContract(t, parentCtx, keepers) - mock := &wasmtesting.MockWasmer{} - wasmtesting.MakeInstantiable(mock) - keepers.WasmKeeper.wasmVM = mock // set mock to not fail on contract init message - - verifierAddr := RandomAccountAddress(t) - beneficiaryAddr := RandomAccountAddress(t) - initMsg := mustMarshal(t, HackatomExampleInitMsg{Verifier: verifierAddr, Beneficiary: beneficiaryAddr}) - otherAddr := keepers.Faucet.NewFundedRandomAccount(parentCtx, sdk.NewInt64Coin("denom", 1_000_000_000)) - - const ( - mySalt = "my salt" - myLabel = "my label" - ) - // create instances for duplicate checks - exampleContract := func(t *testing.T, ctx sdk.Context, fixMsg bool) { - _, _, err := keepers.ContractKeeper.Instantiate2( - ctx, - example.CodeID, - example.CreatorAddr, - nil, - initMsg, - myLabel, - sdk.NewCoins(sdk.NewInt64Coin("denom", 1)), - []byte(mySalt), - fixMsg, - ) - require.NoError(t, err) - } - exampleWithFixMsg := func(t *testing.T, ctx sdk.Context) { - exampleContract(t, ctx, true) - } - exampleWithoutFixMsg := func(t *testing.T, ctx sdk.Context) { - exampleContract(t, ctx, false) - } - specs := map[string]struct { - setup func(t *testing.T, ctx sdk.Context) - codeID uint64 - sender sdk.AccAddress - salt []byte - initMsg json.RawMessage - fixMsg bool - expErr error - }{ - "fix msg - generates different address than without fixMsg": { - setup: exampleWithoutFixMsg, - codeID: example.CodeID, - sender: example.CreatorAddr, - salt: []byte(mySalt), - initMsg: initMsg, - fixMsg: true, - }, - "fix msg - different sender": { - setup: exampleWithFixMsg, - codeID: example.CodeID, - sender: otherAddr, - salt: []byte(mySalt), - initMsg: initMsg, - fixMsg: true, - }, - "fix msg - different code": { - setup: exampleWithFixMsg, - codeID: otherExample.CodeID, - sender: example.CreatorAddr, - salt: []byte(mySalt), - initMsg: []byte(`{}`), - fixMsg: true, - }, - "fix msg - different salt": { - setup: exampleWithFixMsg, - codeID: example.CodeID, - sender: example.CreatorAddr, - salt: []byte("other salt"), - initMsg: initMsg, - fixMsg: true, - }, - "fix msg - different init msg": { - setup: exampleWithFixMsg, - codeID: example.CodeID, - sender: example.CreatorAddr, - salt: []byte(mySalt), - initMsg: mustMarshal(t, HackatomExampleInitMsg{Verifier: otherAddr, Beneficiary: beneficiaryAddr}), - fixMsg: true, - }, - "different sender": { - setup: exampleWithoutFixMsg, - codeID: example.CodeID, - sender: otherAddr, - salt: []byte(mySalt), - initMsg: initMsg, - }, - "different code": { - setup: exampleWithoutFixMsg, - codeID: otherExample.CodeID, - sender: example.CreatorAddr, - salt: []byte(mySalt), - initMsg: []byte(`{}`), - }, - "different salt": { - setup: exampleWithoutFixMsg, - codeID: example.CodeID, - sender: example.CreatorAddr, - salt: []byte(`other salt`), - initMsg: initMsg, - }, - "different init msg - reject same address": { - setup: exampleWithoutFixMsg, - codeID: example.CodeID, - sender: example.CreatorAddr, - salt: []byte(mySalt), - initMsg: mustMarshal(t, HackatomExampleInitMsg{Verifier: otherAddr, Beneficiary: beneficiaryAddr}), - expErr: types.ErrDuplicate, - }, - "fix msg - long msg": { - setup: exampleWithFixMsg, - codeID: example.CodeID, - sender: otherAddr, - salt: []byte(mySalt), - initMsg: []byte(fmt.Sprintf(`{"foo":%q}`, strings.Repeat("b", math.MaxInt16+1))), // too long kills CI - fixMsg: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - spec.setup(t, ctx) - gotAddr, _, gotErr := keepers.ContractKeeper.Instantiate2( - ctx, - spec.codeID, - spec.sender, - nil, - spec.initMsg, - myLabel, - sdk.NewCoins(sdk.NewInt64Coin("denom", 2)), - spec.salt, - spec.fixMsg, - ) - if spec.expErr != nil { - assert.ErrorIs(t, gotErr, spec.expErr) - return - } - require.NoError(t, gotErr) - assert.NotEmpty(t, gotAddr) - }) - } -} diff --git a/x/wasm/keeper/events.go b/x/wasm/keeper/events.go deleted file mode 100644 index 47068aa..0000000 --- a/x/wasm/keeper/events.go +++ /dev/null @@ -1,68 +0,0 @@ -package keeper - -import ( - "fmt" - "strings" - - errorsmod "cosmossdk.io/errors" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// newWasmModuleEvent creates with wasm module event for interacting with the given contract. Adds custom attributes -// to this event. -func newWasmModuleEvent(customAttributes []wasmvmtypes.EventAttribute, contractAddr sdk.AccAddress) (sdk.Events, error) { - attrs, err := contractSDKEventAttributes(customAttributes, contractAddr) - if err != nil { - return nil, err - } - - // each wasm invocation always returns one sdk.Event - return sdk.Events{sdk.NewEvent(types.WasmModuleEventType, attrs...)}, nil -} - -const eventTypeMinLength = 2 - -// newCustomEvents converts wasmvm events from a contract response to sdk type events -func newCustomEvents(evts wasmvmtypes.Events, contractAddr sdk.AccAddress) (sdk.Events, error) { - events := make(sdk.Events, 0, len(evts)) - for _, e := range evts { - typ := strings.TrimSpace(e.Type) - if len(typ) <= eventTypeMinLength { - return nil, errorsmod.Wrap(types.ErrInvalidEvent, fmt.Sprintf("Event type too short: '%s'", typ)) - } - attributes, err := contractSDKEventAttributes(e.Attributes, contractAddr) - if err != nil { - return nil, err - } - events = append(events, sdk.NewEvent(fmt.Sprintf("%s%s", types.CustomContractEventPrefix, typ), attributes...)) - } - return events, nil -} - -// convert and add contract address issuing this event -func contractSDKEventAttributes(customAttributes []wasmvmtypes.EventAttribute, contractAddr sdk.AccAddress) ([]sdk.Attribute, error) { - attrs := []sdk.Attribute{sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddr.String())} - // append attributes from wasm to the sdk.Event - for _, l := range customAttributes { - // ensure key and value are non-empty (and trim what is there) - key := strings.TrimSpace(l.Key) - if len(key) == 0 { - return nil, errorsmod.Wrap(types.ErrInvalidEvent, fmt.Sprintf("Empty attribute key. Value: %s", l.Value)) - } - value := strings.TrimSpace(l.Value) - // TODO: check if this is legal in the SDK - if it is, we can remove this check - if len(value) == 0 { - return nil, errorsmod.Wrap(types.ErrInvalidEvent, fmt.Sprintf("Empty attribute value. Key: %s", key)) - } - // and reserve all _* keys for our use (not contract) - if strings.HasPrefix(key, types.AttributeReservedPrefix) { - return nil, errorsmod.Wrap(types.ErrInvalidEvent, fmt.Sprintf("Attribute key starts with reserved prefix %s: '%s'", types.AttributeReservedPrefix, key)) - } - attrs = append(attrs, sdk.NewAttribute(key, value)) - } - return attrs, nil -} diff --git a/x/wasm/keeper/events_test.go b/x/wasm/keeper/events_test.go deleted file mode 100644 index 5320b28..0000000 --- a/x/wasm/keeper/events_test.go +++ /dev/null @@ -1,290 +0,0 @@ -package keeper - -import ( - "context" - "testing" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestHasWasmModuleEvent(t *testing.T) { - myContractAddr := RandomAccountAddress(t) - specs := map[string]struct { - srcEvents []sdk.Event - exp bool - }{ - "event found": { - srcEvents: []sdk.Event{ - sdk.NewEvent(types.WasmModuleEventType, sdk.NewAttribute("_contract_address", myContractAddr.String())), - }, - exp: true, - }, - "different event: not found": { - srcEvents: []sdk.Event{ - sdk.NewEvent(types.CustomContractEventPrefix, sdk.NewAttribute("_contract_address", myContractAddr.String())), - }, - exp: false, - }, - "event with different address: not found": { - srcEvents: []sdk.Event{ - sdk.NewEvent(types.WasmModuleEventType, sdk.NewAttribute("_contract_address", RandomBech32AccountAddress(t))), - }, - exp: false, - }, - "no event": { - srcEvents: []sdk.Event{}, - exp: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - em := sdk.NewEventManager() - em.EmitEvents(spec.srcEvents) - ctx := sdk.Context{}.WithContext(context.Background()).WithEventManager(em) - - got := hasWasmModuleEvent(ctx, myContractAddr) - assert.Equal(t, spec.exp, got) - }) - } -} - -func TestNewCustomEvents(t *testing.T) { - myContract := RandomAccountAddress(t) - specs := map[string]struct { - src wasmvmtypes.Events - exp sdk.Events - isError bool - }{ - "all good": { - src: wasmvmtypes.Events{{ - Type: "foo", - Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myVal"}}, - }}, - exp: sdk.Events{sdk.NewEvent("wasm-foo", - sdk.NewAttribute("_contract_address", myContract.String()), - sdk.NewAttribute("myKey", "myVal"))}, - }, - "multiple attributes": { - src: wasmvmtypes.Events{{ - Type: "foo", - Attributes: []wasmvmtypes.EventAttribute{ - {Key: "myKey", Value: "myVal"}, - {Key: "myOtherKey", Value: "myOtherVal"}, - }, - }}, - exp: sdk.Events{sdk.NewEvent("wasm-foo", - sdk.NewAttribute("_contract_address", myContract.String()), - sdk.NewAttribute("myKey", "myVal"), - sdk.NewAttribute("myOtherKey", "myOtherVal"))}, - }, - "multiple events": { - src: wasmvmtypes.Events{{ - Type: "foo", - Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myVal"}}, - }, { - Type: "bar", - Attributes: []wasmvmtypes.EventAttribute{{Key: "otherKey", Value: "otherVal"}}, - }}, - exp: sdk.Events{ - sdk.NewEvent("wasm-foo", - sdk.NewAttribute("_contract_address", myContract.String()), - sdk.NewAttribute("myKey", "myVal")), - sdk.NewEvent("wasm-bar", - sdk.NewAttribute("_contract_address", myContract.String()), - sdk.NewAttribute("otherKey", "otherVal")), - }, - }, - "without attributes": { - src: wasmvmtypes.Events{{ - Type: "foo", - }}, - exp: sdk.Events{sdk.NewEvent("wasm-foo", - sdk.NewAttribute("_contract_address", myContract.String()))}, - }, - "error on short event type": { - src: wasmvmtypes.Events{{ - Type: "f", - }}, - isError: true, - }, - "error on _contract_address": { - src: wasmvmtypes.Events{{ - Type: "foo", - Attributes: []wasmvmtypes.EventAttribute{{Key: "_contract_address", Value: RandomBech32AccountAddress(t)}}, - }}, - isError: true, - }, - "error on reserved prefix": { - src: wasmvmtypes.Events{{ - Type: "wasm", - Attributes: []wasmvmtypes.EventAttribute{ - {Key: "_reserved", Value: "is skipped"}, - {Key: "normal", Value: "is used"}, - }, - }}, - isError: true, - }, - "error on empty value": { - src: wasmvmtypes.Events{{ - Type: "boom", - Attributes: []wasmvmtypes.EventAttribute{ - {Key: "some", Value: "data"}, - {Key: "key", Value: ""}, - }, - }}, - isError: true, - }, - "error on empty key": { - src: wasmvmtypes.Events{{ - Type: "boom", - Attributes: []wasmvmtypes.EventAttribute{ - {Key: "some", Value: "data"}, - {Key: "", Value: "value"}, - }, - }}, - isError: true, - }, - "error on whitespace type": { - src: wasmvmtypes.Events{{ - Type: " f ", - Attributes: []wasmvmtypes.EventAttribute{ - {Key: "some", Value: "data"}, - }, - }}, - isError: true, - }, - "error on only whitespace key": { - src: wasmvmtypes.Events{{ - Type: "boom", - Attributes: []wasmvmtypes.EventAttribute{ - {Key: "some", Value: "data"}, - {Key: "\n\n\n\n", Value: "value"}, - }, - }}, - isError: true, - }, - "error on only whitespace value": { - src: wasmvmtypes.Events{{ - Type: "boom", - Attributes: []wasmvmtypes.EventAttribute{ - {Key: "some", Value: "data"}, - {Key: "myKey", Value: " \t\r\n"}, - }, - }}, - isError: true, - }, - "strip out whitespace": { - src: wasmvmtypes.Events{{ - Type: " food\n", - Attributes: []wasmvmtypes.EventAttribute{{Key: "my Key", Value: "\tmyVal"}}, - }}, - exp: sdk.Events{sdk.NewEvent("wasm-food", - sdk.NewAttribute("_contract_address", myContract.String()), - sdk.NewAttribute("my Key", "myVal"))}, - }, - "empty event elements": { - src: make(wasmvmtypes.Events, 10), - isError: true, - }, - "nil": { - exp: sdk.Events{}, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - gotEvent, err := newCustomEvents(spec.src, myContract) - if spec.isError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, spec.exp, gotEvent) - } - }) - } -} - -func TestNewWasmModuleEvent(t *testing.T) { - myContract := RandomAccountAddress(t) - specs := map[string]struct { - src []wasmvmtypes.EventAttribute - exp sdk.Events - isError bool - }{ - "all good": { - src: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myVal"}}, - exp: sdk.Events{sdk.NewEvent("wasm", - sdk.NewAttribute("_contract_address", myContract.String()), - sdk.NewAttribute("myKey", "myVal"))}, - }, - "multiple attributes": { - src: []wasmvmtypes.EventAttribute{ - {Key: "myKey", Value: "myVal"}, - {Key: "myOtherKey", Value: "myOtherVal"}, - }, - exp: sdk.Events{sdk.NewEvent("wasm", - sdk.NewAttribute("_contract_address", myContract.String()), - sdk.NewAttribute("myKey", "myVal"), - sdk.NewAttribute("myOtherKey", "myOtherVal"))}, - }, - "without attributes": { - exp: sdk.Events{sdk.NewEvent("wasm", - sdk.NewAttribute("_contract_address", myContract.String()))}, - }, - "error on _contract_address": { - src: []wasmvmtypes.EventAttribute{{Key: "_contract_address", Value: RandomBech32AccountAddress(t)}}, - isError: true, - }, - "error on whitespace key": { - src: []wasmvmtypes.EventAttribute{{Key: " ", Value: "value"}}, - isError: true, - }, - "error on whitespace value": { - src: []wasmvmtypes.EventAttribute{{Key: "key", Value: "\n\n\n"}}, - isError: true, - }, - "strip whitespace": { - src: []wasmvmtypes.EventAttribute{{Key: " my-real-key ", Value: "\n\n\nsome-val\t\t\t"}}, - exp: sdk.Events{sdk.NewEvent("wasm", - sdk.NewAttribute("_contract_address", myContract.String()), - sdk.NewAttribute("my-real-key", "some-val"))}, - }, - "empty elements": { - src: make([]wasmvmtypes.EventAttribute, 10), - isError: true, - }, - "nil": { - exp: sdk.Events{sdk.NewEvent("wasm", - sdk.NewAttribute("_contract_address", myContract.String()), - )}, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - gotEvent, err := newWasmModuleEvent(spec.src, myContract) - if spec.isError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - assert.Equal(t, spec.exp, gotEvent) - } - }) - } -} - -// returns true when a wasm module event was emitted for this contract already -func hasWasmModuleEvent(ctx sdk.Context, contractAddr sdk.AccAddress) bool { - for _, e := range ctx.EventManager().Events() { - if e.Type == types.WasmModuleEventType { - for _, a := range e.Attributes { - if a.Key == types.AttributeKeyContractAddr && a.Value == contractAddr.String() { - return true - } - } - } - } - return false -} diff --git a/x/wasm/keeper/gas_register.go b/x/wasm/keeper/gas_register.go deleted file mode 100644 index e26a19a..0000000 --- a/x/wasm/keeper/gas_register.go +++ /dev/null @@ -1,253 +0,0 @@ -package keeper - -import ( - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -const ( - // DefaultGasMultiplier is how many CosmWasm gas points = 1 Cosmos SDK gas point. - // - // CosmWasm gas strategy is documented in https://github.com/CosmWasm/cosmwasm/blob/v1.0.0-beta/docs/GAS.md. - // Cosmos SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.10/store/types/gas.go#L198-L209. - // - // The original multiplier of 100 up to CosmWasm 0.16 was based on - // "A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io - // Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read)" - // as well as manual Wasmer benchmarks from 2019. This was then multiplied by 150_000 - // in the 0.16 -> 1.0 upgrade (https://github.com/CosmWasm/cosmwasm/pull/1120). - // - // The multiplier deserves more reproducible benchmarking and a strategy that allows easy adjustments. - // This is tracked in https://github.com/terpnetwork/terp-core/issues/566 and https://github.com/terpnetwork/terp-core/issues/631. - // Gas adjustments are consensus breaking but may happen in any release marked as consensus breaking. - // Do not make assumptions on how much gas an operation will consume in places that are hard to adjust, - // such as hardcoding them in contracts. - // - // Please note that all gas prices returned to wasmvm should have this multiplied. - // Benchmarks and numbers were discussed in: https://github.com/terpnetwork/terp-core/pull/634#issuecomment-938055852 - DefaultGasMultiplier uint64 = 140_000_000 - // DefaultInstanceCost is how much SDK gas we charge each time we load a WASM instance. - // Creating a new instance is costly, and this helps put a recursion limit to contracts calling contracts. - // Benchmarks and numbers were discussed in: https://github.com/terpnetwork/terp-core/pull/634#issuecomment-938056803 - DefaultInstanceCost uint64 = 60_000 - // DefaultCompileCost is how much SDK gas is charged *per byte* for compiling WASM code. - // Benchmarks and numbers were discussed in: https://github.com/terpnetwork/terp-core/pull/634#issuecomment-938056803 - DefaultCompileCost uint64 = 3 - // DefaultEventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events. - // This is used with len(key) + len(value) - DefaultEventAttributeDataCost uint64 = 1 - // DefaultContractMessageDataCost is how much SDK gas is charged *per byte* of the message that goes to the contract - // This is used with len(msg). Note that the message is deserialized in the receiving contract and this is charged - // with wasm gas already. The derserialization of results is also charged in wasmvm. I am unsure if we need to add - // additional costs here. - // Note: also used for error fields on reply, and data on reply. Maybe these should be pulled out to a different (non-zero) field - DefaultContractMessageDataCost uint64 = 0 - // DefaultPerAttributeCost is how much SDK gas we charge per attribute count. - DefaultPerAttributeCost uint64 = 10 - // DefaultPerCustomEventCost is how much SDK gas we charge per event count. - DefaultPerCustomEventCost uint64 = 20 - // DefaultEventAttributeDataFreeTier number of bytes of total attribute data we do not charge. - DefaultEventAttributeDataFreeTier = 100 -) - -// default: 0.15 gas. -// see https://github.com/terpnetwork/terp-core/pull/898#discussion_r937727200 -var defaultPerByteUncompressCost = wasmvmtypes.UFraction{ - Numerator: 15, - Denominator: 100, -} - -// DefaultPerByteUncompressCost is how much SDK gas we charge per source byte to unpack -func DefaultPerByteUncompressCost() wasmvmtypes.UFraction { - return defaultPerByteUncompressCost -} - -// GasRegister abstract source for gas costs -type GasRegister interface { - // NewContractInstanceCosts costs to create a new contract instance from code - NewContractInstanceCosts(pinned bool, msgLen int) sdk.Gas - // CompileCosts costs to persist and "compile" a new wasm contract - CompileCosts(byteLength int) sdk.Gas - // UncompressCosts costs to unpack a new wasm contract - UncompressCosts(byteLength int) sdk.Gas - // InstantiateContractCosts costs when interacting with a wasm contract - InstantiateContractCosts(pinned bool, msgLen int) sdk.Gas - // ReplyCosts costs to to handle a message reply - ReplyCosts(pinned bool, reply wasmvmtypes.Reply) sdk.Gas - // EventCosts costs to persist an event - EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Events) sdk.Gas - // ToWasmVMGas converts from sdk gas to wasmvm gas - ToWasmVMGas(source sdk.Gas) uint64 - // FromWasmVMGas converts from wasmvm gas to sdk gas - FromWasmVMGas(source uint64) sdk.Gas -} - -// WasmGasRegisterConfig config type -type WasmGasRegisterConfig struct { - // InstanceCost costs when interacting with a wasm contract - InstanceCost sdk.Gas - // CompileCosts costs to persist and "compile" a new wasm contract - CompileCost sdk.Gas - // UncompressCost costs per byte to unpack a contract - UncompressCost wasmvmtypes.UFraction - // GasMultiplier is how many cosmwasm gas points = 1 sdk gas point - // SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164 - GasMultiplier sdk.Gas - // EventPerAttributeCost is how much SDK gas is charged *per byte* for attribute data in events. - // This is used with len(key) + len(value) - EventPerAttributeCost sdk.Gas - // EventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events. - // This is used with len(key) + len(value) - EventAttributeDataCost sdk.Gas - // EventAttributeDataFreeTier number of bytes of total attribute data that is free of charge - EventAttributeDataFreeTier uint64 - // ContractMessageDataCost SDK gas charged *per byte* of the message that goes to the contract - // This is used with len(msg) - ContractMessageDataCost sdk.Gas - // CustomEventCost cost per custom event - CustomEventCost uint64 -} - -// DefaultGasRegisterConfig default values -func DefaultGasRegisterConfig() WasmGasRegisterConfig { - return WasmGasRegisterConfig{ - InstanceCost: DefaultInstanceCost, - CompileCost: DefaultCompileCost, - GasMultiplier: DefaultGasMultiplier, - EventPerAttributeCost: DefaultPerAttributeCost, - CustomEventCost: DefaultPerCustomEventCost, - EventAttributeDataCost: DefaultEventAttributeDataCost, - EventAttributeDataFreeTier: DefaultEventAttributeDataFreeTier, - ContractMessageDataCost: DefaultContractMessageDataCost, - UncompressCost: DefaultPerByteUncompressCost(), - } -} - -// WasmGasRegister implements GasRegister interface -type WasmGasRegister struct { - c WasmGasRegisterConfig -} - -// NewDefaultWasmGasRegister creates instance with default values -func NewDefaultWasmGasRegister() WasmGasRegister { - return NewWasmGasRegister(DefaultGasRegisterConfig()) -} - -// NewWasmGasRegister constructor -func NewWasmGasRegister(c WasmGasRegisterConfig) WasmGasRegister { - if c.GasMultiplier == 0 { - panic(errorsmod.Wrap(sdkerrors.ErrLogic, "GasMultiplier can not be 0")) - } - return WasmGasRegister{ - c: c, - } -} - -// NewContractInstanceCosts costs to create a new contract instance from code -func (g WasmGasRegister) NewContractInstanceCosts(pinned bool, msgLen int) storetypes.Gas { - return g.InstantiateContractCosts(pinned, msgLen) -} - -// CompileCosts costs to persist and "compile" a new wasm contract -func (g WasmGasRegister) CompileCosts(byteLength int) storetypes.Gas { - if byteLength < 0 { - panic(errorsmod.Wrap(types.ErrInvalid, "negative length")) - } - return g.c.CompileCost * uint64(byteLength) -} - -// UncompressCosts costs to unpack a new wasm contract -func (g WasmGasRegister) UncompressCosts(byteLength int) sdk.Gas { - if byteLength < 0 { - panic(errorsmod.Wrap(types.ErrInvalid, "negative length")) - } - return g.c.UncompressCost.Mul(uint64(byteLength)).Floor() -} - -// InstantiateContractCosts costs when interacting with a wasm contract -func (g WasmGasRegister) InstantiateContractCosts(pinned bool, msgLen int) sdk.Gas { - if msgLen < 0 { - panic(errorsmod.Wrap(types.ErrInvalid, "negative length")) - } - dataCosts := sdk.Gas(msgLen) * g.c.ContractMessageDataCost - if pinned { - return dataCosts - } - return g.c.InstanceCost + dataCosts -} - -// ReplyCosts costs to to handle a message reply -func (g WasmGasRegister) ReplyCosts(pinned bool, reply wasmvmtypes.Reply) sdk.Gas { - var eventGas sdk.Gas - msgLen := len(reply.Result.Err) - if reply.Result.Ok != nil { - msgLen += len(reply.Result.Ok.Data) - var attrs []wasmvmtypes.EventAttribute - for _, e := range reply.Result.Ok.Events { - eventGas += sdk.Gas(len(e.Type)) * g.c.EventAttributeDataCost - attrs = append(attrs, e.Attributes...) - } - // apply free tier on the whole set not per event - eventGas += g.EventCosts(attrs, nil) - } - return eventGas + g.InstantiateContractCosts(pinned, msgLen) -} - -// EventCosts costs to persist an event -func (g WasmGasRegister) EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Events) sdk.Gas { - gas, remainingFreeTier := g.eventAttributeCosts(attrs, g.c.EventAttributeDataFreeTier) - for _, e := range events { - gas += g.c.CustomEventCost - gas += sdk.Gas(len(e.Type)) * g.c.EventAttributeDataCost // no free tier with event type - var attrCost sdk.Gas - attrCost, remainingFreeTier = g.eventAttributeCosts(e.Attributes, remainingFreeTier) - gas += attrCost - } - return gas -} - -func (g WasmGasRegister) eventAttributeCosts(attrs []wasmvmtypes.EventAttribute, freeTier uint64) (sdk.Gas, uint64) { - if len(attrs) == 0 { - return 0, freeTier - } - var storedBytes uint64 - for _, l := range attrs { - storedBytes += uint64(len(l.Key)) + uint64(len(l.Value)) - } - storedBytes, freeTier = calcWithFreeTier(storedBytes, freeTier) - // total Length * costs + attribute count * costs - r := sdk.NewIntFromUint64(g.c.EventAttributeDataCost).Mul(sdk.NewIntFromUint64(storedBytes)). - Add(sdk.NewIntFromUint64(g.c.EventPerAttributeCost).Mul(sdk.NewIntFromUint64(uint64(len(attrs))))) - if !r.IsUint64() { - panic(sdk.ErrorOutOfGas{Descriptor: "overflow"}) - } - return r.Uint64(), freeTier -} - -// apply free tier -func calcWithFreeTier(storedBytes uint64, freeTier uint64) (uint64, uint64) { - if storedBytes <= freeTier { - return 0, freeTier - storedBytes - } - storedBytes -= freeTier - return storedBytes, 0 -} - -// ToWasmVMGas convert to wasmVM contract runtime gas unit -func (g WasmGasRegister) ToWasmVMGas(source storetypes.Gas) uint64 { - x := source * g.c.GasMultiplier - if x < source { - panic(sdk.ErrorOutOfGas{Descriptor: "overflow"}) - } - return x -} - -// FromWasmVMGas converts to SDK gas unit -func (g WasmGasRegister) FromWasmVMGas(source uint64) sdk.Gas { - return source / g.c.GasMultiplier -} diff --git a/x/wasm/keeper/gas_register_test.go b/x/wasm/keeper/gas_register_test.go deleted file mode 100644 index 3a29e07..0000000 --- a/x/wasm/keeper/gas_register_test.go +++ /dev/null @@ -1,472 +0,0 @@ -package keeper - -import ( - "math" - "strings" - "testing" - - "github.com/terpnetwork/terp-core/x/wasm/types" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" -) - -func TestCompileCosts(t *testing.T) { - specs := map[string]struct { - srcLen int - srcConfig WasmGasRegisterConfig - exp sdk.Gas - expPanic bool - }{ - "one byte": { - srcLen: 1, - srcConfig: DefaultGasRegisterConfig(), - exp: sdk.Gas(3), // DefaultCompileCost - }, - "zero byte": { - srcLen: 0, - srcConfig: DefaultGasRegisterConfig(), - exp: sdk.Gas(0), - }, - "negative len": { - srcLen: -1, - srcConfig: DefaultGasRegisterConfig(), - expPanic: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - if spec.expPanic { - assert.Panics(t, func() { - NewWasmGasRegister(spec.srcConfig).CompileCosts(spec.srcLen) - }) - return - } - gotGas := NewWasmGasRegister(spec.srcConfig).CompileCosts(spec.srcLen) - assert.Equal(t, spec.exp, gotGas) - }) - } -} - -func TestNewContractInstanceCosts(t *testing.T) { - specs := map[string]struct { - srcLen int - srcConfig WasmGasRegisterConfig - pinned bool - exp sdk.Gas - expPanic bool - }{ - "small msg - pinned": { - srcLen: 1, - srcConfig: DefaultGasRegisterConfig(), - pinned: true, - exp: DefaultContractMessageDataCost, - }, - "big msg - pinned": { - srcLen: math.MaxUint32, - srcConfig: DefaultGasRegisterConfig(), - pinned: true, - exp: DefaultContractMessageDataCost * sdk.Gas(math.MaxUint32), - }, - "empty msg - pinned": { - srcLen: 0, - pinned: true, - srcConfig: DefaultGasRegisterConfig(), - exp: sdk.Gas(0), - }, - "small msg - unpinned": { - srcLen: 1, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultContractMessageDataCost + DefaultInstanceCost, - }, - "big msg - unpinned": { - srcLen: math.MaxUint32, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultContractMessageDataCost*math.MaxUint32 + DefaultInstanceCost, - }, - "empty msg - unpinned": { - srcLen: 0, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultInstanceCost, - }, - - "negative len": { - srcLen: -1, - srcConfig: DefaultGasRegisterConfig(), - expPanic: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - if spec.expPanic { - assert.Panics(t, func() { - NewWasmGasRegister(spec.srcConfig).NewContractInstanceCosts(spec.pinned, spec.srcLen) - }) - return - } - gotGas := NewWasmGasRegister(spec.srcConfig).NewContractInstanceCosts(spec.pinned, spec.srcLen) - assert.Equal(t, spec.exp, gotGas) - }) - } -} - -func TestContractInstanceCosts(t *testing.T) { - // same as TestNewContractInstanceCosts currently - specs := map[string]struct { - srcLen int - srcConfig WasmGasRegisterConfig - pinned bool - exp sdk.Gas - expPanic bool - }{ - "small msg - pinned": { - srcLen: 1, - srcConfig: DefaultGasRegisterConfig(), - pinned: true, - exp: DefaultContractMessageDataCost, - }, - "big msg - pinned": { - srcLen: math.MaxUint32, - srcConfig: DefaultGasRegisterConfig(), - pinned: true, - exp: DefaultContractMessageDataCost * math.MaxUint32, - }, - "empty msg - pinned": { - srcLen: 0, - pinned: true, - srcConfig: DefaultGasRegisterConfig(), - exp: sdk.Gas(0), - }, - "small msg - unpinned": { - srcLen: 1, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultContractMessageDataCost + DefaultInstanceCost, - }, - "big msg - unpinned": { - srcLen: math.MaxUint32, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultContractMessageDataCost*math.MaxUint32 + DefaultInstanceCost, - }, - "empty msg - unpinned": { - srcLen: 0, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultInstanceCost, - }, - - "negative len": { - srcLen: -1, - srcConfig: DefaultGasRegisterConfig(), - expPanic: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - if spec.expPanic { - assert.Panics(t, func() { - NewWasmGasRegister(spec.srcConfig).InstantiateContractCosts(spec.pinned, spec.srcLen) - }) - return - } - gotGas := NewWasmGasRegister(spec.srcConfig).InstantiateContractCosts(spec.pinned, spec.srcLen) - assert.Equal(t, spec.exp, gotGas) - }) - } -} - -func TestReplyCost(t *testing.T) { - specs := map[string]struct { - src wasmvmtypes.Reply - srcConfig WasmGasRegisterConfig - pinned bool - exp sdk.Gas - expPanic bool - }{ - "subcall response with events and data - pinned": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{ - Events: []wasmvmtypes.Event{ - {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myData"}}}, - }, - Data: []byte{0x1}, - }, - }, - }, - srcConfig: DefaultGasRegisterConfig(), - pinned: true, - exp: 3*DefaultEventAttributeDataCost + DefaultPerAttributeCost + DefaultContractMessageDataCost, // 3 == len("foo") - }, - "subcall response with events - pinned": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{ - Events: []wasmvmtypes.Event{ - {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myData"}}}, - }, - }, - }, - }, - srcConfig: DefaultGasRegisterConfig(), - pinned: true, - exp: 3*DefaultEventAttributeDataCost + DefaultPerAttributeCost, // 3 == len("foo") - }, - "subcall response with events exceeds free tier- pinned": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{ - Events: []wasmvmtypes.Event{ - {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: strings.Repeat("x", DefaultEventAttributeDataFreeTier), Value: "myData"}}}, - }, - }, - }, - }, - srcConfig: DefaultGasRegisterConfig(), - pinned: true, - exp: (3+6)*DefaultEventAttributeDataCost + DefaultPerAttributeCost, // 3 == len("foo"), 6 == len("myData") - }, - "subcall response error - pinned": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Err: "foo", - }, - }, - srcConfig: DefaultGasRegisterConfig(), - pinned: true, - exp: 3 * DefaultContractMessageDataCost, - }, - "subcall response with events and data - unpinned": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{ - Events: []wasmvmtypes.Event{ - {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myData"}}}, - }, - Data: []byte{0x1}, - }, - }, - }, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultInstanceCost + 3*DefaultEventAttributeDataCost + DefaultPerAttributeCost + DefaultContractMessageDataCost, - }, - "subcall response with events - unpinned": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{ - Events: []wasmvmtypes.Event{ - {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: "myKey", Value: "myData"}}}, - }, - }, - }, - }, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultInstanceCost + 3*DefaultEventAttributeDataCost + DefaultPerAttributeCost, - }, - "subcall response with events exceeds free tier- unpinned": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{ - Events: []wasmvmtypes.Event{ - {Type: "foo", Attributes: []wasmvmtypes.EventAttribute{{Key: strings.Repeat("x", DefaultEventAttributeDataFreeTier), Value: "myData"}}}, - }, - }, - }, - }, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultInstanceCost + (3+6)*DefaultEventAttributeDataCost + DefaultPerAttributeCost, // 3 == len("foo"), 6 == len("myData") - }, - "subcall response error - unpinned": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Err: "foo", - }, - }, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultInstanceCost + 3*DefaultContractMessageDataCost, - }, - "subcall response with empty events": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{ - Events: make([]wasmvmtypes.Event, 10), - }, - }, - }, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultInstanceCost, - }, - "subcall response with events unset": { - src: wasmvmtypes.Reply{ - Result: wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{}, - }, - }, - srcConfig: DefaultGasRegisterConfig(), - exp: DefaultInstanceCost, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - if spec.expPanic { - assert.Panics(t, func() { - NewWasmGasRegister(spec.srcConfig).ReplyCosts(spec.pinned, spec.src) - }) - return - } - gotGas := NewWasmGasRegister(spec.srcConfig).ReplyCosts(spec.pinned, spec.src) - assert.Equal(t, spec.exp, gotGas) - }) - } -} - -func TestEventCosts(t *testing.T) { - // most cases are covered in TestReplyCost already. This ensures some edge cases - specs := map[string]struct { - srcAttrs []wasmvmtypes.EventAttribute - srcEvents wasmvmtypes.Events - expGas sdk.Gas - }{ - "empty events": { - srcEvents: make([]wasmvmtypes.Event, 1), - expGas: DefaultPerCustomEventCost, - }, - "empty attributes": { - srcAttrs: make([]wasmvmtypes.EventAttribute, 1), - expGas: DefaultPerAttributeCost, - }, - "both nil": { - expGas: 0, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - gotGas := NewDefaultWasmGasRegister().EventCosts(spec.srcAttrs, spec.srcEvents) - assert.Equal(t, spec.expGas, gotGas) - }) - } -} - -func TestToWasmVMGasConversion(t *testing.T) { - specs := map[string]struct { - src storetypes.Gas - srcConfig WasmGasRegisterConfig - exp uint64 - expPanic bool - }{ - "0": { - src: 0, - exp: 0, - srcConfig: DefaultGasRegisterConfig(), - }, - "max": { - srcConfig: WasmGasRegisterConfig{ - GasMultiplier: 1, - }, - src: math.MaxUint64, - exp: math.MaxUint64, - }, - "overflow": { - srcConfig: WasmGasRegisterConfig{ - GasMultiplier: 2, - }, - src: math.MaxUint64, - expPanic: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - if spec.expPanic { - assert.Panics(t, func() { - r := NewWasmGasRegister(spec.srcConfig) - _ = r.ToWasmVMGas(spec.src) - }) - return - } - r := NewWasmGasRegister(spec.srcConfig) - got := r.ToWasmVMGas(spec.src) - assert.Equal(t, spec.exp, got) - }) - } -} - -func TestFromWasmVMGasConversion(t *testing.T) { - specs := map[string]struct { - src uint64 - exp storetypes.Gas - srcConfig WasmGasRegisterConfig - expPanic bool - }{ - "0": { - src: 0, - exp: 0, - srcConfig: DefaultGasRegisterConfig(), - }, - "max": { - srcConfig: WasmGasRegisterConfig{ - GasMultiplier: 1, - }, - src: math.MaxUint64, - exp: math.MaxUint64, - }, - "missconfigured": { - srcConfig: WasmGasRegisterConfig{ - GasMultiplier: 0, - }, - src: 1, - expPanic: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - if spec.expPanic { - assert.Panics(t, func() { - r := NewWasmGasRegister(spec.srcConfig) - _ = r.FromWasmVMGas(spec.src) - }) - return - } - r := NewWasmGasRegister(spec.srcConfig) - got := r.FromWasmVMGas(spec.src) - assert.Equal(t, spec.exp, got) - }) - } -} - -func TestUncompressCosts(t *testing.T) { - specs := map[string]struct { - lenIn int - exp sdk.Gas - expPanic bool - }{ - "0": { - exp: 0, - }, - "even": { - lenIn: 100, - exp: 15, - }, - "round down when uneven": { - lenIn: 19, - exp: 2, - }, - "max len": { - lenIn: types.MaxWasmSize, - exp: 122880, - }, - "invalid len": { - lenIn: -1, - expPanic: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - if spec.expPanic { - assert.Panics(t, func() { NewDefaultWasmGasRegister().UncompressCosts(spec.lenIn) }) - return - } - got := NewDefaultWasmGasRegister().UncompressCosts(spec.lenIn) - assert.Equal(t, spec.exp, got) - }) - } -} diff --git a/x/wasm/keeper/genesis.go b/x/wasm/keeper/genesis.go deleted file mode 100644 index 0e467ea..0000000 --- a/x/wasm/keeper/genesis.go +++ /dev/null @@ -1,120 +0,0 @@ -package keeper - -import ( - errorsmod "cosmossdk.io/errors" - abci "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// ValidatorSetSource is a subset of the staking keeper -type ValidatorSetSource interface { - ApplyAndReturnValidatorSetUpdates(sdk.Context) (updates []abci.ValidatorUpdate, err error) -} - -// InitGenesis sets supply information for genesis. -// -// CONTRACT: all types of accounts must have been already initialized/created -func InitGenesis(ctx sdk.Context, keeper *Keeper, data types.GenesisState) ([]abci.ValidatorUpdate, error) { - contractKeeper := NewGovPermissionKeeper(keeper) - err := keeper.SetParams(ctx, data.Params) - if err != nil { - return nil, errorsmod.Wrapf(err, "set params") - } - - var maxCodeID uint64 - for i, code := range data.Codes { - err := keeper.importCode(ctx, code.CodeID, code.CodeInfo, code.CodeBytes) - if err != nil { - return nil, errorsmod.Wrapf(err, "code %d with id: %d", i, code.CodeID) - } - if code.CodeID > maxCodeID { - maxCodeID = code.CodeID - } - if code.Pinned { - if err := contractKeeper.PinCode(ctx, code.CodeID); err != nil { - return nil, errorsmod.Wrapf(err, "contract number %d", i) - } - } - } - - var maxContractID int - for i, contract := range data.Contracts { - contractAddr, err := sdk.AccAddressFromBech32(contract.ContractAddress) - if err != nil { - return nil, errorsmod.Wrapf(err, "address in contract number %d", i) - } - err = keeper.importContract(ctx, contractAddr, &contract.ContractInfo, contract.ContractState, contract.ContractCodeHistory) - if err != nil { - return nil, errorsmod.Wrapf(err, "contract number %d", i) - } - maxContractID = i + 1 // not ideal but max(contractID) is not persisted otherwise - } - - for i, seq := range data.Sequences { - err := keeper.importAutoIncrementID(ctx, seq.IDKey, seq.Value) - if err != nil { - return nil, errorsmod.Wrapf(err, "sequence number %d", i) - } - } - - // sanity check seq values - seqVal := keeper.PeekAutoIncrementID(ctx, types.KeyLastCodeID) - if seqVal <= maxCodeID { - return nil, errorsmod.Wrapf(types.ErrInvalid, "seq %s with value: %d must be greater than: %d ", string(types.KeyLastCodeID), seqVal, maxCodeID) - } - seqVal = keeper.PeekAutoIncrementID(ctx, types.KeyLastInstanceID) - if seqVal <= uint64(maxContractID) { - return nil, errorsmod.Wrapf(types.ErrInvalid, "seq %s with value: %d must be greater than: %d ", string(types.KeyLastInstanceID), seqVal, maxContractID) - } - return nil, nil -} - -// ExportGenesis returns a GenesisState for a given context and keeper. -func ExportGenesis(ctx sdk.Context, keeper *Keeper) *types.GenesisState { - var genState types.GenesisState - - genState.Params = keeper.GetParams(ctx) - - keeper.IterateCodeInfos(ctx, func(codeID uint64, info types.CodeInfo) bool { - bytecode, err := keeper.GetByteCode(ctx, codeID) - if err != nil { - panic(err) - } - genState.Codes = append(genState.Codes, types.Code{ - CodeID: codeID, - CodeInfo: info, - CodeBytes: bytecode, - Pinned: keeper.IsPinnedCode(ctx, codeID), - }) - return false - }) - - keeper.IterateContractInfo(ctx, func(addr sdk.AccAddress, contract types.ContractInfo) bool { - var state []types.Model - keeper.IterateContractState(ctx, addr, func(key, value []byte) bool { - state = append(state, types.Model{Key: key, Value: value}) - return false - }) - - contractCodeHistory := keeper.GetContractHistory(ctx, addr) - - genState.Contracts = append(genState.Contracts, types.Contract{ - ContractAddress: addr.String(), - ContractInfo: contract, - ContractState: state, - ContractCodeHistory: contractCodeHistory, - }) - return false - }) - - for _, k := range [][]byte{types.KeyLastCodeID, types.KeyLastInstanceID} { - genState.Sequences = append(genState.Sequences, types.Sequence{ - IDKey: k, - Value: keeper.PeekAutoIncrementID(ctx, k), - }) - } - - return &genState -} diff --git a/x/wasm/keeper/genesis_test.go b/x/wasm/keeper/genesis_test.go deleted file mode 100644 index 6be1689..0000000 --- a/x/wasm/keeper/genesis_test.go +++ /dev/null @@ -1,705 +0,0 @@ -package keeper - -import ( - "crypto/sha256" - "encoding/base64" - "fmt" - "math/rand" - "os" - "testing" - "time" - - abci "github.com/cometbft/cometbft/abci/types" - - "github.com/cosmos/cosmos-sdk/baseapp" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - - dbm "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/libs/log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - fuzz "github.com/google/gofuzz" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -const firstCodeID = 1 - -func TestGenesisExportImport(t *testing.T) { - wasmKeeper, srcCtx := setupKeeper(t) - contractKeeper := NewGovPermissionKeeper(wasmKeeper) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - // store some test data - f := fuzz.New().Funcs(ModelFuzzers...) - - err = wasmKeeper.SetParams(srcCtx, types.DefaultParams()) - require.NoError(t, err) - - for i := 0; i < 25; i++ { - var ( - codeInfo types.CodeInfo - contract types.ContractInfo - stateModels []types.Model - history []types.ContractCodeHistoryEntry - pinned bool - contractExtension bool - ) - f.Fuzz(&codeInfo) - f.Fuzz(&contract) - f.Fuzz(&stateModels) - f.NilChance(0).Fuzz(&history) - f.Fuzz(&pinned) - f.Fuzz(&contractExtension) - - creatorAddr, err := sdk.AccAddressFromBech32(codeInfo.Creator) - require.NoError(t, err) - codeID, _, err := contractKeeper.Create(srcCtx, creatorAddr, wasmCode, &codeInfo.InstantiateConfig) - require.NoError(t, err) - if pinned { - err = contractKeeper.PinCode(srcCtx, codeID) - require.NoError(t, err) - } - if contractExtension { - anyTime := time.Now().UTC() - var nestedType v1beta1.TextProposal - f.NilChance(0).Fuzz(&nestedType) - myExtension, err := v1beta1.NewProposal(&nestedType, 1, anyTime, anyTime) - require.NoError(t, err) - err = contract.SetExtension(&myExtension) - require.NoError(t, err) - } - - contract.CodeID = codeID - contractAddr := wasmKeeper.ClassicAddressGenerator()(srcCtx, codeID, nil) - wasmKeeper.storeContractInfo(srcCtx, contractAddr, &contract) - wasmKeeper.appendToContractHistory(srcCtx, contractAddr, history...) - err = wasmKeeper.importContractState(srcCtx, contractAddr, stateModels) - require.NoError(t, err) - } - var wasmParams types.Params - f.NilChance(0).Fuzz(&wasmParams) - err = wasmKeeper.SetParams(srcCtx, wasmParams) - require.NoError(t, err) - - // export - exportedState := ExportGenesis(srcCtx, wasmKeeper) - // order should not matter - rand.Shuffle(len(exportedState.Codes), func(i, j int) { - exportedState.Codes[i], exportedState.Codes[j] = exportedState.Codes[j], exportedState.Codes[i] - }) - rand.Shuffle(len(exportedState.Contracts), func(i, j int) { - exportedState.Contracts[i], exportedState.Contracts[j] = exportedState.Contracts[j], exportedState.Contracts[i] - }) - rand.Shuffle(len(exportedState.Sequences), func(i, j int) { - exportedState.Sequences[i], exportedState.Sequences[j] = exportedState.Sequences[j], exportedState.Sequences[i] - }) - exportedGenesis, err := wasmKeeper.cdc.MarshalJSON(exportedState) - require.NoError(t, err) - - // setup new instances - dstKeeper, dstCtx := setupKeeper(t) - - // reset contract code index in source DB for comparison with dest DB - wasmKeeper.IterateContractInfo(srcCtx, func(address sdk.AccAddress, info types.ContractInfo) bool { - creatorAddress := sdk.MustAccAddressFromBech32(info.Creator) - history := wasmKeeper.GetContractHistory(srcCtx, address) - - wasmKeeper.addToContractCodeSecondaryIndex(srcCtx, address, history[len(history)-1]) - wasmKeeper.addToContractCreatorSecondaryIndex(srcCtx, creatorAddress, history[0].Updated, address) - return false - }) - - // re-import - var importState types.GenesisState - err = dstKeeper.cdc.UnmarshalJSON(exportedGenesis, &importState) - require.NoError(t, err) - _, err = InitGenesis(dstCtx, dstKeeper, importState) - require.NoError(t, err) - - // compare whole DB - - srcIT := srcCtx.KVStore(wasmKeeper.storeKey).Iterator(nil, nil) - dstIT := dstCtx.KVStore(dstKeeper.storeKey).Iterator(nil, nil) - - for i := 0; srcIT.Valid(); i++ { - require.True(t, dstIT.Valid(), "[%s] destination DB has less elements than source. Missing: %x", wasmKeeper.storeKey.Name(), srcIT.Key()) - require.Equal(t, srcIT.Key(), dstIT.Key(), i) - require.Equal(t, srcIT.Value(), dstIT.Value(), "[%s] element (%d): %X", wasmKeeper.storeKey.Name(), i, srcIT.Key()) - dstIT.Next() - srcIT.Next() - } - if !assert.False(t, dstIT.Valid()) { - t.Fatalf("dest Iterator still has key :%X", dstIT.Key()) - } - srcIT.Close() - dstIT.Close() -} - -func TestGenesisInit(t *testing.T) { - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - myCodeInfo := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)) - specs := map[string]struct { - src types.GenesisState - expSuccess bool - }{ - "happy path: code info correct": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Sequences: []types.Sequence{ - {IDKey: types.KeyLastCodeID, Value: 2}, - {IDKey: types.KeyLastInstanceID, Value: 1}, - }, - Params: types.DefaultParams(), - }, - expSuccess: true, - }, - "happy path: code ids can contain gaps": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }, { - CodeID: 3, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Sequences: []types.Sequence{ - {IDKey: types.KeyLastCodeID, Value: 10}, - {IDKey: types.KeyLastInstanceID, Value: 1}, - }, - Params: types.DefaultParams(), - }, - expSuccess: true, - }, - "happy path: code order does not matter": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: 2, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }, { - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Contracts: nil, - Sequences: []types.Sequence{ - {IDKey: types.KeyLastCodeID, Value: 3}, - {IDKey: types.KeyLastInstanceID, Value: 1}, - }, - Params: types.DefaultParams(), - }, - expSuccess: true, - }, - "prevent code hash mismatch": {src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: firstCodeID, - CodeInfo: types.CodeInfoFixture(func(i *types.CodeInfo) { i.CodeHash = make([]byte, sha256.Size) }), - CodeBytes: wasmCode, - }}, - Params: types.DefaultParams(), - }}, - "prevent duplicate codeIDs": {src: types.GenesisState{ - Codes: []types.Code{ - { - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }, - { - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }, - }, - Params: types.DefaultParams(), - }}, - "codes with same checksum can be pinned": { - src: types.GenesisState{ - Codes: []types.Code{ - { - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - Pinned: true, - }, - { - CodeID: 2, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - Pinned: true, - }, - }, - Params: types.DefaultParams(), - }, - }, - "happy path: code id in info and contract do match": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Contracts: []types.Contract{ - { - ContractAddress: BuildContractAddressClassic(1, 1).String(), - ContractInfo: types.ContractInfoFixture(func(c *types.ContractInfo) { c.CodeID = 1 }, types.RandCreatedFields), - ContractCodeHistory: []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 1, - Updated: &types.AbsoluteTxPosition{BlockHeight: rand.Uint64(), TxIndex: rand.Uint64()}, - Msg: []byte(`{}`), - }, - }, - }, - }, - Sequences: []types.Sequence{ - {IDKey: types.KeyLastCodeID, Value: 2}, - {IDKey: types.KeyLastInstanceID, Value: 2}, - }, - Params: types.DefaultParams(), - }, - expSuccess: true, - }, - "happy path: code info with two contracts": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Contracts: []types.Contract{ - { - ContractAddress: BuildContractAddressClassic(1, 1).String(), - ContractInfo: types.ContractInfoFixture(func(c *types.ContractInfo) { c.CodeID = 1 }, types.RandCreatedFields), - ContractCodeHistory: []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 1, - Updated: &types.AbsoluteTxPosition{BlockHeight: rand.Uint64(), TxIndex: rand.Uint64()}, - Msg: []byte(`{}`), - }, - }, - }, { - ContractAddress: BuildContractAddressClassic(1, 2).String(), - ContractInfo: types.ContractInfoFixture(func(c *types.ContractInfo) { c.CodeID = 1 }, types.RandCreatedFields), - ContractCodeHistory: []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 1, - Updated: &types.AbsoluteTxPosition{BlockHeight: rand.Uint64(), TxIndex: rand.Uint64()}, - Msg: []byte(`{"foo":"bar"}`), - }, - }, - }, - }, - Sequences: []types.Sequence{ - {IDKey: types.KeyLastCodeID, Value: 2}, - {IDKey: types.KeyLastInstanceID, Value: 3}, - }, - Params: types.DefaultParams(), - }, - expSuccess: true, - }, - "prevent contracts that points to non existing codeID": { - src: types.GenesisState{ - Contracts: []types.Contract{ - { - ContractAddress: BuildContractAddressClassic(1, 1).String(), - ContractInfo: types.ContractInfoFixture(func(c *types.ContractInfo) { c.CodeID = 1 }, types.RandCreatedFields), - ContractCodeHistory: []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 1, - Updated: &types.AbsoluteTxPosition{BlockHeight: rand.Uint64(), TxIndex: rand.Uint64()}, - Msg: []byte(`{"foo":"bar"}`), - }, - }, - }, - }, - Params: types.DefaultParams(), - }, - }, - "prevent duplicate contract address": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Contracts: []types.Contract{ - { - ContractAddress: BuildContractAddressClassic(1, 1).String(), - ContractInfo: types.ContractInfoFixture(func(c *types.ContractInfo) { c.CodeID = 1 }, types.RandCreatedFields), - ContractCodeHistory: []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 1, - Updated: &types.AbsoluteTxPosition{BlockHeight: rand.Uint64(), TxIndex: rand.Uint64()}, - Msg: []byte(`{"foo":"bar"}`), - }, - }, - }, { - ContractAddress: BuildContractAddressClassic(1, 1).String(), - ContractInfo: types.ContractInfoFixture(func(c *types.ContractInfo) { c.CodeID = 1 }, types.RandCreatedFields), - ContractCodeHistory: []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 1, - Updated: &types.AbsoluteTxPosition{BlockHeight: rand.Uint64(), TxIndex: rand.Uint64()}, - Msg: []byte(`{"other":"value"}`), - }, - }, - }, - }, - Params: types.DefaultParams(), - }, - }, - "prevent duplicate contract model keys": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Contracts: []types.Contract{ - { - ContractAddress: BuildContractAddressClassic(1, 1).String(), - ContractInfo: types.ContractInfoFixture(func(c *types.ContractInfo) { c.CodeID = 1 }, types.RandCreatedFields), - ContractState: []types.Model{ - { - Key: []byte{0x1}, - Value: []byte("foo"), - }, - { - Key: []byte{0x1}, - Value: []byte("bar"), - }, - }, - ContractCodeHistory: []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 1, - Updated: &types.AbsoluteTxPosition{BlockHeight: rand.Uint64(), TxIndex: rand.Uint64()}, - Msg: []byte(`{"foo":"bar"}`), - }, - }, - }, - }, - Params: types.DefaultParams(), - }, - }, - "prevent duplicate sequences": { - src: types.GenesisState{ - Sequences: []types.Sequence{ - {IDKey: []byte("foo"), Value: 1}, - {IDKey: []byte("foo"), Value: 9999}, - }, - Params: types.DefaultParams(), - }, - }, - "prevent code id seq init value == max codeID used": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: 2, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Sequences: []types.Sequence{ - {IDKey: types.KeyLastCodeID, Value: 1}, - }, - Params: types.DefaultParams(), - }, - }, - "prevent contract id seq init value == count contracts": { - src: types.GenesisState{ - Codes: []types.Code{{ - CodeID: firstCodeID, - CodeInfo: myCodeInfo, - CodeBytes: wasmCode, - }}, - Contracts: []types.Contract{ - { - ContractAddress: BuildContractAddressClassic(1, 1).String(), - ContractInfo: types.ContractInfoFixture(func(c *types.ContractInfo) { c.CodeID = 1 }, types.RandCreatedFields), - ContractCodeHistory: []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 1, - Updated: &types.AbsoluteTxPosition{BlockHeight: rand.Uint64(), TxIndex: rand.Uint64()}, - Msg: []byte(`{}`), - }, - }, - }, - }, - Sequences: []types.Sequence{ - {IDKey: types.KeyLastCodeID, Value: 2}, - {IDKey: types.KeyLastInstanceID, Value: 1}, - }, - Params: types.DefaultParams(), - }, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - keeper, ctx := setupKeeper(t) - - require.NoError(t, types.ValidateGenesis(spec.src)) - _, gotErr := InitGenesis(ctx, keeper, spec.src) - if !spec.expSuccess { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - - for _, c := range spec.src.Codes { - assert.Equal(t, c.Pinned, keeper.IsPinnedCode(ctx, c.CodeID)) - } - }) - } -} - -func TestImportContractWithCodeHistoryPreserved(t *testing.T) { - genesisTemplate := ` -{ - "params":{ - "code_upload_access": { - "permission": "Everybody" - }, - "instantiate_default_permission": "Everybody" - }, - "codes": [ - { - "code_id": "1", - "code_info": { - "code_hash": %q, - "creator": "cosmos1qtu5n0cnhfkjj6l2rq97hmky9fd89gwca9yarx", - "instantiate_config": { - "permission": "OnlyAddress", - "address": "cosmos1qtu5n0cnhfkjj6l2rq97hmky9fd89gwca9yarx" - } - }, - "code_bytes": %q - } - ], - "contracts": [ - { - "contract_address": "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", - "contract_info": { - "code_id": "1", - "creator": "cosmos13x849jzd03vne42ynpj25hn8npjecxqrjghd8x", - "admin": "cosmos1h5t8zxmjr30e9dqghtlpl40f2zz5cgey6esxtn", - "label": "ȀĴnZV芢毤", - "created": { - "block_height" : "100", - "tx_index" : "10" - } - }, - "contract_code_history": [ - { - "operation": "CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT", - "code_id": "1", - "updated": { - "block_height" : "100", - "tx_index" : "10" - }, - "msg": {"foo": "bar"} - }, - { - "operation": "CONTRACT_CODE_HISTORY_OPERATION_TYPE_MIGRATE", - "code_id": "1", - "updated": { - "block_height" : "200", - "tx_index" : "10" - }, - "msg": {"other": "msg"} - } - ] - } - ], - "sequences": [ - {"id_key": "BGxhc3RDb2RlSWQ=", "value": "2"}, - {"id_key": "BGxhc3RDb250cmFjdElk", "value": "3"} - ] -}` - keeper, ctx := setupKeeper(t) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - wasmCodeHash := sha256.Sum256(wasmCode) - enc64 := base64.StdEncoding.EncodeToString - genesisStr := fmt.Sprintf(genesisTemplate, enc64(wasmCodeHash[:]), enc64(wasmCode)) - - var importState types.GenesisState - err = keeper.cdc.UnmarshalJSON([]byte(genesisStr), &importState) - require.NoError(t, err) - require.NoError(t, importState.ValidateBasic(), genesisStr) - - ctx = ctx.WithBlockHeight(0).WithGasMeter(sdk.NewInfiniteGasMeter()) - - // when - _, err = InitGenesis(ctx, keeper, importState) - require.NoError(t, err) - - // verify wasm code - gotWasmCode, err := keeper.GetByteCode(ctx, 1) - require.NoError(t, err) - assert.Equal(t, wasmCode, gotWasmCode, "byte code does not match") - - // verify code info - gotCodeInfo := keeper.GetCodeInfo(ctx, 1) - require.NotNil(t, gotCodeInfo) - codeCreatorAddr := "cosmos1qtu5n0cnhfkjj6l2rq97hmky9fd89gwca9yarx" - expCodeInfo := types.CodeInfo{ - CodeHash: wasmCodeHash[:], - Creator: codeCreatorAddr, - InstantiateConfig: types.AccessConfig{ - Permission: types.AccessTypeOnlyAddress, - Address: codeCreatorAddr, - }, - } - assert.Equal(t, expCodeInfo, *gotCodeInfo) - - // verify contract - contractAddr, _ := sdk.AccAddressFromBech32("cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr") - gotContractInfo := keeper.GetContractInfo(ctx, contractAddr) - require.NotNil(t, gotContractInfo) - contractCreatorAddr := "cosmos13x849jzd03vne42ynpj25hn8npjecxqrjghd8x" - adminAddr := "cosmos1h5t8zxmjr30e9dqghtlpl40f2zz5cgey6esxtn" - - expContractInfo := types.ContractInfo{ - CodeID: firstCodeID, - Creator: contractCreatorAddr, - Admin: adminAddr, - Label: "ȀĴnZV芢毤", - Created: &types.AbsoluteTxPosition{BlockHeight: 100, TxIndex: 10}, - } - assert.Equal(t, expContractInfo, *gotContractInfo) - - expHistory := []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: firstCodeID, - Updated: &types.AbsoluteTxPosition{ - BlockHeight: 100, - TxIndex: 10, - }, - Msg: []byte(`{"foo": "bar"}`), - }, - { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: firstCodeID, - Updated: &types.AbsoluteTxPosition{ - BlockHeight: 200, - TxIndex: 10, - }, - Msg: []byte(`{"other": "msg"}`), - }, - } - assert.Equal(t, expHistory, keeper.GetContractHistory(ctx, contractAddr)) - assert.Equal(t, uint64(2), keeper.PeekAutoIncrementID(ctx, types.KeyLastCodeID)) - assert.Equal(t, uint64(3), keeper.PeekAutoIncrementID(ctx, types.KeyLastInstanceID)) -} - -func setupKeeper(t *testing.T) (*Keeper, sdk.Context) { - t.Helper() - tempDir, err := os.MkdirTemp("", "wasm") - require.NoError(t, err) - t.Cleanup(func() { os.RemoveAll(tempDir) }) - - keyWasm := sdk.NewKVStoreKey(types.StoreKey) - - db := dbm.NewMemDB() - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(keyWasm, storetypes.StoreTypeIAVL, db) - require.NoError(t, ms.LoadLatestVersion()) - - ctx := sdk.NewContext(ms, tmproto.Header{ - Height: 1234567, - Time: time.Date(2020, time.April, 22, 12, 0, 0, 0, time.UTC), - }, false, log.NewNopLogger()) - - encodingConfig := MakeEncodingConfig(t) - // register an example extension. must be protobuf - encodingConfig.InterfaceRegistry.RegisterImplementations( - (*types.ContractInfoExtension)(nil), - &v1beta1.Proposal{}, - ) - // also registering gov interfaces for nested Any type - v1beta1.RegisterInterfaces(encodingConfig.InterfaceRegistry) - - wasmConfig := types.DefaultWasmConfig() - - srcKeeper := NewKeeper( - encodingConfig.Marshaler, - keyWasm, - authkeeper.AccountKeeper{}, - &bankkeeper.BaseKeeper{}, - stakingkeeper.Keeper{}, - nil, - nil, - nil, - nil, - nil, - nil, - nil, - tempDir, - wasmConfig, - AvailableCapabilities, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) - return &srcKeeper, ctx -} - -type StakingKeeperMock struct { - err error - validatorUpdate []abci.ValidatorUpdate - gotCalls int -} - -func (s *StakingKeeperMock) ApplyAndReturnValidatorSetUpdates(_ sdk.Context) ([]abci.ValidatorUpdate, error) { - s.gotCalls++ - return s.validatorUpdate, s.err -} - -var _ MessageRouter = &MockMsgHandler{} - -type MockMsgHandler struct { - result *sdk.Result - err error - expCalls int //nolint:unused - gotCalls int - expMsg sdk.Msg //nolint:unused - gotMsg sdk.Msg -} - -func (m *MockMsgHandler) Handler(msg sdk.Msg) baseapp.MsgServiceHandler { - return m.Handle -} - -func (m *MockMsgHandler) Handle(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - m.gotCalls++ - m.gotMsg = msg - return m.result, m.err -} diff --git a/x/wasm/keeper/handler_plugin.go b/x/wasm/keeper/handler_plugin.go deleted file mode 100644 index beba71a..0000000 --- a/x/wasm/keeper/handler_plugin.go +++ /dev/null @@ -1,219 +0,0 @@ -package keeper - -import ( - "errors" - "fmt" - - errorsmod "cosmossdk.io/errors" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/cosmos/cosmos-sdk/baseapp" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// msgEncoder is an extension point to customize encodings -type msgEncoder interface { - // Encode converts wasmvm message to n cosmos message types - Encode(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Msg, error) -} - -// MessageRouter ADR 031 request type routing -type MessageRouter interface { - Handler(msg sdk.Msg) baseapp.MsgServiceHandler -} - -// SDKMessageHandler can handles messages that can be encoded into sdk.Message types and routed. -type SDKMessageHandler struct { - router MessageRouter - encoders msgEncoder -} - -func NewDefaultMessageHandler( - router MessageRouter, - channelKeeper types.ChannelKeeper, - capabilityKeeper types.CapabilityKeeper, - bankKeeper types.Burner, - unpacker codectypes.AnyUnpacker, - portSource types.ICS20TransferPortSource, - customEncoders ...*MessageEncoders, -) Messenger { - encoders := DefaultEncoders(unpacker, portSource) - for _, e := range customEncoders { - encoders = encoders.Merge(e) - } - return NewMessageHandlerChain( - NewSDKMessageHandler(router, encoders), - NewIBCRawPacketHandler(channelKeeper, capabilityKeeper), - NewBurnCoinMessageHandler(bankKeeper), - ) -} - -func NewSDKMessageHandler(router MessageRouter, encoders msgEncoder) SDKMessageHandler { - return SDKMessageHandler{ - router: router, - encoders: encoders, - } -} - -func (h SDKMessageHandler) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - sdkMsgs, err := h.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg) - if err != nil { - return nil, nil, err - } - for _, sdkMsg := range sdkMsgs { - res, err := h.handleSdkMessage(ctx, contractAddr, sdkMsg) - if err != nil { - return nil, nil, err - } - // append data - data = append(data, res.Data) - // append events - sdkEvents := make([]sdk.Event, len(res.Events)) - for i := range res.Events { - sdkEvents[i] = sdk.Event(res.Events[i]) - } - events = append(events, sdkEvents...) - } - return -} - -func (h SDKMessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - // make sure this account can send it - for _, acct := range msg.GetSigners() { - if !acct.Equals(contractAddr) { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission") - } - } - - // find the handler and execute it - if handler := h.router.Handler(msg); handler != nil { - // ADR 031 request type routing - msgResult, err := handler(ctx, msg) - return msgResult, err - } - // legacy sdk.Msg routing - // Assuming that the app developer has migrated all their Msgs to - // proto messages and has registered all `Msg services`, then this - // path should never be called, because all those Msgs should be - // registered within the `msgServiceRouter` already. - return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) -} - -// MessageHandlerChain defines a chain of handlers that are called one by one until it can be handled. -type MessageHandlerChain struct { - handlers []Messenger -} - -func NewMessageHandlerChain(first Messenger, others ...Messenger) *MessageHandlerChain { - r := &MessageHandlerChain{handlers: append([]Messenger{first}, others...)} - for i := range r.handlers { - if r.handlers[i] == nil { - panic(fmt.Sprintf("handler must not be nil at position : %d", i)) - } - } - return r -} - -// DispatchMsg dispatch message and calls chained handlers one after another in -// order to find the right one to process given message. If a handler cannot -// process given message (returns ErrUnknownMsg), its result is ignored and the -// next handler is executed. -func (m MessageHandlerChain) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { - for _, h := range m.handlers { - events, data, err := h.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) - switch { - case err == nil: - return events, data, nil - case errors.Is(err, types.ErrUnknownMsg): - continue - default: - return events, data, err - } - } - return nil, nil, errorsmod.Wrap(types.ErrUnknownMsg, "no handler found") -} - -// IBCRawPacketHandler handels IBC.SendPacket messages which are published to an IBC channel. -type IBCRawPacketHandler struct { - channelKeeper types.ChannelKeeper - capabilityKeeper types.CapabilityKeeper -} - -func NewIBCRawPacketHandler(chk types.ChannelKeeper, cak types.CapabilityKeeper) IBCRawPacketHandler { - return IBCRawPacketHandler{channelKeeper: chk, capabilityKeeper: cak} -} - -// DispatchMsg publishes a raw IBC packet onto the channel. -func (h IBCRawPacketHandler) DispatchMsg(ctx sdk.Context, _ sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { - if msg.IBC == nil || msg.IBC.SendPacket == nil { - return nil, nil, types.ErrUnknownMsg - } - if contractIBCPortID == "" { - return nil, nil, errorsmod.Wrapf(types.ErrUnsupportedForContract, "ibc not supported") - } - contractIBCChannelID := msg.IBC.SendPacket.ChannelID - if contractIBCChannelID == "" { - return nil, nil, errorsmod.Wrapf(types.ErrEmpty, "ibc channel") - } - - channelCap, ok := h.capabilityKeeper.GetCapability(ctx, host.ChannelCapabilityPath(contractIBCPortID, contractIBCChannelID)) - if !ok { - return nil, nil, errorsmod.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") - } - seq, err := h.channelKeeper.SendPacket(ctx, channelCap, contractIBCPortID, contractIBCChannelID, ConvertWasmIBCTimeoutHeightToCosmosHeight(msg.IBC.SendPacket.Timeout.Block), msg.IBC.SendPacket.Timeout.Timestamp, msg.IBC.SendPacket.Data) - if err != nil { - return nil, nil, errorsmod.Wrap(err, "channel") - } - moduleLogger(ctx).Debug("ibc packet set", "seq", seq) - - resp := &types.MsgIBCSendResponse{Sequence: seq} - val, err := resp.Marshal() - if err != nil { - return nil, nil, errorsmod.Wrap(err, "failed to marshal IBC send response") - } - - return nil, [][]byte{val}, nil -} - -var _ Messenger = MessageHandlerFunc(nil) - -// MessageHandlerFunc is a helper to construct a function based message handler. -type MessageHandlerFunc func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) - -// DispatchMsg delegates dispatching of provided message into the MessageHandlerFunc. -func (m MessageHandlerFunc) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return m(ctx, contractAddr, contractIBCPortID, msg) -} - -// NewBurnCoinMessageHandler handles wasmvm.BurnMsg messages -func NewBurnCoinMessageHandler(burner types.Burner) MessageHandlerFunc { - return func(ctx sdk.Context, contractAddr sdk.AccAddress, _ string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - if msg.Bank != nil && msg.Bank.Burn != nil { - coins, err := ConvertWasmCoinsToSdkCoins(msg.Bank.Burn.Amount) - if err != nil { - return nil, nil, err - } - if coins.IsZero() { - return nil, nil, types.ErrEmpty.Wrap("amount") - } - if err := burner.SendCoinsFromAccountToModule(ctx, contractAddr, types.ModuleName, coins); err != nil { - return nil, nil, errorsmod.Wrap(err, "transfer to module") - } - if err := burner.BurnCoins(ctx, types.ModuleName, coins); err != nil { - return nil, nil, errorsmod.Wrap(err, "burn coins") - } - moduleLogger(ctx).Info("Burned", "amount", coins) - return nil, nil, nil - } - return nil, nil, types.ErrUnknownMsg - } -} diff --git a/x/wasm/keeper/handler_plugin_encoders.go b/x/wasm/keeper/handler_plugin_encoders.go deleted file mode 100644 index e6883e1..0000000 --- a/x/wasm/keeper/handler_plugin_encoders.go +++ /dev/null @@ -1,395 +0,0 @@ -package keeper - -import ( - "encoding/json" - "fmt" - - errorsmod "cosmossdk.io/errors" - v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -type ( - BankEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.BankMsg) ([]sdk.Msg, error) - CustomEncoder func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) - DistributionEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.DistributionMsg) ([]sdk.Msg, error) - StakingEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.StakingMsg) ([]sdk.Msg, error) - StargateEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.StargateMsg) ([]sdk.Msg, error) - WasmEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.WasmMsg) ([]sdk.Msg, error) - IBCEncoder func(ctx sdk.Context, sender sdk.AccAddress, contractIBCPortID string, msg *wasmvmtypes.IBCMsg) ([]sdk.Msg, error) -) - -type MessageEncoders struct { - Bank func(sender sdk.AccAddress, msg *wasmvmtypes.BankMsg) ([]sdk.Msg, error) - Custom func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) - Distribution func(sender sdk.AccAddress, msg *wasmvmtypes.DistributionMsg) ([]sdk.Msg, error) - IBC func(ctx sdk.Context, sender sdk.AccAddress, contractIBCPortID string, msg *wasmvmtypes.IBCMsg) ([]sdk.Msg, error) - Staking func(sender sdk.AccAddress, msg *wasmvmtypes.StakingMsg) ([]sdk.Msg, error) - Stargate func(sender sdk.AccAddress, msg *wasmvmtypes.StargateMsg) ([]sdk.Msg, error) - Wasm func(sender sdk.AccAddress, msg *wasmvmtypes.WasmMsg) ([]sdk.Msg, error) - Gov func(sender sdk.AccAddress, msg *wasmvmtypes.GovMsg) ([]sdk.Msg, error) -} - -func DefaultEncoders(unpacker codectypes.AnyUnpacker, portSource types.ICS20TransferPortSource) MessageEncoders { - return MessageEncoders{ - Bank: EncodeBankMsg, - Custom: NoCustomMsg, - Distribution: EncodeDistributionMsg, - IBC: EncodeIBCMsg(portSource), - Staking: EncodeStakingMsg, - Stargate: EncodeStargateMsg(unpacker), - Wasm: EncodeWasmMsg, - Gov: EncodeGovMsg, - } -} - -func (e MessageEncoders) Merge(o *MessageEncoders) MessageEncoders { - if o == nil { - return e - } - if o.Bank != nil { - e.Bank = o.Bank - } - if o.Custom != nil { - e.Custom = o.Custom - } - if o.Distribution != nil { - e.Distribution = o.Distribution - } - if o.IBC != nil { - e.IBC = o.IBC - } - if o.Staking != nil { - e.Staking = o.Staking - } - if o.Stargate != nil { - e.Stargate = o.Stargate - } - if o.Wasm != nil { - e.Wasm = o.Wasm - } - if o.Gov != nil { - e.Gov = o.Gov - } - return e -} - -func (e MessageEncoders) Encode(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Msg, error) { - switch { - case msg.Bank != nil: - return e.Bank(contractAddr, msg.Bank) - case msg.Custom != nil: - return e.Custom(contractAddr, msg.Custom) - case msg.Distribution != nil: - return e.Distribution(contractAddr, msg.Distribution) - case msg.IBC != nil: - return e.IBC(ctx, contractAddr, contractIBCPortID, msg.IBC) - case msg.Staking != nil: - return e.Staking(contractAddr, msg.Staking) - case msg.Stargate != nil: - return e.Stargate(contractAddr, msg.Stargate) - case msg.Wasm != nil: - return e.Wasm(contractAddr, msg.Wasm) - case msg.Gov != nil: - return EncodeGovMsg(contractAddr, msg.Gov) - } - return nil, errorsmod.Wrap(types.ErrUnknownMsg, "unknown variant of Wasm") -} - -func EncodeBankMsg(sender sdk.AccAddress, msg *wasmvmtypes.BankMsg) ([]sdk.Msg, error) { - if msg.Send == nil { - return nil, errorsmod.Wrap(types.ErrUnknownMsg, "unknown variant of Bank") - } - if len(msg.Send.Amount) == 0 { - return nil, nil - } - toSend, err := ConvertWasmCoinsToSdkCoins(msg.Send.Amount) - if err != nil { - return nil, err - } - sdkMsg := banktypes.MsgSend{ - FromAddress: sender.String(), - ToAddress: msg.Send.ToAddress, - Amount: toSend, - } - return []sdk.Msg{&sdkMsg}, nil -} - -func NoCustomMsg(_ sdk.AccAddress, _ json.RawMessage) ([]sdk.Msg, error) { - return nil, errorsmod.Wrap(types.ErrUnknownMsg, "custom variant not supported") -} - -func EncodeDistributionMsg(sender sdk.AccAddress, msg *wasmvmtypes.DistributionMsg) ([]sdk.Msg, error) { - switch { - case msg.SetWithdrawAddress != nil: - setMsg := distributiontypes.MsgSetWithdrawAddress{ - DelegatorAddress: sender.String(), - WithdrawAddress: msg.SetWithdrawAddress.Address, - } - return []sdk.Msg{&setMsg}, nil - case msg.WithdrawDelegatorReward != nil: - withdrawMsg := distributiontypes.MsgWithdrawDelegatorReward{ - DelegatorAddress: sender.String(), - ValidatorAddress: msg.WithdrawDelegatorReward.Validator, - } - return []sdk.Msg{&withdrawMsg}, nil - default: - return nil, errorsmod.Wrap(types.ErrUnknownMsg, "unknown variant of Distribution") - } -} - -func EncodeStakingMsg(sender sdk.AccAddress, msg *wasmvmtypes.StakingMsg) ([]sdk.Msg, error) { - switch { - case msg.Delegate != nil: - coin, err := ConvertWasmCoinToSdkCoin(msg.Delegate.Amount) - if err != nil { - return nil, err - } - sdkMsg := stakingtypes.MsgDelegate{ - DelegatorAddress: sender.String(), - ValidatorAddress: msg.Delegate.Validator, - Amount: coin, - } - return []sdk.Msg{&sdkMsg}, nil - - case msg.Redelegate != nil: - coin, err := ConvertWasmCoinToSdkCoin(msg.Redelegate.Amount) - if err != nil { - return nil, err - } - sdkMsg := stakingtypes.MsgBeginRedelegate{ - DelegatorAddress: sender.String(), - ValidatorSrcAddress: msg.Redelegate.SrcValidator, - ValidatorDstAddress: msg.Redelegate.DstValidator, - Amount: coin, - } - return []sdk.Msg{&sdkMsg}, nil - case msg.Undelegate != nil: - coin, err := ConvertWasmCoinToSdkCoin(msg.Undelegate.Amount) - if err != nil { - return nil, err - } - sdkMsg := stakingtypes.MsgUndelegate{ - DelegatorAddress: sender.String(), - ValidatorAddress: msg.Undelegate.Validator, - Amount: coin, - } - return []sdk.Msg{&sdkMsg}, nil - default: - return nil, errorsmod.Wrap(types.ErrUnknownMsg, "unknown variant of Staking") - } -} - -func EncodeStargateMsg(unpacker codectypes.AnyUnpacker) StargateEncoder { - return func(sender sdk.AccAddress, msg *wasmvmtypes.StargateMsg) ([]sdk.Msg, error) { - codecAny := codectypes.Any{ - TypeUrl: msg.TypeURL, - Value: msg.Value, - } - var sdkMsg sdk.Msg - if err := unpacker.UnpackAny(&codecAny, &sdkMsg); err != nil { - return nil, errorsmod.Wrap(types.ErrInvalidMsg, fmt.Sprintf("Cannot unpack proto message with type URL: %s", msg.TypeURL)) - } - if err := codectypes.UnpackInterfaces(sdkMsg, unpacker); err != nil { - return nil, errorsmod.Wrap(types.ErrInvalidMsg, fmt.Sprintf("UnpackInterfaces inside msg: %s", err)) - } - return []sdk.Msg{sdkMsg}, nil - } -} - -func EncodeWasmMsg(sender sdk.AccAddress, msg *wasmvmtypes.WasmMsg) ([]sdk.Msg, error) { - switch { - case msg.Execute != nil: - coins, err := ConvertWasmCoinsToSdkCoins(msg.Execute.Funds) - if err != nil { - return nil, err - } - - sdkMsg := types.MsgExecuteContract{ - Sender: sender.String(), - Contract: msg.Execute.ContractAddr, - Msg: msg.Execute.Msg, - Funds: coins, - } - return []sdk.Msg{&sdkMsg}, nil - case msg.Instantiate != nil: - coins, err := ConvertWasmCoinsToSdkCoins(msg.Instantiate.Funds) - if err != nil { - return nil, err - } - - sdkMsg := types.MsgInstantiateContract{ - Sender: sender.String(), - CodeID: msg.Instantiate.CodeID, - Label: msg.Instantiate.Label, - Msg: msg.Instantiate.Msg, - Admin: msg.Instantiate.Admin, - Funds: coins, - } - return []sdk.Msg{&sdkMsg}, nil - case msg.Instantiate2 != nil: - coins, err := ConvertWasmCoinsToSdkCoins(msg.Instantiate2.Funds) - if err != nil { - return nil, err - } - - sdkMsg := types.MsgInstantiateContract2{ - Sender: sender.String(), - Admin: msg.Instantiate2.Admin, - CodeID: msg.Instantiate2.CodeID, - Label: msg.Instantiate2.Label, - Msg: msg.Instantiate2.Msg, - Funds: coins, - Salt: msg.Instantiate2.Salt, - // FixMsg is discouraged, see: https://medium.com/cosmwasm/dev-note-3-limitations-of-instantiate2-and-how-to-deal-with-them-a3f946874230 - FixMsg: false, - } - return []sdk.Msg{&sdkMsg}, nil - case msg.Migrate != nil: - sdkMsg := types.MsgMigrateContract{ - Sender: sender.String(), - Contract: msg.Migrate.ContractAddr, - CodeID: msg.Migrate.NewCodeID, - Msg: msg.Migrate.Msg, - } - return []sdk.Msg{&sdkMsg}, nil - case msg.ClearAdmin != nil: - sdkMsg := types.MsgClearAdmin{ - Sender: sender.String(), - Contract: msg.ClearAdmin.ContractAddr, - } - return []sdk.Msg{&sdkMsg}, nil - case msg.UpdateAdmin != nil: - sdkMsg := types.MsgUpdateAdmin{ - Sender: sender.String(), - Contract: msg.UpdateAdmin.ContractAddr, - NewAdmin: msg.UpdateAdmin.Admin, - } - return []sdk.Msg{&sdkMsg}, nil - default: - return nil, errorsmod.Wrap(types.ErrUnknownMsg, "unknown variant of Wasm") - } -} - -func EncodeIBCMsg(portSource types.ICS20TransferPortSource) func(ctx sdk.Context, sender sdk.AccAddress, contractIBCPortID string, msg *wasmvmtypes.IBCMsg) ([]sdk.Msg, error) { - return func(ctx sdk.Context, sender sdk.AccAddress, contractIBCPortID string, msg *wasmvmtypes.IBCMsg) ([]sdk.Msg, error) { - switch { - case msg.CloseChannel != nil: - return []sdk.Msg{&channeltypes.MsgChannelCloseInit{ - PortId: PortIDForContract(sender), - ChannelId: msg.CloseChannel.ChannelID, - Signer: sender.String(), - }}, nil - case msg.Transfer != nil: - amount, err := ConvertWasmCoinToSdkCoin(msg.Transfer.Amount) - if err != nil { - return nil, errorsmod.Wrap(err, "amount") - } - msg := &ibctransfertypes.MsgTransfer{ - SourcePort: portSource.GetPort(ctx), - SourceChannel: msg.Transfer.ChannelID, - Token: amount, - Sender: sender.String(), - Receiver: msg.Transfer.ToAddress, - TimeoutHeight: ConvertWasmIBCTimeoutHeightToCosmosHeight(msg.Transfer.Timeout.Block), - TimeoutTimestamp: msg.Transfer.Timeout.Timestamp, - } - return []sdk.Msg{msg}, nil - default: - return nil, errorsmod.Wrap(types.ErrUnknownMsg, "unknown variant of IBC") - } - } -} - -func EncodeGovMsg(sender sdk.AccAddress, msg *wasmvmtypes.GovMsg) ([]sdk.Msg, error) { - switch { - case msg.Vote != nil: - voteOption, err := convertVoteOption(msg.Vote.Vote) - if err != nil { - return nil, errorsmod.Wrap(err, "vote option") - } - m := v1.NewMsgVote(sender, msg.Vote.ProposalId, voteOption, "") - return []sdk.Msg{m}, nil - case msg.VoteWeighted != nil: - opts := make([]*v1.WeightedVoteOption, len(msg.VoteWeighted.Options)) - for i, v := range msg.VoteWeighted.Options { - weight, err := sdk.NewDecFromStr(v.Weight) - if err != nil { - return nil, errorsmod.Wrapf(err, "weight for vote %d", i+1) - } - voteOption, err := convertVoteOption(v.Option) - if err != nil { - return nil, errorsmod.Wrap(err, "vote option") - } - opts[i] = &v1.WeightedVoteOption{Option: voteOption, Weight: weight.String()} - } - m := v1.NewMsgVoteWeighted(sender, msg.VoteWeighted.ProposalId, opts, "") - return []sdk.Msg{m}, nil - - default: - return nil, types.ErrUnknownMsg.Wrap("unknown variant of gov") - } -} - -func convertVoteOption(s interface{}) (v1.VoteOption, error) { - var option v1.VoteOption - switch s { - case wasmvmtypes.Yes: - option = v1.OptionYes - case wasmvmtypes.No: - option = v1.OptionNo - case wasmvmtypes.NoWithVeto: - option = v1.OptionNoWithVeto - case wasmvmtypes.Abstain: - option = v1.OptionAbstain - default: - return v1.OptionEmpty, types.ErrInvalid - } - return option, nil -} - -// ConvertWasmIBCTimeoutHeightToCosmosHeight converts a wasmvm type ibc timeout height to ibc module type height -func ConvertWasmIBCTimeoutHeightToCosmosHeight(ibcTimeoutBlock *wasmvmtypes.IBCTimeoutBlock) ibcclienttypes.Height { - if ibcTimeoutBlock == nil { - return ibcclienttypes.NewHeight(0, 0) - } - return ibcclienttypes.NewHeight(ibcTimeoutBlock.Revision, ibcTimeoutBlock.Height) -} - -// ConvertWasmCoinsToSdkCoins converts the wasm vm type coins to sdk type coins -func ConvertWasmCoinsToSdkCoins(coins []wasmvmtypes.Coin) (sdk.Coins, error) { - var toSend sdk.Coins - for _, coin := range coins { - c, err := ConvertWasmCoinToSdkCoin(coin) - if err != nil { - return nil, err - } - toSend = toSend.Add(c) - } - return toSend.Sort(), nil -} - -// ConvertWasmCoinToSdkCoin converts a wasm vm type coin to sdk type coin -func ConvertWasmCoinToSdkCoin(coin wasmvmtypes.Coin) (sdk.Coin, error) { - amount, ok := sdk.NewIntFromString(coin.Amount) - if !ok { - return sdk.Coin{}, errorsmod.Wrap(sdkerrors.ErrInvalidCoins, coin.Amount+coin.Denom) - } - r := sdk.Coin{ - Denom: coin.Denom, - Amount: amount, - } - return r, r.Validate() -} diff --git a/x/wasm/keeper/handler_plugin_encoders_test.go b/x/wasm/keeper/handler_plugin_encoders_test.go deleted file mode 100644 index 9d19721..0000000 --- a/x/wasm/keeper/handler_plugin_encoders_test.go +++ /dev/null @@ -1,933 +0,0 @@ -package keeper - -import ( - "testing" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/gogoproto/proto" - ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestEncoding(t *testing.T) { - var ( - addr1 = RandomAccountAddress(t) - addr2 = RandomAccountAddress(t) - addr3 = RandomAccountAddress(t) - invalidAddr = "xrnd1d02kd90n38qvr3qb9qof83fn2d2" - ) - valAddr := make(sdk.ValAddress, types.SDKAddrLen) - valAddr[0] = 12 - valAddr2 := make(sdk.ValAddress, types.SDKAddrLen) - valAddr2[1] = 123 - - jsonMsg := types.RawContractMessage(`{"foo": 123}`) - - bankMsg := &banktypes.MsgSend{ - FromAddress: addr2.String(), - ToAddress: addr1.String(), - Amount: sdk.Coins{ - sdk.NewInt64Coin("uatom", 12345), - sdk.NewInt64Coin("utgd", 54321), - }, - } - bankMsgBin, err := proto.Marshal(bankMsg) - require.NoError(t, err) - - content, err := codectypes.NewAnyWithValue(types.StoreCodeProposalFixture()) - require.NoError(t, err) - - proposalMsg := &v1beta1.MsgSubmitProposal{ - Proposer: addr1.String(), - InitialDeposit: sdk.NewCoins(sdk.NewInt64Coin("uatom", 12345)), - Content: content, - } - proposalMsgBin, err := proto.Marshal(proposalMsg) - require.NoError(t, err) - - cases := map[string]struct { - sender sdk.AccAddress - srcMsg wasmvmtypes.CosmosMsg - srcContractIBCPort string - transferPortSource types.ICS20TransferPortSource - // set if valid - output []sdk.Msg - // set if expect mapping fails - expError bool - // set if sdk validate basic should fail - expInvalid bool - }{ - "simple send": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: addr2.String(), - Amount: []wasmvmtypes.Coin{ - { - Denom: "uatom", - Amount: "12345", - }, - { - Denom: "usdt", - Amount: "54321", - }, - }, - }, - }, - }, - output: []sdk.Msg{ - &banktypes.MsgSend{ - FromAddress: addr1.String(), - ToAddress: addr2.String(), - Amount: sdk.Coins{ - sdk.NewInt64Coin("uatom", 12345), - sdk.NewInt64Coin("usdt", 54321), - }, - }, - }, - }, - "invalid send amount": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: addr2.String(), - Amount: []wasmvmtypes.Coin{ - { - Denom: "uatom", - Amount: "123.456", - }, - }, - }, - }, - }, - expError: true, - }, - "invalid address": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: invalidAddr, - Amount: []wasmvmtypes.Coin{ - { - Denom: "uatom", - Amount: "7890", - }, - }, - }, - }, - }, - expError: false, // addresses are checked in the handler - expInvalid: true, - output: []sdk.Msg{ - &banktypes.MsgSend{ - FromAddress: addr1.String(), - ToAddress: invalidAddr, - Amount: sdk.Coins{ - sdk.NewInt64Coin("uatom", 7890), - }, - }, - }, - }, - "wasm execute": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - Execute: &wasmvmtypes.ExecuteMsg{ - ContractAddr: addr2.String(), - Msg: jsonMsg, - Funds: []wasmvmtypes.Coin{ - wasmvmtypes.NewCoin(12, "eth"), - }, - }, - }, - }, - output: []sdk.Msg{ - &types.MsgExecuteContract{ - Sender: addr1.String(), - Contract: addr2.String(), - Msg: jsonMsg, - Funds: sdk.NewCoins(sdk.NewInt64Coin("eth", 12)), - }, - }, - }, - "wasm instantiate": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - Instantiate: &wasmvmtypes.InstantiateMsg{ - CodeID: 7, - Msg: jsonMsg, - Funds: []wasmvmtypes.Coin{ - wasmvmtypes.NewCoin(123, "eth"), - }, - Label: "myLabel", - Admin: addr2.String(), - }, - }, - }, - output: []sdk.Msg{ - &types.MsgInstantiateContract{ - Sender: addr1.String(), - CodeID: 7, - Label: "myLabel", - Msg: jsonMsg, - Funds: sdk.NewCoins(sdk.NewInt64Coin("eth", 123)), - Admin: addr2.String(), - }, - }, - }, - "wasm instantiate2": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - Instantiate2: &wasmvmtypes.Instantiate2Msg{ - CodeID: 7, - Msg: jsonMsg, - Funds: []wasmvmtypes.Coin{ - wasmvmtypes.NewCoin(123, "eth"), - }, - Label: "myLabel", - Admin: addr2.String(), - Salt: []byte("mySalt"), - }, - }, - }, - output: []sdk.Msg{ - &types.MsgInstantiateContract2{ - Sender: addr1.String(), - Admin: addr2.String(), - CodeID: 7, - Label: "myLabel", - Msg: jsonMsg, - Funds: sdk.NewCoins(sdk.NewInt64Coin("eth", 123)), - Salt: []byte("mySalt"), - FixMsg: false, - }, - }, - }, - "wasm migrate": { - sender: addr2, - srcMsg: wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - Migrate: &wasmvmtypes.MigrateMsg{ - ContractAddr: addr1.String(), - NewCodeID: 12, - Msg: jsonMsg, - }, - }, - }, - output: []sdk.Msg{ - &types.MsgMigrateContract{ - Sender: addr2.String(), - Contract: addr1.String(), - CodeID: 12, - Msg: jsonMsg, - }, - }, - }, - "wasm update admin": { - sender: addr2, - srcMsg: wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - UpdateAdmin: &wasmvmtypes.UpdateAdminMsg{ - ContractAddr: addr1.String(), - Admin: addr3.String(), - }, - }, - }, - output: []sdk.Msg{ - &types.MsgUpdateAdmin{ - Sender: addr2.String(), - Contract: addr1.String(), - NewAdmin: addr3.String(), - }, - }, - }, - "wasm clear admin": { - sender: addr2, - srcMsg: wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - ClearAdmin: &wasmvmtypes.ClearAdminMsg{ - ContractAddr: addr1.String(), - }, - }, - }, - output: []sdk.Msg{ - &types.MsgClearAdmin{ - Sender: addr2.String(), - Contract: addr1.String(), - }, - }, - }, - "staking delegate": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Staking: &wasmvmtypes.StakingMsg{ - Delegate: &wasmvmtypes.DelegateMsg{ - Validator: valAddr.String(), - Amount: wasmvmtypes.NewCoin(777, "stake"), - }, - }, - }, - output: []sdk.Msg{ - &stakingtypes.MsgDelegate{ - DelegatorAddress: addr1.String(), - ValidatorAddress: valAddr.String(), - Amount: sdk.NewInt64Coin("stake", 777), - }, - }, - }, - "staking delegate to non-validator": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Staking: &wasmvmtypes.StakingMsg{ - Delegate: &wasmvmtypes.DelegateMsg{ - Validator: addr2.String(), - Amount: wasmvmtypes.NewCoin(777, "stake"), - }, - }, - }, - expError: false, // fails in the handler - expInvalid: true, - output: []sdk.Msg{ - &stakingtypes.MsgDelegate{ - DelegatorAddress: addr1.String(), - ValidatorAddress: addr2.String(), - Amount: sdk.NewInt64Coin("stake", 777), - }, - }, - }, - "staking undelegate": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Staking: &wasmvmtypes.StakingMsg{ - Undelegate: &wasmvmtypes.UndelegateMsg{ - Validator: valAddr.String(), - Amount: wasmvmtypes.NewCoin(555, "stake"), - }, - }, - }, - output: []sdk.Msg{ - &stakingtypes.MsgUndelegate{ - DelegatorAddress: addr1.String(), - ValidatorAddress: valAddr.String(), - Amount: sdk.NewInt64Coin("stake", 555), - }, - }, - }, - "staking redelegate": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Staking: &wasmvmtypes.StakingMsg{ - Redelegate: &wasmvmtypes.RedelegateMsg{ - SrcValidator: valAddr.String(), - DstValidator: valAddr2.String(), - Amount: wasmvmtypes.NewCoin(222, "stake"), - }, - }, - }, - output: []sdk.Msg{ - &stakingtypes.MsgBeginRedelegate{ - DelegatorAddress: addr1.String(), - ValidatorSrcAddress: valAddr.String(), - ValidatorDstAddress: valAddr2.String(), - Amount: sdk.NewInt64Coin("stake", 222), - }, - }, - }, - "staking withdraw (explicit recipient)": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Distribution: &wasmvmtypes.DistributionMsg{ - WithdrawDelegatorReward: &wasmvmtypes.WithdrawDelegatorRewardMsg{ - Validator: valAddr2.String(), - }, - }, - }, - output: []sdk.Msg{ - &distributiontypes.MsgWithdrawDelegatorReward{ - DelegatorAddress: addr1.String(), - ValidatorAddress: valAddr2.String(), - }, - }, - }, - "staking set withdraw address": { - sender: addr1, - srcMsg: wasmvmtypes.CosmosMsg{ - Distribution: &wasmvmtypes.DistributionMsg{ - SetWithdrawAddress: &wasmvmtypes.SetWithdrawAddressMsg{ - Address: addr2.String(), - }, - }, - }, - output: []sdk.Msg{ - &distributiontypes.MsgSetWithdrawAddress{ - DelegatorAddress: addr1.String(), - WithdrawAddress: addr2.String(), - }, - }, - }, - "stargate encoded bank msg": { - sender: addr2, - srcMsg: wasmvmtypes.CosmosMsg{ - Stargate: &wasmvmtypes.StargateMsg{ - TypeURL: "/cosmos.bank.v1beta1.MsgSend", - Value: bankMsgBin, - }, - }, - output: []sdk.Msg{bankMsg}, - }, - "stargate encoded msg with any type": { - sender: addr2, - srcMsg: wasmvmtypes.CosmosMsg{ - Stargate: &wasmvmtypes.StargateMsg{ - TypeURL: "/cosmos.gov.v1beta1.MsgSubmitProposal", - Value: proposalMsgBin, - }, - }, - output: []sdk.Msg{proposalMsg}, - }, - "stargate encoded invalid typeUrl": { - sender: addr2, - srcMsg: wasmvmtypes.CosmosMsg{ - Stargate: &wasmvmtypes.StargateMsg{ - TypeURL: "/cosmos.bank.v2.MsgSend", - Value: bankMsgBin, - }, - }, - expError: true, - }, - "IBC transfer with block timeout": { - sender: addr1, - srcContractIBCPort: "myIBCPort", - srcMsg: wasmvmtypes.CosmosMsg{ - IBC: &wasmvmtypes.IBCMsg{ - Transfer: &wasmvmtypes.TransferMsg{ - ChannelID: "myChanID", - ToAddress: addr2.String(), - Amount: wasmvmtypes.Coin{ - Denom: "ALX", - Amount: "1", - }, - Timeout: wasmvmtypes.IBCTimeout{ - Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}, - }, - }, - }, - }, - transferPortSource: wasmtesting.MockIBCTransferKeeper{GetPortFn: func(ctx sdk.Context) string { - return "myTransferPort" - }}, - output: []sdk.Msg{ - &ibctransfertypes.MsgTransfer{ - SourcePort: "myTransferPort", - SourceChannel: "myChanID", - Token: sdk.Coin{ - Denom: "ALX", - Amount: sdk.NewInt(1), - }, - Sender: addr1.String(), - Receiver: addr2.String(), - TimeoutHeight: clienttypes.Height{RevisionNumber: 1, RevisionHeight: 2}, - }, - }, - }, - "IBC transfer with time timeout": { - sender: addr1, - srcContractIBCPort: "myIBCPort", - srcMsg: wasmvmtypes.CosmosMsg{ - IBC: &wasmvmtypes.IBCMsg{ - Transfer: &wasmvmtypes.TransferMsg{ - ChannelID: "myChanID", - ToAddress: addr2.String(), - Amount: wasmvmtypes.Coin{ - Denom: "ALX", - Amount: "1", - }, - Timeout: wasmvmtypes.IBCTimeout{Timestamp: 100}, - }, - }, - }, - transferPortSource: wasmtesting.MockIBCTransferKeeper{GetPortFn: func(ctx sdk.Context) string { - return "transfer" - }}, - output: []sdk.Msg{ - &ibctransfertypes.MsgTransfer{ - SourcePort: "transfer", - SourceChannel: "myChanID", - Token: sdk.Coin{ - Denom: "ALX", - Amount: sdk.NewInt(1), - }, - Sender: addr1.String(), - Receiver: addr2.String(), - TimeoutTimestamp: 100, - }, - }, - }, - "IBC transfer with time and height timeout": { - sender: addr1, - srcContractIBCPort: "myIBCPort", - srcMsg: wasmvmtypes.CosmosMsg{ - IBC: &wasmvmtypes.IBCMsg{ - Transfer: &wasmvmtypes.TransferMsg{ - ChannelID: "myChanID", - ToAddress: addr2.String(), - Amount: wasmvmtypes.Coin{ - Denom: "ALX", - Amount: "1", - }, - Timeout: wasmvmtypes.IBCTimeout{Timestamp: 100, Block: &wasmvmtypes.IBCTimeoutBlock{Height: 1, Revision: 2}}, - }, - }, - }, - transferPortSource: wasmtesting.MockIBCTransferKeeper{GetPortFn: func(ctx sdk.Context) string { - return "transfer" - }}, - output: []sdk.Msg{ - &ibctransfertypes.MsgTransfer{ - SourcePort: "transfer", - SourceChannel: "myChanID", - Token: sdk.Coin{ - Denom: "ALX", - Amount: sdk.NewInt(1), - }, - Sender: addr1.String(), - Receiver: addr2.String(), - TimeoutTimestamp: 100, - TimeoutHeight: clienttypes.NewHeight(2, 1), - }, - }, - }, - "IBC close channel": { - sender: addr1, - srcContractIBCPort: "myIBCPort", - srcMsg: wasmvmtypes.CosmosMsg{ - IBC: &wasmvmtypes.IBCMsg{ - CloseChannel: &wasmvmtypes.CloseChannelMsg{ - ChannelID: "channel-1", - }, - }, - }, - output: []sdk.Msg{ - &channeltypes.MsgChannelCloseInit{ - PortId: "wasm." + addr1.String(), - ChannelId: "channel-1", - Signer: addr1.String(), - }, - }, - }, - } - encodingConfig := MakeEncodingConfig(t) - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - var ctx sdk.Context - encoder := DefaultEncoders(encodingConfig.Marshaler, tc.transferPortSource) - res, err := encoder.Encode(ctx, tc.sender, tc.srcContractIBCPort, tc.srcMsg) - if tc.expError { - assert.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tc.output, res) - - // and valid sdk message - for _, v := range res { - gotErr := v.ValidateBasic() - if tc.expInvalid { - assert.Error(t, gotErr) - } else { - assert.NoError(t, gotErr) - } - } - }) - } -} - -func TestEncodeGovMsg(t *testing.T) { - myAddr := RandomAccountAddress(t) - - cases := map[string]struct { - sender sdk.AccAddress - srcMsg wasmvmtypes.CosmosMsg - transferPortSource types.ICS20TransferPortSource - // set if valid - output []sdk.Msg - // set if expect mapping fails - expError bool - // set if sdk validate basic should fail - expInvalid bool - }{ - "Gov vote: yes": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - Vote: &wasmvmtypes.VoteMsg{ProposalId: 1, Vote: wasmvmtypes.Yes}, - }, - }, - output: []sdk.Msg{ - &v1.MsgVote{ - ProposalId: 1, - Voter: myAddr.String(), - Option: v1.OptionYes, - }, - }, - }, - "Gov vote: No": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - Vote: &wasmvmtypes.VoteMsg{ProposalId: 1, Vote: wasmvmtypes.No}, - }, - }, - output: []sdk.Msg{ - &v1.MsgVote{ - ProposalId: 1, - Voter: myAddr.String(), - Option: v1.OptionNo, - }, - }, - }, - "Gov vote: Abstain": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - Vote: &wasmvmtypes.VoteMsg{ProposalId: 10, Vote: wasmvmtypes.Abstain}, - }, - }, - output: []sdk.Msg{ - &v1.MsgVote{ - ProposalId: 10, - Voter: myAddr.String(), - Option: v1.OptionAbstain, - }, - }, - }, - "Gov vote: No with veto": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - Vote: &wasmvmtypes.VoteMsg{ProposalId: 1, Vote: wasmvmtypes.NoWithVeto}, - }, - }, - output: []sdk.Msg{ - &v1.MsgVote{ - ProposalId: 1, - Voter: myAddr.String(), - Option: v1.OptionNoWithVeto, - }, - }, - }, - "Gov vote: unset option": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - Vote: &wasmvmtypes.VoteMsg{ProposalId: 1}, - }, - }, - expError: true, - }, - "Gov weighted vote: single vote": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ - ProposalId: 1, - Options: []wasmvmtypes.WeightedVoteOption{ - {Option: wasmvmtypes.Yes, Weight: "1"}, - }, - }, - }, - }, - output: []sdk.Msg{ - &v1.MsgVoteWeighted{ - ProposalId: 1, - Voter: myAddr.String(), - Options: []*v1.WeightedVoteOption{ - {Option: v1.OptionYes, Weight: sdk.NewDec(1).String()}, - }, - }, - }, - }, - "Gov weighted vote: splitted": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ - ProposalId: 1, - Options: []wasmvmtypes.WeightedVoteOption{ - {Option: wasmvmtypes.Yes, Weight: "0.23"}, - {Option: wasmvmtypes.No, Weight: "0.24"}, - {Option: wasmvmtypes.Abstain, Weight: "0.26"}, - {Option: wasmvmtypes.NoWithVeto, Weight: "0.27"}, - }, - }, - }, - }, - output: []sdk.Msg{ - &v1.MsgVoteWeighted{ - ProposalId: 1, - Voter: myAddr.String(), - Options: []*v1.WeightedVoteOption{ - {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(23, 2).String()}, - {Option: v1.OptionNo, Weight: sdk.NewDecWithPrec(24, 2).String()}, - {Option: v1.OptionAbstain, Weight: sdk.NewDecWithPrec(26, 2).String()}, - {Option: v1.OptionNoWithVeto, Weight: sdk.NewDecWithPrec(27, 2).String()}, - }, - }, - }, - }, - "Gov weighted vote: duplicate option": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ - ProposalId: 1, - Options: []wasmvmtypes.WeightedVoteOption{ - {Option: wasmvmtypes.Yes, Weight: "0.5"}, - {Option: wasmvmtypes.Yes, Weight: "0.5"}, - }, - }, - }, - }, - output: []sdk.Msg{ - &v1.MsgVoteWeighted{ - ProposalId: 1, - Voter: myAddr.String(), - Options: []*v1.WeightedVoteOption{ - {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(5, 1).String()}, - {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(5, 1).String()}, - }, - }, - }, - expInvalid: true, - }, - "Gov weighted vote: weight sum exceeds 1": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ - ProposalId: 1, - Options: []wasmvmtypes.WeightedVoteOption{ - {Option: wasmvmtypes.Yes, Weight: "0.51"}, - {Option: wasmvmtypes.No, Weight: "0.5"}, - }, - }, - }, - }, - output: []sdk.Msg{ - &v1.MsgVoteWeighted{ - ProposalId: 1, - Voter: myAddr.String(), - Options: []*v1.WeightedVoteOption{ - {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(51, 2).String()}, - {Option: v1.OptionNo, Weight: sdk.NewDecWithPrec(5, 1).String()}, - }, - }, - }, - expInvalid: true, - }, - "Gov weighted vote: weight sum less than 1": { - sender: myAddr, - srcMsg: wasmvmtypes.CosmosMsg{ - Gov: &wasmvmtypes.GovMsg{ - VoteWeighted: &wasmvmtypes.VoteWeightedMsg{ - ProposalId: 1, - Options: []wasmvmtypes.WeightedVoteOption{ - {Option: wasmvmtypes.Yes, Weight: "0.49"}, - {Option: wasmvmtypes.No, Weight: "0.5"}, - }, - }, - }, - }, - output: []sdk.Msg{ - &v1.MsgVoteWeighted{ - ProposalId: 1, - Voter: myAddr.String(), - Options: []*v1.WeightedVoteOption{ - {Option: v1.OptionYes, Weight: sdk.NewDecWithPrec(49, 2).String()}, - {Option: v1.OptionNo, Weight: sdk.NewDecWithPrec(5, 1).String()}, - }, - }, - }, - expInvalid: true, - }, - } - encodingConfig := MakeEncodingConfig(t) - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - var ctx sdk.Context - encoder := DefaultEncoders(encodingConfig.Marshaler, tc.transferPortSource) - res, gotEncErr := encoder.Encode(ctx, tc.sender, "myIBCPort", tc.srcMsg) - if tc.expError { - assert.Error(t, gotEncErr) - return - } - require.NoError(t, gotEncErr) - assert.Equal(t, tc.output, res) - - // and valid sdk message - for _, v := range res { - gotErr := v.ValidateBasic() - if tc.expInvalid { - assert.Error(t, gotErr) - } else { - assert.NoError(t, gotErr) - } - } - }) - } -} - -func TestConvertWasmCoinToSdkCoin(t *testing.T) { - specs := map[string]struct { - src wasmvmtypes.Coin - expErr bool - expVal sdk.Coin - }{ - "all good": { - src: wasmvmtypes.Coin{ - Denom: "foo", - Amount: "1", - }, - expVal: sdk.NewCoin("foo", sdk.NewIntFromUint64(1)), - }, - "negative amount": { - src: wasmvmtypes.Coin{ - Denom: "foo", - Amount: "-1", - }, - expErr: true, - }, - "denom to short": { - src: wasmvmtypes.Coin{ - Denom: "f", - Amount: "1", - }, - expErr: true, - }, - "invalid demum char": { - src: wasmvmtypes.Coin{ - Denom: "&fff", - Amount: "1", - }, - expErr: true, - }, - "not a number amount": { - src: wasmvmtypes.Coin{ - Denom: "foo", - Amount: "bar", - }, - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - gotVal, gotErr := ConvertWasmCoinToSdkCoin(spec.src) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expVal, gotVal) - }) - } -} - -func TestConvertWasmCoinsToSdkCoins(t *testing.T) { - specs := map[string]struct { - src []wasmvmtypes.Coin - exp sdk.Coins - expErr bool - }{ - "empty": { - src: []wasmvmtypes.Coin{}, - exp: nil, - }, - "single coin": { - src: []wasmvmtypes.Coin{{Denom: "foo", Amount: "1"}}, - exp: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1))), - }, - "multiple coins": { - src: []wasmvmtypes.Coin{ - {Denom: "foo", Amount: "1"}, - {Denom: "bar", Amount: "2"}, - }, - exp: sdk.NewCoins( - sdk.NewCoin("bar", sdk.NewInt(2)), - sdk.NewCoin("foo", sdk.NewInt(1)), - ), - }, - "sorted": { - src: []wasmvmtypes.Coin{ - {Denom: "foo", Amount: "1"}, - {Denom: "other", Amount: "1"}, - {Denom: "bar", Amount: "1"}, - }, - exp: []sdk.Coin{ - sdk.NewCoin("bar", sdk.NewInt(1)), - sdk.NewCoin("foo", sdk.NewInt(1)), - sdk.NewCoin("other", sdk.NewInt(1)), - }, - }, - "zero amounts dropped": { - src: []wasmvmtypes.Coin{ - {Denom: "foo", Amount: "1"}, - {Denom: "bar", Amount: "0"}, - }, - exp: sdk.NewCoins( - sdk.NewCoin("foo", sdk.NewInt(1)), - ), - }, - "duplicate denoms merged": { - src: []wasmvmtypes.Coin{ - {Denom: "foo", Amount: "1"}, - {Denom: "foo", Amount: "1"}, - }, - exp: []sdk.Coin{sdk.NewCoin("foo", sdk.NewInt(2))}, - }, - "duplicate denoms with one 0 amount does not fail": { - src: []wasmvmtypes.Coin{ - {Denom: "foo", Amount: "0"}, - {Denom: "foo", Amount: "1"}, - }, - exp: []sdk.Coin{sdk.NewCoin("foo", sdk.NewInt(1))}, - }, - "empty denom rejected": { - src: []wasmvmtypes.Coin{{Denom: "", Amount: "1"}}, - expErr: true, - }, - "invalid denom rejected": { - src: []wasmvmtypes.Coin{{Denom: "!%&", Amount: "1"}}, - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - gotCoins, gotErr := ConvertWasmCoinsToSdkCoins(spec.src) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.exp, gotCoins) - assert.NoError(t, gotCoins.Validate()) - }) - } -} diff --git a/x/wasm/keeper/handler_plugin_test.go b/x/wasm/keeper/handler_plugin_test.go deleted file mode 100644 index 42cc810..0000000 --- a/x/wasm/keeper/handler_plugin_test.go +++ /dev/null @@ -1,415 +0,0 @@ -package keeper - -import ( - "encoding/json" - "testing" - - errorsmod "cosmossdk.io/errors" - "github.com/cometbft/cometbft/libs/log" - - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestMessageHandlerChainDispatch(t *testing.T) { - capturingHandler, gotMsgs := wasmtesting.NewCapturingMessageHandler() - - alwaysUnknownMsgHandler := &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, nil, types.ErrUnknownMsg - }, - } - - assertNotCalledHandler := &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - t.Fatal("not expected to be called") - return - }, - } - - myMsg := wasmvmtypes.CosmosMsg{Custom: []byte(`{}`)} - specs := map[string]struct { - handlers []Messenger - expErr *errorsmod.Error - expEvents []sdk.Event - }{ - "single handler": { - handlers: []Messenger{capturingHandler}, - }, - "passed to next handler": { - handlers: []Messenger{alwaysUnknownMsgHandler, capturingHandler}, - }, - "stops iteration when handled": { - handlers: []Messenger{capturingHandler, assertNotCalledHandler}, - }, - "stops iteration on handler error": { - handlers: []Messenger{&wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, nil, types.ErrInvalidMsg - }, - }, assertNotCalledHandler}, - expErr: types.ErrInvalidMsg, - }, - "return events when handle": { - handlers: []Messenger{ - &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - _, data, _ = capturingHandler.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) - return []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))}, data, nil - }, - }, - }, - expEvents: []sdk.Event{sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar"))}, - }, - "return error when none can handle": { - handlers: []Messenger{alwaysUnknownMsgHandler}, - expErr: types.ErrUnknownMsg, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - *gotMsgs = make([]wasmvmtypes.CosmosMsg, 0) - - // when - h := MessageHandlerChain{spec.handlers} - gotEvents, gotData, gotErr := h.DispatchMsg(sdk.Context{}, RandomAccountAddress(t), "anyPort", myMsg) - - // then - require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr) - if spec.expErr != nil { - return - } - assert.Equal(t, []wasmvmtypes.CosmosMsg{myMsg}, *gotMsgs) - assert.Equal(t, [][]byte{{1}}, gotData) // {1} is default in capturing handler - assert.Equal(t, spec.expEvents, gotEvents) - }) - } -} - -func TestSDKMessageHandlerDispatch(t *testing.T) { - myEvent := sdk.NewEvent("myEvent", sdk.NewAttribute("foo", "bar")) - const myData = "myData" - myRouterResult := sdk.Result{ - Data: []byte(myData), - Events: sdk.Events{myEvent}.ToABCIEvents(), - } - - var gotMsg []sdk.Msg - capturingMessageRouter := wasmtesting.MessageRouterFunc(func(msg sdk.Msg) baseapp.MsgServiceHandler { - return func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) { - gotMsg = append(gotMsg, msg) - return &myRouterResult, nil - } - }) - noRouteMessageRouter := wasmtesting.MessageRouterFunc(func(msg sdk.Msg) baseapp.MsgServiceHandler { - return nil - }) - myContractAddr := RandomAccountAddress(t) - myContractMessage := wasmvmtypes.CosmosMsg{Custom: []byte("{}")} - - specs := map[string]struct { - srcRoute MessageRouter - srcEncoder CustomEncoder - expErr *errorsmod.Error - expMsgDispatched int - }{ - "all good": { - srcRoute: capturingMessageRouter, - srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - myMsg := types.MsgExecuteContract{ - Sender: myContractAddr.String(), - Contract: RandomBech32AccountAddress(t), - Msg: []byte("{}"), - } - return []sdk.Msg{&myMsg}, nil - }, - expMsgDispatched: 1, - }, - "multiple output msgs": { - srcRoute: capturingMessageRouter, - srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - first := &types.MsgExecuteContract{ - Sender: myContractAddr.String(), - Contract: RandomBech32AccountAddress(t), - Msg: []byte("{}"), - } - second := &types.MsgExecuteContract{ - Sender: myContractAddr.String(), - Contract: RandomBech32AccountAddress(t), - Msg: []byte("{}"), - } - return []sdk.Msg{first, second}, nil - }, - expMsgDispatched: 2, - }, - "invalid sdk message rejected": { - srcRoute: capturingMessageRouter, - srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - invalidMsg := types.MsgExecuteContract{ - Sender: myContractAddr.String(), - Contract: RandomBech32AccountAddress(t), - Msg: []byte("INVALID_JSON"), - } - return []sdk.Msg{&invalidMsg}, nil - }, - expErr: types.ErrInvalid, - }, - "invalid sender rejected": { - srcRoute: capturingMessageRouter, - srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - invalidMsg := types.MsgExecuteContract{ - Sender: RandomBech32AccountAddress(t), - Contract: RandomBech32AccountAddress(t), - Msg: []byte("{}"), - } - return []sdk.Msg{&invalidMsg}, nil - }, - expErr: sdkerrors.ErrUnauthorized, - }, - "unroutable message rejected": { - srcRoute: noRouteMessageRouter, - srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - myMsg := types.MsgExecuteContract{ - Sender: myContractAddr.String(), - Contract: RandomBech32AccountAddress(t), - Msg: []byte("{}"), - } - return []sdk.Msg{&myMsg}, nil - }, - expErr: sdkerrors.ErrUnknownRequest, - }, - "encoding error passed": { - srcRoute: capturingMessageRouter, - srcEncoder: func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - myErr := types.ErrUnpinContractFailed // any error that is not used - return nil, myErr - }, - expErr: types.ErrUnpinContractFailed, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - gotMsg = make([]sdk.Msg, 0) - - // when - ctx := sdk.Context{} - h := NewSDKMessageHandler(spec.srcRoute, MessageEncoders{Custom: spec.srcEncoder}) - gotEvents, gotData, gotErr := h.DispatchMsg(ctx, myContractAddr, "myPort", myContractMessage) - - // then - require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr) - if spec.expErr != nil { - require.Len(t, gotMsg, 0) - return - } - assert.Len(t, gotMsg, spec.expMsgDispatched) - for i := 0; i < spec.expMsgDispatched; i++ { - assert.Equal(t, myEvent, gotEvents[i]) - assert.Equal(t, []byte(myData), gotData[i]) - } - }) - } -} - -func TestIBCRawPacketHandler(t *testing.T) { - ibcPort := "contractsIBCPort" - ctx := sdk.Context{}.WithLogger(log.TestingLogger()) - - type CapturedPacket struct { - sourcePort string - sourceChannel string - timeoutHeight clienttypes.Height - timeoutTimestamp uint64 - data []byte - } - var capturedPacket *CapturedPacket - - chanKeeper := &wasmtesting.MockChannelKeeper{ - GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channeltypes.Channel, bool) { - return channeltypes.Channel{ - Counterparty: channeltypes.NewCounterparty( - "other-port", - "other-channel-1", - ), - }, true - }, - SendPacketFn: func(ctx sdk.Context, channelCap *capabilitytypes.Capability, sourcePort string, sourceChannel string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, data []byte) (uint64, error) { - capturedPacket = &CapturedPacket{ - sourcePort: sourcePort, - sourceChannel: sourceChannel, - timeoutHeight: timeoutHeight, - timeoutTimestamp: timeoutTimestamp, - data: data, - } - return 1, nil - }, - } - capKeeper := &wasmtesting.MockCapabilityKeeper{ - GetCapabilityFn: func(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool) { - return &capabilitytypes.Capability{}, true - }, - } - - specs := map[string]struct { - srcMsg wasmvmtypes.SendPacketMsg - chanKeeper types.ChannelKeeper - capKeeper types.CapabilityKeeper - expPacketSent *CapturedPacket - expErr *errorsmod.Error - }{ - "all good": { - srcMsg: wasmvmtypes.SendPacketMsg{ - ChannelID: "channel-1", - Data: []byte("myData"), - Timeout: wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}}, - }, - chanKeeper: chanKeeper, - capKeeper: capKeeper, - expPacketSent: &CapturedPacket{ - sourcePort: ibcPort, - sourceChannel: "channel-1", - timeoutHeight: clienttypes.Height{RevisionNumber: 1, RevisionHeight: 2}, - data: []byte("myData"), - }, - }, - "capability not found returns error": { - srcMsg: wasmvmtypes.SendPacketMsg{ - ChannelID: "channel-1", - Data: []byte("myData"), - Timeout: wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 2}}, - }, - chanKeeper: chanKeeper, - capKeeper: wasmtesting.MockCapabilityKeeper{ - GetCapabilityFn: func(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool) { - return nil, false - }, - }, - expErr: channeltypes.ErrChannelCapabilityNotFound, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - capturedPacket = nil - // when - h := NewIBCRawPacketHandler(spec.chanKeeper, spec.capKeeper) - evts, data, gotErr := h.DispatchMsg(ctx, RandomAccountAddress(t), ibcPort, wasmvmtypes.CosmosMsg{IBC: &wasmvmtypes.IBCMsg{SendPacket: &spec.srcMsg}}) - // then - require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr) - if spec.expErr != nil { - return - } - - assert.Nil(t, evts) - require.NotNil(t, data) - - expMsg := types.MsgIBCSendResponse{Sequence: 1} - - actualMsg := types.MsgIBCSendResponse{} - err := actualMsg.Unmarshal(data[0]) - require.NoError(t, err) - - assert.Equal(t, expMsg, actualMsg) - assert.Equal(t, spec.expPacketSent, capturedPacket) - }) - } -} - -func TestBurnCoinMessageHandlerIntegration(t *testing.T) { - // testing via full keeper setup so that we are confident the - // module permissions are set correct and no other handler - // picks the message in the default handler chain - ctx, keepers := CreateDefaultTestInput(t) - // set some supply - keepers.Faucet.NewFundedRandomAccount(ctx, sdk.NewCoin("denom", sdk.NewInt(10_000_000))) - k := keepers.WasmKeeper - - example := InstantiateHackatomExampleContract(t, ctx, keepers) // with deposit of 100 stake - - before, err := keepers.BankKeeper.TotalSupply(sdk.WrapSDKContext(ctx), &banktypes.QueryTotalSupplyRequest{}) - require.NoError(t, err) - - specs := map[string]struct { - msg wasmvmtypes.BurnMsg - expErr bool - }{ - "all good": { - msg: wasmvmtypes.BurnMsg{ - Amount: wasmvmtypes.Coins{{ - Denom: "denom", - Amount: "100", - }}, - }, - }, - "not enough funds in contract": { - msg: wasmvmtypes.BurnMsg{ - Amount: wasmvmtypes.Coins{{ - Denom: "denom", - Amount: "101", - }}, - }, - expErr: true, - }, - "zero amount rejected": { - msg: wasmvmtypes.BurnMsg{ - Amount: wasmvmtypes.Coins{{ - Denom: "denom", - Amount: "0", - }}, - }, - expErr: true, - }, - "unknown denom - insufficient funds": { - msg: wasmvmtypes.BurnMsg{ - Amount: wasmvmtypes.Coins{{ - Denom: "unknown", - Amount: "1", - }}, - }, - expErr: true, - }, - } - parentCtx := ctx - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - ctx, _ = parentCtx.CacheContext() - k.wasmVM = &wasmtesting.MockWasmer{ExecuteFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - return &wasmvmtypes.Response{ - Messages: []wasmvmtypes.SubMsg{ - {Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{Burn: &spec.msg}}, ReplyOn: wasmvmtypes.ReplyNever}, - }, - }, 0, nil - }} - - // when - _, err = k.execute(ctx, example.Contract, example.CreatorAddr, nil, nil) - - // then - if spec.expErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - // and total supply reduced by burned amount - after, err := keepers.BankKeeper.TotalSupply(sdk.WrapSDKContext(ctx), &banktypes.QueryTotalSupplyRequest{}) - require.NoError(t, err) - diff := before.Supply.Sub(after.Supply...) - assert.Equal(t, sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(100))), diff) - }) - } - - // test cases: - // not enough money to burn -} diff --git a/x/wasm/keeper/ibc.go b/x/wasm/keeper/ibc.go deleted file mode 100644 index ff94048..0000000 --- a/x/wasm/keeper/ibc.go +++ /dev/null @@ -1,56 +0,0 @@ -package keeper - -import ( - "strings" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - host "github.com/cosmos/ibc-go/v7/modules/core/24-host" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// bindIbcPort will reserve the port. -// returns a string name of the port or error if we cannot bind it. -// this will fail if call twice. -func (k Keeper) bindIbcPort(ctx sdk.Context, portID string) error { - portCap := k.portKeeper.BindPort(ctx, portID) - return k.ClaimCapability(ctx, portCap, host.PortPath(portID)) -} - -// ensureIbcPort is like registerIbcPort, but it checks if we already hold the port -// before calling register, so this is safe to call multiple times. -// Returns success if we already registered or just registered and error if we cannot -// (lack of permissions or someone else has it) -func (k Keeper) ensureIbcPort(ctx sdk.Context, contractAddr sdk.AccAddress) (string, error) { - portID := PortIDForContract(contractAddr) - if _, ok := k.capabilityKeeper.GetCapability(ctx, host.PortPath(portID)); ok { - return portID, nil - } - return portID, k.bindIbcPort(ctx, portID) -} - -const portIDPrefix = "wasm." - -func PortIDForContract(addr sdk.AccAddress) string { - return portIDPrefix + addr.String() -} - -func ContractFromPortID(portID string) (sdk.AccAddress, error) { - if !strings.HasPrefix(portID, portIDPrefix) { - return nil, errorsmod.Wrapf(types.ErrInvalid, "without prefix") - } - return sdk.AccAddressFromBech32(portID[len(portIDPrefix):]) -} - -// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function -func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool { - return k.capabilityKeeper.AuthenticateCapability(ctx, cap, name) -} - -// ClaimCapability allows the transfer module to claim a capability -// that IBC module passes to it -func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { - return k.capabilityKeeper.ClaimCapability(ctx, cap, name) -} diff --git a/x/wasm/keeper/ibc_test.go b/x/wasm/keeper/ibc_test.go deleted file mode 100644 index 063dfb7..0000000 --- a/x/wasm/keeper/ibc_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package keeper - -import ( - "fmt" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - - "github.com/stretchr/testify/require" -) - -func TestDontBindPortNonIBCContract(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - example := InstantiateHackatomExampleContract(t, ctx, keepers) // ensure we bound the port - _, _, err := keepers.IBCKeeper.PortKeeper.LookupModuleByPort(ctx, keepers.WasmKeeper.GetContractInfo(ctx, example.Contract).IBCPortID) - require.Error(t, err) -} - -func TestBindingPortForIBCContractOnInstantiate(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - example := InstantiateIBCReflectContract(t, ctx, keepers) // ensure we bound the port - owner, _, err := keepers.IBCKeeper.PortKeeper.LookupModuleByPort(ctx, keepers.WasmKeeper.GetContractInfo(ctx, example.Contract).IBCPortID) - require.NoError(t, err) - require.Equal(t, "wasm", owner) - - initMsgBz := IBCReflectInitMsg{ - ReflectCodeID: example.ReflectCodeID, - }.GetBytes(t) - - // create a second contract should give yet another portID (and different address) - creator := RandomAccountAddress(t) - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, example.CodeID, creator, nil, initMsgBz, "ibc-reflect-2", nil) - require.NoError(t, err) - require.NotEqual(t, example.Contract, addr) - - portID2 := PortIDForContract(addr) - owner, _, err = keepers.IBCKeeper.PortKeeper.LookupModuleByPort(ctx, portID2) - require.NoError(t, err) - require.Equal(t, "wasm", owner) -} - -func TestContractFromPortID(t *testing.T) { - contractAddr := BuildContractAddressClassic(1, 100) - specs := map[string]struct { - srcPort string - expAddr sdk.AccAddress - expErr bool - }{ - "all good": { - srcPort: fmt.Sprintf("wasm.%s", contractAddr.String()), - expAddr: contractAddr, - }, - "without prefix": { - srcPort: contractAddr.String(), - expErr: true, - }, - "invalid prefix": { - srcPort: fmt.Sprintf("wasmx.%s", contractAddr.String()), - expErr: true, - }, - "without separator char": { - srcPort: fmt.Sprintf("wasm%s", contractAddr.String()), - expErr: true, - }, - "invalid account": { - srcPort: "wasm.foobar", - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - gotAddr, gotErr := ContractFromPortID(spec.srcPort) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expAddr, gotAddr) - }) - } -} diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go deleted file mode 100644 index f5d5c4d..0000000 --- a/x/wasm/keeper/keeper.go +++ /dev/null @@ -1,1211 +0,0 @@ -package keeper - -import ( - "bytes" - "context" - "encoding/binary" - "encoding/hex" - "fmt" - "math" - "reflect" - "strconv" - "strings" - "time" - - errorsmod "cosmossdk.io/errors" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/prefix" - "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - vestingexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" - - "github.com/terpnetwork/terp-core/x/wasm/ioutils" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// contractMemoryLimit is the memory limit of each contract execution (in MiB) -// constant value so all nodes run with the same limit. -const contractMemoryLimit = 32 - -type contextKey int - -const ( - // private type creates an interface key for Context that cannot be accessed by any other package - contextKeyQueryStackSize contextKey = iota -) - -// Option is an extension point to instantiate keeper with non default values -type Option interface { - apply(*Keeper) -} - -// WasmVMQueryHandler is an extension point for custom query handler implementations -type WasmVMQueryHandler interface { - // HandleQuery executes the requested query - HandleQuery(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) -} - -type CoinTransferrer interface { - // TransferCoins sends the coin amounts from the source to the destination with rules applied. - TransferCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error -} - -// AccountPruner handles the balances and data cleanup for accounts that are pruned on contract instantiate. -// This is an extension point to attach custom logic -type AccountPruner interface { - // CleanupExistingAccount handles the cleanup process for balances and data of the given account. The persisted account - // type is already reset to base account at this stage. - // The method returns true when the account address can be reused. Unsupported account types are rejected by returning false - CleanupExistingAccount(ctx sdk.Context, existingAccount authtypes.AccountI) (handled bool, err error) -} - -// WasmVMResponseHandler is an extension point to handles the response data returned by a contract call. -type WasmVMResponseHandler interface { - // Handle processes the data returned by a contract invocation. - Handle( - ctx sdk.Context, - contractAddr sdk.AccAddress, - ibcPort string, - messages []wasmvmtypes.SubMsg, - origRspData []byte, - ) ([]byte, error) -} - -// list of account types that are accepted for wasm contracts. Chains importing wasmd -// can overwrite this list with the WithAcceptedAccountTypesOnContractInstantiation option. -var defaultAcceptedAccountTypes = map[reflect.Type]struct{}{ - reflect.TypeOf(&authtypes.BaseAccount{}): {}, -} - -// Keeper will have a reference to Wasmer with it's own data directory. -type Keeper struct { - storeKey storetypes.StoreKey - cdc codec.Codec - accountKeeper types.AccountKeeper - bank CoinTransferrer - portKeeper types.PortKeeper - capabilityKeeper types.CapabilityKeeper - wasmVM types.WasmerEngine - wasmVMQueryHandler WasmVMQueryHandler - wasmVMResponseHandler WasmVMResponseHandler - messenger Messenger - // queryGasLimit is the max wasmvm gas that can be spent on executing a query with a contract - queryGasLimit uint64 - gasRegister GasRegister - maxQueryStackSize uint32 - acceptedAccountTypes map[reflect.Type]struct{} - accountPruner AccountPruner - // the address capable of executing a MsgUpdateParams message. Typically, this - // should be the x/gov module account. - authority string -} - -func (k Keeper) getUploadAccessConfig(ctx sdk.Context) types.AccessConfig { - return k.GetParams(ctx).CodeUploadAccess -} - -func (k Keeper) getInstantiateAccessConfig(ctx sdk.Context) types.AccessType { - return k.GetParams(ctx).InstantiateDefaultPermission -} - -// GetParams returns the total set of wasm parameters. -func (k Keeper) GetParams(ctx sdk.Context) types.Params { - var params types.Params - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.ParamsKey) - if bz == nil { - return params - } - - k.cdc.MustUnmarshal(bz, ¶ms) - return params -} - -// SetParams sets all wasm parameters. -func (k Keeper) SetParams(ctx sdk.Context, ps types.Params) error { - if err := ps.ValidateBasic(); err != nil { - return err - } - - store := ctx.KVStore(k.storeKey) - bz, err := k.cdc.Marshal(&ps) - if err != nil { - return err - } - store.Set(types.ParamsKey, bz) - - return nil -} - -// GetAuthority returns the x/wasm module's authority. -func (k Keeper) GetAuthority() string { - return k.authority -} - -func (k Keeper) create(ctx sdk.Context, creator sdk.AccAddress, wasmCode []byte, instantiateAccess *types.AccessConfig, authZ AuthorizationPolicy) (codeID uint64, checksum []byte, err error) { - if creator == nil { - return 0, checksum, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "cannot be nil") - } - - // figure out proper instantiate access - defaultAccessConfig := k.getInstantiateAccessConfig(ctx).With(creator) - if instantiateAccess == nil { - instantiateAccess = &defaultAccessConfig - } - chainConfigs := ChainAccessConfigs{ - Instantiate: defaultAccessConfig, - Upload: k.getUploadAccessConfig(ctx), - } - - if !authZ.CanCreateCode(chainConfigs, creator, *instantiateAccess) { - return 0, checksum, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not create code") - } - - if ioutils.IsGzip(wasmCode) { - ctx.GasMeter().ConsumeGas(k.gasRegister.UncompressCosts(len(wasmCode)), "Uncompress gzip bytecode") - wasmCode, err = ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize)) - if err != nil { - return 0, checksum, errorsmod.Wrap(types.ErrCreateFailed, err.Error()) - } - } - - ctx.GasMeter().ConsumeGas(k.gasRegister.CompileCosts(len(wasmCode)), "Compiling wasm bytecode") - checksum, err = k.wasmVM.Create(wasmCode) - if err != nil { - return 0, checksum, errorsmod.Wrap(types.ErrCreateFailed, err.Error()) - } - report, err := k.wasmVM.AnalyzeCode(checksum) - if err != nil { - return 0, checksum, errorsmod.Wrap(types.ErrCreateFailed, err.Error()) - } - codeID = k.autoIncrementID(ctx, types.KeyLastCodeID) - k.Logger(ctx).Debug("storing new contract", "capabilities", report.RequiredCapabilities, "code_id", codeID) - codeInfo := types.NewCodeInfo(checksum, creator, *instantiateAccess) - k.storeCodeInfo(ctx, codeID, codeInfo) - - evt := sdk.NewEvent( - types.EventTypeStoreCode, - sdk.NewAttribute(types.AttributeKeyChecksum, hex.EncodeToString(checksum)), - sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), // last element to be compatible with scripts - ) - for _, f := range strings.Split(report.RequiredCapabilities, ",") { - evt.AppendAttributes(sdk.NewAttribute(types.AttributeKeyRequiredCapability, strings.TrimSpace(f))) - } - ctx.EventManager().EmitEvent(evt) - - return codeID, checksum, nil -} - -func (k Keeper) storeCodeInfo(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo) { - store := ctx.KVStore(k.storeKey) - // 0x01 | codeID (uint64) -> ContractInfo - store.Set(types.GetCodeKey(codeID), k.cdc.MustMarshal(&codeInfo)) -} - -func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo, wasmCode []byte) error { - if ioutils.IsGzip(wasmCode) { - var err error - wasmCode, err = ioutils.Uncompress(wasmCode, uint64(types.MaxWasmSize)) - if err != nil { - return errorsmod.Wrap(types.ErrCreateFailed, err.Error()) - } - } - newCodeHash, err := k.wasmVM.Create(wasmCode) - if err != nil { - return errorsmod.Wrap(types.ErrCreateFailed, err.Error()) - } - if !bytes.Equal(codeInfo.CodeHash, newCodeHash) { - return errorsmod.Wrap(types.ErrInvalid, "code hashes not same") - } - - store := ctx.KVStore(k.storeKey) - key := types.GetCodeKey(codeID) - if store.Has(key) { - return errorsmod.Wrapf(types.ErrDuplicate, "duplicate code: %d", codeID) - } - // 0x01 | codeID (uint64) -> ContractInfo - store.Set(key, k.cdc.MustMarshal(&codeInfo)) - return nil -} - -func (k Keeper) instantiate( - ctx sdk.Context, - codeID uint64, - creator, admin sdk.AccAddress, - initMsg []byte, - label string, - deposit sdk.Coins, - addressGenerator AddressGenerator, - authPolicy AuthorizationPolicy, -) (sdk.AccAddress, []byte, error) { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "instantiate") - - if creator == nil { - return nil, nil, types.ErrEmpty.Wrap("creator") - } - instanceCosts := k.gasRegister.NewContractInstanceCosts(k.IsPinnedCode(ctx, codeID), len(initMsg)) - ctx.GasMeter().ConsumeGas(instanceCosts, "Loading CosmWasm module: instantiate") - - codeInfo := k.GetCodeInfo(ctx, codeID) - if codeInfo == nil { - return nil, nil, errorsmod.Wrap(types.ErrNotFound, "code") - } - if !authPolicy.CanInstantiateContract(codeInfo.InstantiateConfig, creator) { - return nil, nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not instantiate") - } - - contractAddress := addressGenerator(ctx, codeID, codeInfo.CodeHash) - if k.HasContractInfo(ctx, contractAddress) { - return nil, nil, types.ErrDuplicate.Wrap("instance with this code id, sender and label exists: try a different label") - } - - // check account - // every cosmos module can define custom account types when needed. The cosmos-sdk comes with extension points - // to support this and a set of base and vesting account types that we integrated in our default lists. - // But not all account types of other modules are known or may make sense for contracts, therefore we kept this - // decision logic also very flexible and extendable. We provide new options to overwrite the default settings via WithAcceptedAccountTypesOnContractInstantiation and - // WithPruneAccountTypesOnContractInstantiation as constructor arguments - existingAcct := k.accountKeeper.GetAccount(ctx, contractAddress) - if existingAcct != nil { - if existingAcct.GetSequence() != 0 || existingAcct.GetPubKey() != nil { - return nil, nil, types.ErrAccountExists.Wrap("address is claimed by external account") - } - if _, accept := k.acceptedAccountTypes[reflect.TypeOf(existingAcct)]; accept { - // keep account and balance as it is - k.Logger(ctx).Info("instantiate contract with existing account", "address", contractAddress.String()) - } else { - // consider an account in the wasmd namespace spam and overwrite it. - k.Logger(ctx).Info("pruning existing account for contract instantiation", "address", contractAddress.String()) - contractAccount := k.accountKeeper.NewAccountWithAddress(ctx, contractAddress) - k.accountKeeper.SetAccount(ctx, contractAccount) - // also handle balance to not open cases where these accounts are abused and become liquid - switch handled, err := k.accountPruner.CleanupExistingAccount(ctx, existingAcct); { - case err != nil: - return nil, nil, errorsmod.Wrap(err, "prune balance") - case !handled: - return nil, nil, types.ErrAccountExists.Wrap("address is claimed by external account") - } - } - } else { - // create an empty account (so we don't have issues later) - contractAccount := k.accountKeeper.NewAccountWithAddress(ctx, contractAddress) - k.accountKeeper.SetAccount(ctx, contractAccount) - } - // deposit initial contract funds - if !deposit.IsZero() { - if err := k.bank.TransferCoins(ctx, creator, contractAddress, deposit); err != nil { - return nil, nil, err - } - } - - // prepare params for contract instantiate call - env := types.NewEnv(ctx, contractAddress) - info := types.NewInfo(creator, deposit) - - // create prefixed data store - // 0x03 | BuildContractAddressClassic (sdk.AccAddress) - prefixStoreKey := types.GetContractStorePrefix(contractAddress) - vmStore := types.NewStoreAdapter(prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)) - - // prepare querier - querier := k.newQueryHandler(ctx, contractAddress) - - // instantiate wasm contract - gas := k.runtimeGasForContract(ctx) - res, gasUsed, err := k.wasmVM.Instantiate(codeInfo.CodeHash, env, info, initMsg, vmStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if err != nil { - return nil, nil, errorsmod.Wrap(types.ErrInstantiateFailed, err.Error()) - } - - // persist instance first - createdAt := types.NewAbsoluteTxPosition(ctx) - contractInfo := types.NewContractInfo(codeID, creator, admin, label, createdAt) - - // check for IBC flag - report, err := k.wasmVM.AnalyzeCode(codeInfo.CodeHash) - if err != nil { - return nil, nil, errorsmod.Wrap(types.ErrInstantiateFailed, err.Error()) - } - if report.HasIBCEntryPoints { - // register IBC port - ibcPort, err := k.ensureIbcPort(ctx, contractAddress) - if err != nil { - return nil, nil, err - } - contractInfo.IBCPortID = ibcPort - } - - // store contract before dispatch so that contract could be called back - historyEntry := contractInfo.InitialHistory(initMsg) - k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry) - k.addToContractCreatorSecondaryIndex(ctx, creator, historyEntry.Updated, contractAddress) - k.appendToContractHistory(ctx, contractAddress, historyEntry) - k.storeContractInfo(ctx, contractAddress, &contractInfo) - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeInstantiate, - sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), - sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), - )) - - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events) - if err != nil { - return nil, nil, errorsmod.Wrap(err, "dispatch") - } - - return contractAddress, data, nil -} - -// Execute executes the contract instance -func (k Keeper) execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "execute") - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress) - if err != nil { - return nil, err - } - - executeCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(msg)) - ctx.GasMeter().ConsumeGas(executeCosts, "Loading CosmWasm module: execute") - - // add more funds - if !coins.IsZero() { - if err := k.bank.TransferCoins(ctx, caller, contractAddress, coins); err != nil { - return nil, err - } - } - - env := types.NewEnv(ctx, contractAddress) - info := types.NewInfo(caller, coins) - - // prepare querier - querier := k.newQueryHandler(ctx, contractAddress) - gas := k.runtimeGasForContract(ctx) - res, gasUsed, execErr := k.wasmVM.Execute(codeInfo.CodeHash, env, info, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - return nil, errorsmod.Wrap(types.ErrExecuteFailed, execErr.Error()) - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeExecute, - sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), - )) - - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events) - if err != nil { - return nil, errorsmod.Wrap(err, "dispatch") - } - - return data, nil -} - -func (k Keeper) migrate(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, newCodeID uint64, msg []byte, authZ AuthorizationPolicy) ([]byte, error) { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "migrate") - migrateSetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, newCodeID), len(msg)) - ctx.GasMeter().ConsumeGas(migrateSetupCosts, "Loading CosmWasm module: migrate") - - contractInfo := k.GetContractInfo(ctx, contractAddress) - if contractInfo == nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract") - } - if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not migrate") - } - - newCodeInfo := k.GetCodeInfo(ctx, newCodeID) - if newCodeInfo == nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown code") - } - - if !authZ.CanInstantiateContract(newCodeInfo.InstantiateConfig, caller) { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "to use new code") - } - - // check for IBC flag - switch report, err := k.wasmVM.AnalyzeCode(newCodeInfo.CodeHash); { - case err != nil: - return nil, errorsmod.Wrap(types.ErrMigrationFailed, err.Error()) - case !report.HasIBCEntryPoints && contractInfo.IBCPortID != "": - // prevent update to non ibc contract - return nil, errorsmod.Wrap(types.ErrMigrationFailed, "requires ibc callbacks") - case report.HasIBCEntryPoints && contractInfo.IBCPortID == "": - // add ibc port - ibcPort, err := k.ensureIbcPort(ctx, contractAddress) - if err != nil { - return nil, err - } - contractInfo.IBCPortID = ibcPort - } - - env := types.NewEnv(ctx, contractAddress) - - // prepare querier - querier := k.newQueryHandler(ctx, contractAddress) - - prefixStoreKey := types.GetContractStorePrefix(contractAddress) - vmStore := types.NewStoreAdapter(prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey)) - gas := k.runtimeGasForContract(ctx) - res, gasUsed, err := k.wasmVM.Migrate(newCodeInfo.CodeHash, env, msg, vmStore, cosmwasmAPI, &querier, k.gasMeter(ctx), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if err != nil { - return nil, errorsmod.Wrap(types.ErrMigrationFailed, err.Error()) - } - // delete old secondary index entry - k.removeFromContractCodeSecondaryIndex(ctx, contractAddress, k.getLastContractHistoryEntry(ctx, contractAddress)) - // persist migration updates - historyEntry := contractInfo.AddMigration(ctx, newCodeID, msg) - k.appendToContractHistory(ctx, contractAddress, historyEntry) - k.addToContractCodeSecondaryIndex(ctx, contractAddress, historyEntry) - k.storeContractInfo(ctx, contractAddress, contractInfo) - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeMigrate, - sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(newCodeID, 10)), - sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), - )) - - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events) - if err != nil { - return nil, errorsmod.Wrap(err, "dispatch") - } - - return data, nil -} - -// Sudo allows priviledged access to a contract. This can never be called by an external tx, but only by -// another native Go module directly, or on-chain governance (if sudo proposals are enabled). Thus, the keeper doesn't -// place any access controls on it, that is the responsibility or the app developer (who passes the wasm.Keeper in app.go) -func (k Keeper) Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "sudo") - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress) - if err != nil { - return nil, err - } - - sudoSetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(msg)) - ctx.GasMeter().ConsumeGas(sudoSetupCosts, "Loading CosmWasm module: sudo") - - env := types.NewEnv(ctx, contractAddress) - - // prepare querier - querier := k.newQueryHandler(ctx, contractAddress) - gas := k.runtimeGasForContract(ctx) - res, gasUsed, execErr := k.wasmVM.Sudo(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - return nil, errorsmod.Wrap(types.ErrExecuteFailed, execErr.Error()) - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeSudo, - sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), - )) - - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events) - if err != nil { - return nil, errorsmod.Wrap(err, "dispatch") - } - - return data, nil -} - -// reply is only called from keeper internal functions (dispatchSubmessages) after processing the submessage -func (k Keeper) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddress) - if err != nil { - return nil, err - } - - // always consider this pinned - replyCosts := k.gasRegister.ReplyCosts(true, reply) - ctx.GasMeter().ConsumeGas(replyCosts, "Loading CosmWasm module: reply") - - env := types.NewEnv(ctx, contractAddress) - - // prepare querier - querier := k.newQueryHandler(ctx, contractAddress) - gas := k.runtimeGasForContract(ctx) - - res, gasUsed, execErr := k.wasmVM.Reply(codeInfo.CodeHash, env, reply, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - return nil, errorsmod.Wrap(types.ErrExecuteFailed, execErr.Error()) - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeReply, - sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), - )) - - data, err := k.handleContractResponse(ctx, contractAddress, contractInfo.IBCPortID, res.Messages, res.Attributes, res.Data, res.Events) - if err != nil { - return nil, errorsmod.Wrap(err, "dispatch") - } - - return data, nil -} - -// addToContractCodeSecondaryIndex adds element to the index for contracts-by-codeid queries -func (k Keeper) addToContractCodeSecondaryIndex(ctx sdk.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) { - store := ctx.KVStore(k.storeKey) - store.Set(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry), []byte{}) -} - -// removeFromContractCodeSecondaryIndex removes element to the index for contracts-by-codeid queries -func (k Keeper) removeFromContractCodeSecondaryIndex(ctx sdk.Context, contractAddress sdk.AccAddress, entry types.ContractCodeHistoryEntry) { - ctx.KVStore(k.storeKey).Delete(types.GetContractByCreatedSecondaryIndexKey(contractAddress, entry)) -} - -// addToContractCreatorSecondaryIndex adds element to the index for contracts-by-creator queries -func (k Keeper) addToContractCreatorSecondaryIndex(ctx sdk.Context, creatorAddress sdk.AccAddress, position *types.AbsoluteTxPosition, contractAddress sdk.AccAddress) { - store := ctx.KVStore(k.storeKey) - store.Set(types.GetContractByCreatorSecondaryIndexKey(creatorAddress, position.Bytes(), contractAddress), []byte{}) -} - -// IterateContractsByCreator iterates over all contracts with given creator address in order of creation time asc. -func (k Keeper) IterateContractsByCreator(ctx sdk.Context, creator sdk.AccAddress, cb func(address sdk.AccAddress) bool) { - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractsByCreatorPrefix(creator)) - for iter := prefixStore.Iterator(nil, nil); iter.Valid(); iter.Next() { - key := iter.Key() - if cb(key[types.AbsoluteTxPositionLen:]) { - return - } - } -} - -// IterateContractsByCode iterates over all contracts with given codeID ASC on code update time. -func (k Keeper) IterateContractsByCode(ctx sdk.Context, codeID uint64, cb func(address sdk.AccAddress) bool) { - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractByCodeIDSecondaryIndexPrefix(codeID)) - iter := prefixStore.Iterator(nil, nil) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - key := iter.Key() - if cb(key[types.AbsoluteTxPositionLen:]) { - return - } - } -} - -func (k Keeper) setContractAdmin(ctx sdk.Context, contractAddress, caller, newAdmin sdk.AccAddress, authZ AuthorizationPolicy) error { - contractInfo := k.GetContractInfo(ctx, contractAddress) - if contractInfo == nil { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "unknown contract") - } - if !authZ.CanModifyContract(contractInfo.AdminAddr(), caller) { - return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not modify contract") - } - newAdminStr := newAdmin.String() - contractInfo.Admin = newAdminStr - k.storeContractInfo(ctx, contractAddress, contractInfo) - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeUpdateContractAdmin, - sdk.NewAttribute(types.AttributeKeyContractAddr, contractAddress.String()), - sdk.NewAttribute(types.AttributeKeyNewAdmin, newAdminStr), - )) - - return nil -} - -func (k Keeper) appendToContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress, newEntries ...types.ContractCodeHistoryEntry) { - store := ctx.KVStore(k.storeKey) - // find last element position - var pos uint64 - prefixStore := prefix.NewStore(store, types.GetContractCodeHistoryElementPrefix(contractAddr)) - iter := prefixStore.ReverseIterator(nil, nil) - defer iter.Close() - - if iter.Valid() { - pos = sdk.BigEndianToUint64(iter.Key()) - } - // then store with incrementing position - for _, e := range newEntries { - pos++ - key := types.GetContractCodeHistoryElementKey(contractAddr, pos) - store.Set(key, k.cdc.MustMarshal(&e)) //nolint:gosec - } -} - -func (k Keeper) GetContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress) []types.ContractCodeHistoryEntry { - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(contractAddr)) - r := make([]types.ContractCodeHistoryEntry, 0) - iter := prefixStore.Iterator(nil, nil) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - var e types.ContractCodeHistoryEntry - k.cdc.MustUnmarshal(iter.Value(), &e) - r = append(r, e) - } - return r -} - -// getLastContractHistoryEntry returns the last element from history. To be used internally only as it panics when none exists -func (k Keeper) getLastContractHistoryEntry(ctx sdk.Context, contractAddr sdk.AccAddress) types.ContractCodeHistoryEntry { - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.GetContractCodeHistoryElementPrefix(contractAddr)) - iter := prefixStore.ReverseIterator(nil, nil) - defer iter.Close() - - var r types.ContractCodeHistoryEntry - if !iter.Valid() { - // all contracts have a history - panic(fmt.Sprintf("no history for %s", contractAddr.String())) - } - k.cdc.MustUnmarshal(iter.Value(), &r) - return r -} - -// QuerySmart queries the smart contract itself. -func (k Keeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "query-smart") - - // checks and increase query stack size - ctx, err := checkAndIncreaseQueryStackSize(ctx, k.maxQueryStackSize) - if err != nil { - return nil, err - } - - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) - if err != nil { - return nil, err - } - - smartQuerySetupCosts := k.gasRegister.InstantiateContractCosts(k.IsPinnedCode(ctx, contractInfo.CodeID), len(req)) - ctx.GasMeter().ConsumeGas(smartQuerySetupCosts, "Loading CosmWasm module: query") - - // prepare querier - querier := k.newQueryHandler(ctx, contractAddr) - - env := types.NewEnv(ctx, contractAddr) - queryResult, gasUsed, qErr := k.wasmVM.Query(codeInfo.CodeHash, env, req, prefixStore, cosmwasmAPI, querier, k.gasMeter(ctx), k.runtimeGasForContract(ctx), costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if qErr != nil { - return nil, errorsmod.Wrap(types.ErrQueryFailed, qErr.Error()) - } - return queryResult, nil -} - -func checkAndIncreaseQueryStackSize(ctx sdk.Context, maxQueryStackSize uint32) (sdk.Context, error) { - var queryStackSize uint32 - - // read current value - if size := ctx.Context().Value(contextKeyQueryStackSize); size != nil { - queryStackSize = size.(uint32) - } else { - queryStackSize = 0 - } - - // increase - queryStackSize++ - - // did we go too far? - if queryStackSize > maxQueryStackSize { - return ctx, types.ErrExceedMaxQueryStackSize - } - - // set updated stack size - ctx = ctx.WithContext(context.WithValue(ctx.Context(), contextKeyQueryStackSize, queryStackSize)) - - return ctx, nil -} - -// QueryRaw returns the contract's state for give key. Returns `nil` when key is `nil`. -func (k Keeper) QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "query-raw") - if key == nil { - return nil - } - prefixStoreKey := types.GetContractStorePrefix(contractAddress) - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) - return prefixStore.Get(key) -} - -// internal helper function -func (k Keeper) contractInstance(ctx sdk.Context, contractAddress sdk.AccAddress) (types.ContractInfo, types.CodeInfo, wasmvm.KVStore, error) { - store := ctx.KVStore(k.storeKey) - - contractBz := store.Get(types.GetContractAddressKey(contractAddress)) - if contractBz == nil { - return types.ContractInfo{}, types.CodeInfo{}, nil, errorsmod.Wrap(types.ErrNotFound, "contract") - } - var contractInfo types.ContractInfo - k.cdc.MustUnmarshal(contractBz, &contractInfo) - - codeInfoBz := store.Get(types.GetCodeKey(contractInfo.CodeID)) - if codeInfoBz == nil { - return contractInfo, types.CodeInfo{}, nil, errorsmod.Wrap(types.ErrNotFound, "code info") - } - var codeInfo types.CodeInfo - k.cdc.MustUnmarshal(codeInfoBz, &codeInfo) - prefixStoreKey := types.GetContractStorePrefix(contractAddress) - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) - return contractInfo, codeInfo, types.NewStoreAdapter(prefixStore), nil -} - -func (k Keeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { - store := ctx.KVStore(k.storeKey) - var contract types.ContractInfo - contractBz := store.Get(types.GetContractAddressKey(contractAddress)) - if contractBz == nil { - return nil - } - k.cdc.MustUnmarshal(contractBz, &contract) - return &contract -} - -func (k Keeper) HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool { - store := ctx.KVStore(k.storeKey) - return store.Has(types.GetContractAddressKey(contractAddress)) -} - -// storeContractInfo persists the ContractInfo. No secondary index updated here. -func (k Keeper) storeContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress, contract *types.ContractInfo) { - store := ctx.KVStore(k.storeKey) - store.Set(types.GetContractAddressKey(contractAddress), k.cdc.MustMarshal(contract)) -} - -func (k Keeper) IterateContractInfo(ctx sdk.Context, cb func(sdk.AccAddress, types.ContractInfo) bool) { - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.ContractKeyPrefix) - iter := prefixStore.Iterator(nil, nil) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - var contract types.ContractInfo - k.cdc.MustUnmarshal(iter.Value(), &contract) - // cb returns true to stop early - if cb(iter.Key(), contract) { - break - } - } -} - -// IterateContractState iterates through all elements of the key value store for the given contract address and passes -// them to the provided callback function. The callback method can return true to abort early. -func (k Keeper) IterateContractState(ctx sdk.Context, contractAddress sdk.AccAddress, cb func(key, value []byte) bool) { - prefixStoreKey := types.GetContractStorePrefix(contractAddress) - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) - iter := prefixStore.Iterator(nil, nil) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - if cb(iter.Key(), iter.Value()) { - break - } - } -} - -func (k Keeper) importContractState(ctx sdk.Context, contractAddress sdk.AccAddress, models []types.Model) error { - prefixStoreKey := types.GetContractStorePrefix(contractAddress) - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), prefixStoreKey) - for _, model := range models { - if model.Value == nil { - model.Value = []byte{} - } - if prefixStore.Has(model.Key) { - return errorsmod.Wrapf(types.ErrDuplicate, "duplicate key: %x", model.Key) - } - prefixStore.Set(model.Key, model.Value) - } - return nil -} - -func (k Keeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo { - store := ctx.KVStore(k.storeKey) - var codeInfo types.CodeInfo - codeInfoBz := store.Get(types.GetCodeKey(codeID)) - if codeInfoBz == nil { - return nil - } - k.cdc.MustUnmarshal(codeInfoBz, &codeInfo) - return &codeInfo -} - -func (k Keeper) containsCodeInfo(ctx sdk.Context, codeID uint64) bool { - store := ctx.KVStore(k.storeKey) - return store.Has(types.GetCodeKey(codeID)) -} - -func (k Keeper) IterateCodeInfos(ctx sdk.Context, cb func(uint64, types.CodeInfo) bool) { - prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.CodeKeyPrefix) - iter := prefixStore.Iterator(nil, nil) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - var c types.CodeInfo - k.cdc.MustUnmarshal(iter.Value(), &c) - // cb returns true to stop early - if cb(binary.BigEndian.Uint64(iter.Key()), c) { - return - } - } -} - -func (k Keeper) GetByteCode(ctx sdk.Context, codeID uint64) ([]byte, error) { - store := ctx.KVStore(k.storeKey) - var codeInfo types.CodeInfo - codeInfoBz := store.Get(types.GetCodeKey(codeID)) - if codeInfoBz == nil { - return nil, nil - } - k.cdc.MustUnmarshal(codeInfoBz, &codeInfo) - return k.wasmVM.GetCode(codeInfo.CodeHash) -} - -// PinCode pins the wasm contract in wasmvm cache -func (k Keeper) pinCode(ctx sdk.Context, codeID uint64) error { - codeInfo := k.GetCodeInfo(ctx, codeID) - if codeInfo == nil { - return errorsmod.Wrap(types.ErrNotFound, "code info") - } - - if err := k.wasmVM.Pin(codeInfo.CodeHash); err != nil { - return errorsmod.Wrap(types.ErrPinContractFailed, err.Error()) - } - store := ctx.KVStore(k.storeKey) - // store 1 byte to not run into `nil` debugging issues - store.Set(types.GetPinnedCodeIndexPrefix(codeID), []byte{1}) - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePinCode, - sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), - )) - return nil -} - -// UnpinCode removes the wasm contract from wasmvm cache -func (k Keeper) unpinCode(ctx sdk.Context, codeID uint64) error { - codeInfo := k.GetCodeInfo(ctx, codeID) - if codeInfo == nil { - return errorsmod.Wrap(types.ErrNotFound, "code info") - } - if err := k.wasmVM.Unpin(codeInfo.CodeHash); err != nil { - return errorsmod.Wrap(types.ErrUnpinContractFailed, err.Error()) - } - - store := ctx.KVStore(k.storeKey) - store.Delete(types.GetPinnedCodeIndexPrefix(codeID)) - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeUnpinCode, - sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), - )) - return nil -} - -// IsPinnedCode returns true when codeID is pinned in wasmvm cache -func (k Keeper) IsPinnedCode(ctx sdk.Context, codeID uint64) bool { - store := ctx.KVStore(k.storeKey) - return store.Has(types.GetPinnedCodeIndexPrefix(codeID)) -} - -// InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned -func (k Keeper) InitializePinnedCodes(ctx sdk.Context) error { - store := prefix.NewStore(ctx.KVStore(k.storeKey), types.PinnedCodeIndexPrefix) - iter := store.Iterator(nil, nil) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - codeInfo := k.GetCodeInfo(ctx, types.ParsePinnedCodeIndex(iter.Key())) - if codeInfo == nil { - return errorsmod.Wrap(types.ErrNotFound, "code info") - } - if err := k.wasmVM.Pin(codeInfo.CodeHash); err != nil { - return errorsmod.Wrap(types.ErrPinContractFailed, err.Error()) - } - } - return nil -} - -// setContractInfoExtension updates the extension point data that is stored with the contract info -func (k Keeper) setContractInfoExtension(ctx sdk.Context, contractAddr sdk.AccAddress, ext types.ContractInfoExtension) error { - info := k.GetContractInfo(ctx, contractAddr) - if info == nil { - return errorsmod.Wrap(types.ErrNotFound, "contract info") - } - if err := info.SetExtension(ext); err != nil { - return err - } - k.storeContractInfo(ctx, contractAddr, info) - return nil -} - -// setAccessConfig updates the access config of a code id. -func (k Keeper) setAccessConfig(ctx sdk.Context, codeID uint64, caller sdk.AccAddress, newConfig types.AccessConfig, authz AuthorizationPolicy) error { - info := k.GetCodeInfo(ctx, codeID) - if info == nil { - return errorsmod.Wrap(types.ErrNotFound, "code info") - } - isSubset := newConfig.Permission.IsSubset(k.getInstantiateAccessConfig(ctx)) - if !authz.CanModifyCodeAccessConfig(sdk.MustAccAddressFromBech32(info.Creator), caller, isSubset) { - return errorsmod.Wrap(sdkerrors.ErrUnauthorized, "can not modify code access config") - } - - info.InstantiateConfig = newConfig - k.storeCodeInfo(ctx, codeID, *info) - evt := sdk.NewEvent( - types.EventTypeUpdateCodeAccessConfig, - sdk.NewAttribute(types.AttributeKeyCodePermission, newConfig.Permission.String()), - sdk.NewAttribute(types.AttributeKeyCodeID, strconv.FormatUint(codeID, 10)), - ) - if addrs := newConfig.AllAuthorizedAddresses(); len(addrs) != 0 { - attr := sdk.NewAttribute(types.AttributeKeyAuthorizedAddresses, strings.Join(addrs, ",")) - evt.Attributes = append(evt.Attributes, attr.ToKVPair()) - } - ctx.EventManager().EmitEvent(evt) - return nil -} - -// handleContractResponse processes the contract response data by emitting events and sending sub-/messages. -func (k *Keeper) handleContractResponse( - ctx sdk.Context, - contractAddr sdk.AccAddress, - ibcPort string, - msgs []wasmvmtypes.SubMsg, - attrs []wasmvmtypes.EventAttribute, - data []byte, - evts wasmvmtypes.Events, -) ([]byte, error) { - attributeGasCost := k.gasRegister.EventCosts(attrs, evts) - ctx.GasMeter().ConsumeGas(attributeGasCost, "Custom contract event attributes") - // emit all events from this contract itself - if len(attrs) != 0 { - wasmEvents, err := newWasmModuleEvent(attrs, contractAddr) - if err != nil { - return nil, err - } - ctx.EventManager().EmitEvents(wasmEvents) - } - if len(evts) > 0 { - customEvents, err := newCustomEvents(evts, contractAddr) - if err != nil { - return nil, err - } - ctx.EventManager().EmitEvents(customEvents) - } - return k.wasmVMResponseHandler.Handle(ctx, contractAddr, ibcPort, msgs, data) -} - -func (k Keeper) runtimeGasForContract(ctx sdk.Context) uint64 { - meter := ctx.GasMeter() - if meter.IsOutOfGas() { - return 0 - } - if meter.Limit() == math.MaxUint64 { // infinite gas meter and not out of gas - return math.MaxUint64 - } - return k.gasRegister.ToWasmVMGas(meter.Limit() - meter.GasConsumedToLimit()) -} - -func (k Keeper) consumeRuntimeGas(ctx sdk.Context, gas uint64) { - consumed := k.gasRegister.FromWasmVMGas(gas) - ctx.GasMeter().ConsumeGas(consumed, "wasm contract") - // throw OutOfGas error if we ran out (got exactly to zero due to better limit enforcing) - if ctx.GasMeter().IsOutOfGas() { - panic(sdk.ErrorOutOfGas{Descriptor: "Wasmer function execution"}) - } -} - -func (k Keeper) autoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 { - store := ctx.KVStore(k.storeKey) - bz := store.Get(lastIDKey) - id := uint64(1) - if bz != nil { - id = binary.BigEndian.Uint64(bz) - } - bz = sdk.Uint64ToBigEndian(id + 1) - store.Set(lastIDKey, bz) - return id -} - -// PeekAutoIncrementID reads the current value without incrementing it. -func (k Keeper) PeekAutoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 { - store := ctx.KVStore(k.storeKey) - bz := store.Get(lastIDKey) - id := uint64(1) - if bz != nil { - id = binary.BigEndian.Uint64(bz) - } - return id -} - -func (k Keeper) importAutoIncrementID(ctx sdk.Context, lastIDKey []byte, val uint64) error { - store := ctx.KVStore(k.storeKey) - if store.Has(lastIDKey) { - return errorsmod.Wrapf(types.ErrDuplicate, "autoincrement id: %s", string(lastIDKey)) - } - bz := sdk.Uint64ToBigEndian(val) - store.Set(lastIDKey, bz) - return nil -} - -func (k Keeper) importContract(ctx sdk.Context, contractAddr sdk.AccAddress, c *types.ContractInfo, state []types.Model, entries []types.ContractCodeHistoryEntry) error { - if !k.containsCodeInfo(ctx, c.CodeID) { - return errorsmod.Wrapf(types.ErrNotFound, "code id: %d", c.CodeID) - } - if k.HasContractInfo(ctx, contractAddr) { - return errorsmod.Wrapf(types.ErrDuplicate, "contract: %s", contractAddr) - } - - creatorAddress, err := sdk.AccAddressFromBech32(c.Creator) - if err != nil { - return err - } - - k.appendToContractHistory(ctx, contractAddr, entries...) - k.storeContractInfo(ctx, contractAddr, c) - k.addToContractCodeSecondaryIndex(ctx, contractAddr, entries[len(entries)-1]) - k.addToContractCreatorSecondaryIndex(ctx, creatorAddress, entries[0].Updated, contractAddr) - return k.importContractState(ctx, contractAddr, state) -} - -func (k Keeper) newQueryHandler(ctx sdk.Context, contractAddress sdk.AccAddress) QueryHandler { - return NewQueryHandler(ctx, k.wasmVMQueryHandler, contractAddress, k.gasRegister) -} - -// MultipliedGasMeter wraps the GasMeter from context and multiplies all reads by out defined multiplier -type MultipliedGasMeter struct { - originalMeter sdk.GasMeter - GasRegister GasRegister -} - -func NewMultipliedGasMeter(originalMeter sdk.GasMeter, gr GasRegister) MultipliedGasMeter { - return MultipliedGasMeter{originalMeter: originalMeter, GasRegister: gr} -} - -var _ wasmvm.GasMeter = MultipliedGasMeter{} - -func (m MultipliedGasMeter) GasConsumed() sdk.Gas { - return m.GasRegister.ToWasmVMGas(m.originalMeter.GasConsumed()) -} - -func (k Keeper) gasMeter(ctx sdk.Context) MultipliedGasMeter { - return NewMultipliedGasMeter(ctx.GasMeter(), k.gasRegister) -} - -// Logger returns a module-specific logger. -func (k Keeper) Logger(ctx sdk.Context) log.Logger { - return moduleLogger(ctx) -} - -func moduleLogger(ctx sdk.Context) log.Logger { - return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) -} - -// Querier creates a new grpc querier instance -func Querier(k *Keeper) *GrpcQuerier { - return NewGrpcQuerier(k.cdc, k.storeKey, k, k.queryGasLimit) -} - -// QueryGasLimit returns the gas limit for smart queries. -func (k Keeper) QueryGasLimit() sdk.Gas { - return k.queryGasLimit -} - -// BankCoinTransferrer replicates the cosmos-sdk behaviour as in -// https://github.com/cosmos/cosmos-sdk/blob/v0.41.4/x/bank/keeper/msg_server.go#L26 -type BankCoinTransferrer struct { - keeper types.BankKeeper -} - -func NewBankCoinTransferrer(keeper types.BankKeeper) BankCoinTransferrer { - return BankCoinTransferrer{ - keeper: keeper, - } -} - -// TransferCoins transfers coins from source to destination account when coin send was enabled for them and the recipient -// is not in the blocked address list. -func (c BankCoinTransferrer) TransferCoins(parentCtx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amount sdk.Coins) error { - em := sdk.NewEventManager() - ctx := parentCtx.WithEventManager(em) - if err := c.keeper.IsSendEnabledCoins(ctx, amount...); err != nil { - return err - } - if c.keeper.BlockedAddr(toAddr) { - return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", toAddr.String()) - } - - sdkerr := c.keeper.SendCoins(ctx, fromAddr, toAddr, amount) - if sdkerr != nil { - return sdkerr - } - for _, e := range em.Events() { - if e.Type == sdk.EventTypeMessage { // skip messages as we talk to the keeper directly - continue - } - parentCtx.EventManager().EmitEvent(e) - } - return nil -} - -var _ AccountPruner = VestingCoinBurner{} - -// VestingCoinBurner default implementation for AccountPruner to burn the coins -type VestingCoinBurner struct { - bank types.BankKeeper -} - -// NewVestingCoinBurner constructor -func NewVestingCoinBurner(bank types.BankKeeper) VestingCoinBurner { - if bank == nil { - panic("bank keeper must not be nil") - } - return VestingCoinBurner{bank: bank} -} - -// CleanupExistingAccount accepts only vesting account types to burns all their original vesting coin balances. -// Other account types will be rejected and returned as unhandled. -func (b VestingCoinBurner) CleanupExistingAccount(ctx sdk.Context, existingAcc authtypes.AccountI) (handled bool, err error) { - v, ok := existingAcc.(vestingexported.VestingAccount) - if !ok { - return false, nil - } - - ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - coinsToBurn := sdk.NewCoins() - for _, orig := range v.GetOriginalVesting() { // focus on the coin denoms that were setup originally; getAllBalances has some issues - coinsToBurn = append(coinsToBurn, b.bank.GetBalance(ctx, existingAcc.GetAddress(), orig.Denom)) - } - if err := b.bank.SendCoinsFromAccountToModule(ctx, existingAcc.GetAddress(), types.ModuleName, coinsToBurn); err != nil { - return false, errorsmod.Wrap(err, "prune account balance") - } - if err := b.bank.BurnCoins(ctx, types.ModuleName, coinsToBurn); err != nil { - return false, errorsmod.Wrap(err, "burn account balance") - } - return true, nil -} - -type msgDispatcher interface { - DispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) -} - -// DefaultWasmVMContractResponseHandler default implementation that first dispatches submessage then normal messages. -// The Submessage execution may include an success/failure response handling by the contract that can overwrite the -// original -type DefaultWasmVMContractResponseHandler struct { - md msgDispatcher -} - -func NewDefaultWasmVMContractResponseHandler(md msgDispatcher) *DefaultWasmVMContractResponseHandler { - return &DefaultWasmVMContractResponseHandler{md: md} -} - -// Handle processes the data returned by a contract invocation. -func (h DefaultWasmVMContractResponseHandler) Handle(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, messages []wasmvmtypes.SubMsg, origRspData []byte) ([]byte, error) { - result := origRspData - switch rsp, err := h.md.DispatchSubmessages(ctx, contractAddr, ibcPort, messages); { - case err != nil: - return nil, errorsmod.Wrap(err, "submessages") - case rsp != nil: - result = rsp - } - return result, nil -} diff --git a/x/wasm/keeper/keeper_cgo.go b/x/wasm/keeper/keeper_cgo.go deleted file mode 100644 index 64c94f3..0000000 --- a/x/wasm/keeper/keeper_cgo.go +++ /dev/null @@ -1,65 +0,0 @@ -//go:build cgo - -package keeper - -import ( - "path/filepath" - - storetypes "github.com/cosmos/cosmos-sdk/store/types" - - wasmvm "github.com/CosmWasm/wasmvm" - "github.com/cosmos/cosmos-sdk/codec" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// NewKeeper creates a new contract Keeper instance -// If customEncoders is non-nil, we can use this to override some of the message handler, especially custom -func NewKeeper( - cdc codec.Codec, - storeKey storetypes.StoreKey, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, - stakingKeeper types.StakingKeeper, - distrKeeper types.DistributionKeeper, - channelKeeper types.ChannelKeeper, - portKeeper types.PortKeeper, - capabilityKeeper types.CapabilityKeeper, - portSource types.ICS20TransferPortSource, - router MessageRouter, - _ GRPCQueryRouter, - homeDir string, - wasmConfig types.WasmConfig, - availableCapabilities string, - authority string, - opts ...Option, -) Keeper { - wasmer, err := wasmvm.NewVM(filepath.Join(homeDir, "wasm"), availableCapabilities, contractMemoryLimit, wasmConfig.ContractDebugMode, wasmConfig.MemoryCacheSize) - if err != nil { - panic(err) - } - - keeper := &Keeper{ - storeKey: storeKey, - cdc: cdc, - wasmVM: wasmer, - accountKeeper: accountKeeper, - bank: NewBankCoinTransferrer(bankKeeper), - accountPruner: NewVestingCoinBurner(bankKeeper), - portKeeper: portKeeper, - capabilityKeeper: capabilityKeeper, - messenger: NewDefaultMessageHandler(router, channelKeeper, capabilityKeeper, bankKeeper, cdc, portSource), - queryGasLimit: wasmConfig.SmartQueryGasLimit, - gasRegister: NewDefaultWasmGasRegister(), - maxQueryStackSize: types.DefaultMaxQueryStackSize, - acceptedAccountTypes: defaultAcceptedAccountTypes, - authority: authority, - } - keeper.wasmVMQueryHandler = DefaultQueryPlugins(bankKeeper, stakingKeeper, distrKeeper, channelKeeper, keeper) - for _, o := range opts { - o.apply(keeper) - } - // not updateable, yet - keeper.wasmVMResponseHandler = NewDefaultWasmVMContractResponseHandler(NewMessageDispatcher(keeper.messenger, keeper)) - return *keeper -} diff --git a/x/wasm/keeper/keeper_no_cgo.go b/x/wasm/keeper/keeper_no_cgo.go deleted file mode 100644 index 2c45dbc..0000000 --- a/x/wasm/keeper/keeper_no_cgo.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build !cgo - -package keeper - -import ( - "github.com/cosmos/cosmos-sdk/codec" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// NewKeeper creates a new contract Keeper instance -// If customEncoders is non-nil, we can use this to override some of the message handler, especially custom -func NewKeeper( - cdc codec.Codec, - storeKey storetypes.StoreKey, - accountKeeper types.AccountKeeper, - bankKeeper types.BankKeeper, - stakingKeeper types.StakingKeeper, - distrKeeper types.DistributionKeeper, - channelKeeper types.ChannelKeeper, - portKeeper types.PortKeeper, - capabilityKeeper types.CapabilityKeeper, - portSource types.ICS20TransferPortSource, - router MessageRouter, - _ GRPCQueryRouter, - homeDir string, - wasmConfig types.WasmConfig, - availableCapabilities string, - authority string, - opts ...Option, -) Keeper { - panic("not implemented, please build with cgo enabled") -} diff --git a/x/wasm/keeper/keeper_test.go b/x/wasm/keeper/keeper_test.go deleted file mode 100644 index 6d97ae6..0000000 --- a/x/wasm/keeper/keeper_test.go +++ /dev/null @@ -1,2418 +0,0 @@ -package keeper - -import ( - "bytes" - _ "embed" - "encoding/json" - "errors" - "fmt" - "os" - "strings" - "testing" - "time" - - errorsmod "cosmossdk.io/errors" - abci "github.com/cometbft/cometbft/abci/types" - - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/cometbft/cometbft/libs/rand" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - stypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/auth/vesting" - vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - fuzz "github.com/google/gofuzz" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -//go:embed testdata/hackatom.wasm -var hackatomWasm []byte - -const AvailableCapabilities = "iterator,staking,stargate,cosmwasm_1_1" - -func TestNewKeeper(t *testing.T) { - _, keepers := CreateTestInput(t, false, AvailableCapabilities) - require.NotNil(t, keepers.ContractKeeper) -} - -func TestCreateSuccess(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - em := sdk.NewEventManager() - contractID, _, err := keeper.Create(ctx.WithEventManager(em), creator, hackatomWasm, nil) - require.NoError(t, err) - require.Equal(t, uint64(1), contractID) - // and verify content - storedCode, err := keepers.WasmKeeper.GetByteCode(ctx, contractID) - require.NoError(t, err) - require.Equal(t, hackatomWasm, storedCode) - // and events emitted - codeHash := strings.ToLower("beb3de5e9b93b52e514c74ce87ccddb594b9bcd33b7f1af1bb6da63fc883917b") - exp := sdk.Events{sdk.NewEvent("store_code", sdk.NewAttribute("code_checksum", codeHash), sdk.NewAttribute("code_id", "1"))} - assert.Equal(t, exp, em.Events()) -} - -func TestCreateNilCreatorAddress(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - - _, _, err := keepers.ContractKeeper.Create(ctx, nil, hackatomWasm, nil) - require.Error(t, err, "nil creator is not allowed") -} - -func TestCreateNilWasmCode(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - _, _, err := keepers.ContractKeeper.Create(ctx, creator, nil, nil) - require.Error(t, err, "nil WASM code is not allowed") -} - -func TestCreateInvalidWasmCode(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - _, _, err := keepers.ContractKeeper.Create(ctx, creator, []byte("potatoes"), nil) - require.Error(t, err, "potatoes are not valid WASM code") -} - -func TestCreateStoresInstantiatePermission(t *testing.T) { - var ( - deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - myAddr sdk.AccAddress = bytes.Repeat([]byte{1}, types.SDKAddrLen) - ) - - specs := map[string]struct { - srcPermission types.AccessType - expInstConf types.AccessConfig - }{ - "default": { - srcPermission: types.DefaultParams().InstantiateDefaultPermission, - expInstConf: types.AllowEverybody, - }, - "everybody": { - srcPermission: types.AccessTypeEverybody, - expInstConf: types.AllowEverybody, - }, - "nobody": { - srcPermission: types.AccessTypeNobody, - expInstConf: types.AllowNobody, - }, - "onlyAddress with matching address": { - srcPermission: types.AccessTypeOnlyAddress, - expInstConf: types.AccessConfig{Permission: types.AccessTypeOnlyAddress, Address: myAddr.String()}, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper - err := keepers.WasmKeeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowEverybody, - InstantiateDefaultPermission: spec.srcPermission, - }) - require.NoError(t, err) - fundAccounts(t, ctx, accKeeper, bankKeeper, myAddr, deposit) - - codeID, _, err := keeper.Create(ctx, myAddr, hackatomWasm, nil) - require.NoError(t, err) - - codeInfo := keepers.WasmKeeper.GetCodeInfo(ctx, codeID) - require.NotNil(t, codeInfo) - assert.True(t, spec.expInstConf.Equals(codeInfo.InstantiateConfig), "got %#v", codeInfo.InstantiateConfig) - }) - } -} - -func TestCreateWithParamPermissions(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - otherAddr := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - specs := map[string]struct { - policy AuthorizationPolicy - chainUpload types.AccessConfig - expError *errorsmod.Error - }{ - "default": { - policy: DefaultAuthorizationPolicy{}, - chainUpload: types.DefaultUploadAccess, - }, - "everybody": { - policy: DefaultAuthorizationPolicy{}, - chainUpload: types.AllowEverybody, - }, - "nobody": { - policy: DefaultAuthorizationPolicy{}, - chainUpload: types.AllowNobody, - expError: sdkerrors.ErrUnauthorized, - }, - "onlyAddress with matching address": { - policy: DefaultAuthorizationPolicy{}, - chainUpload: types.AccessTypeOnlyAddress.With(creator), - }, - "onlyAddress with non matching address": { - policy: DefaultAuthorizationPolicy{}, - chainUpload: types.AccessTypeOnlyAddress.With(otherAddr), - expError: sdkerrors.ErrUnauthorized, - }, - "gov: always allowed": { - policy: GovAuthorizationPolicy{}, - chainUpload: types.AllowNobody, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - params := types.DefaultParams() - params.CodeUploadAccess = spec.chainUpload - err := keepers.WasmKeeper.SetParams(ctx, params) - require.NoError(t, err) - keeper := NewPermissionedKeeper(keepers.WasmKeeper, spec.policy) - _, _, err = keeper.Create(ctx, creator, hackatomWasm, nil) - require.True(t, spec.expError.Is(err), err) - if spec.expError != nil { - return - } - }) - } -} - -// ensure that the user cannot set the code instantiate permission to something more permissive -// than the default -func TestEnforceValidPermissionsOnCreate(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - contractKeeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - other := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - onlyCreator := types.AccessTypeOnlyAddress.With(creator) - onlyOther := types.AccessTypeOnlyAddress.With(other) - - specs := map[string]struct { - defaultPermssion types.AccessType - requestedPermission *types.AccessConfig - // grantedPermission is set iff no error - grantedPermission types.AccessConfig - // expError is nil iff the request is allowed - expError *errorsmod.Error - }{ - "override everybody": { - defaultPermssion: types.AccessTypeEverybody, - requestedPermission: &onlyCreator, - grantedPermission: onlyCreator, - }, - "default to everybody": { - defaultPermssion: types.AccessTypeEverybody, - requestedPermission: nil, - grantedPermission: types.AccessConfig{Permission: types.AccessTypeEverybody}, - }, - "explicitly set everybody": { - defaultPermssion: types.AccessTypeEverybody, - requestedPermission: &types.AccessConfig{Permission: types.AccessTypeEverybody}, - grantedPermission: types.AccessConfig{Permission: types.AccessTypeEverybody}, - }, - "cannot override nobody": { - defaultPermssion: types.AccessTypeNobody, - requestedPermission: &onlyCreator, - expError: sdkerrors.ErrUnauthorized, - }, - "default to nobody": { - defaultPermssion: types.AccessTypeNobody, - requestedPermission: nil, - grantedPermission: types.AccessConfig{Permission: types.AccessTypeNobody}, - }, - "only defaults to code creator": { - defaultPermssion: types.AccessTypeOnlyAddress, - requestedPermission: nil, - grantedPermission: onlyCreator, - }, - "can explicitly set to code creator": { - defaultPermssion: types.AccessTypeOnlyAddress, - requestedPermission: &onlyCreator, - grantedPermission: onlyCreator, - }, - "cannot override which address in only": { - defaultPermssion: types.AccessTypeOnlyAddress, - requestedPermission: &onlyOther, - expError: sdkerrors.ErrUnauthorized, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - params := types.DefaultParams() - params.InstantiateDefaultPermission = spec.defaultPermssion - err := keeper.SetParams(ctx, params) - require.NoError(t, err) - codeID, _, err := contractKeeper.Create(ctx, creator, hackatomWasm, spec.requestedPermission) - require.True(t, spec.expError.Is(err), err) - if spec.expError == nil { - codeInfo := keeper.GetCodeInfo(ctx, codeID) - require.Equal(t, codeInfo.InstantiateConfig, spec.grantedPermission) - } - }) - } -} - -func TestCreateDuplicate(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - // create one copy - contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - require.Equal(t, uint64(1), contractID) - - // create second copy - duplicateID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - require.Equal(t, uint64(2), duplicateID) - - // and verify both content is proper - storedCode, err := keepers.WasmKeeper.GetByteCode(ctx, contractID) - require.NoError(t, err) - require.Equal(t, hackatomWasm, storedCode) - storedCode, err = keepers.WasmKeeper.GetByteCode(ctx, duplicateID) - require.NoError(t, err) - require.Equal(t, hackatomWasm, storedCode) -} - -func TestCreateWithSimulation(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - - ctx = ctx.WithBlockHeader(tmproto.Header{Height: 1}). - WithGasMeter(stypes.NewInfiniteGasMeter()) - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - // create this once in simulation mode - contractID, _, err := keepers.ContractKeeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - require.Equal(t, uint64(1), contractID) - - // then try to create it in non-simulation mode (should not fail) - ctx, keepers = CreateTestInput(t, false, AvailableCapabilities) - ctx = ctx.WithGasMeter(sdk.NewGasMeter(10_000_000)) - creator = keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - contractID, _, err = keepers.ContractKeeper.Create(ctx, creator, hackatomWasm, nil) - - require.NoError(t, err) - require.Equal(t, uint64(1), contractID) - - // and verify content - code, err := keepers.WasmKeeper.GetByteCode(ctx, contractID) - require.NoError(t, err) - require.Equal(t, code, hackatomWasm) -} - -func TestIsSimulationMode(t *testing.T) { - specs := map[string]struct { - ctx sdk.Context - exp bool - }{ - "genesis block": { - ctx: sdk.Context{}.WithBlockHeader(tmproto.Header{}).WithGasMeter(stypes.NewInfiniteGasMeter()), - exp: false, - }, - "any regular block": { - ctx: sdk.Context{}.WithBlockHeader(tmproto.Header{Height: 1}).WithGasMeter(stypes.NewGasMeter(10000000)), - exp: false, - }, - "simulation": { - ctx: sdk.Context{}.WithBlockHeader(tmproto.Header{Height: 1}).WithGasMeter(stypes.NewInfiniteGasMeter()), - exp: true, - }, - } - for msg := range specs { - t.Run(msg, func(t *testing.T) { - // assert.Equal(t, spec.exp, isSimulationMode(spec.ctx)) - }) - } -} - -func TestCreateWithGzippedPayload(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm.gzip") - require.NoError(t, err, "reading gzipped WASM code") - - contractID, _, err := keeper.Create(ctx, creator, wasmCode, nil) - require.NoError(t, err) - require.Equal(t, uint64(1), contractID) - // and verify content - storedCode, err := keepers.WasmKeeper.GetByteCode(ctx, contractID) - require.NoError(t, err) - require.Equal(t, hackatomWasm, storedCode) -} - -func TestCreateWithBrokenGzippedPayload(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - wasmCode, err := os.ReadFile("./testdata/broken_crc.gzip") - require.NoError(t, err, "reading gzipped WASM code") - - gm := sdk.NewInfiniteGasMeter() - codeID, checksum, err := keeper.Create(ctx.WithGasMeter(gm), creator, wasmCode, nil) - require.Error(t, err) - assert.Empty(t, codeID) - assert.Empty(t, checksum) - assert.GreaterOrEqual(t, gm.GasConsumed(), sdk.Gas(121384)) // 809232 * 0.15 (default uncompress costs) = 121384 -} - -func TestInstantiate(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len)) - keepers.Faucet.Fund(ctx, creator, deposit...) - example := StoreHackatomExampleContract(t, ctx, keepers) - - initMsg := HackatomExampleInitMsg{ - Verifier: RandomAccountAddress(t), - Beneficiary: RandomAccountAddress(t), - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - gasBefore := ctx.GasMeter().GasConsumed() - - em := sdk.NewEventManager() - // create with no balance is also legal - gotContractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx.WithEventManager(em), example.CodeID, creator, nil, initMsgBz, "demo contract 1", nil) - require.NoError(t, err) - require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", gotContractAddr.String()) - - gasAfter := ctx.GasMeter().GasConsumed() - if types.EnableGasVerification { - require.Equal(t, uint64(0x1b5bc), gasAfter-gasBefore) - } - - // ensure it is stored properly - info := keepers.WasmKeeper.GetContractInfo(ctx, gotContractAddr) - require.NotNil(t, info) - assert.Equal(t, creator.String(), info.Creator) - assert.Equal(t, example.CodeID, info.CodeID) - assert.Equal(t, "demo contract 1", info.Label) - - exp := []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: example.CodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: initMsgBz, - }} - assert.Equal(t, exp, keepers.WasmKeeper.GetContractHistory(ctx, gotContractAddr)) - - // and events emitted - expEvt := sdk.Events{ - sdk.NewEvent("instantiate", - sdk.NewAttribute("_contract_address", gotContractAddr.String()), sdk.NewAttribute("code_id", "1")), - sdk.NewEvent("wasm", - sdk.NewAttribute("_contract_address", gotContractAddr.String()), sdk.NewAttribute("Let the", "hacking begin")), - } - assert.Equal(t, expEvt, em.Events()) -} - -func TestInstantiateWithDeposit(t *testing.T) { - var ( - bob = bytes.Repeat([]byte{1}, types.SDKAddrLen) - fred = bytes.Repeat([]byte{2}, types.SDKAddrLen) - - deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100)) - initMsg = mustMarshal(t, HackatomExampleInitMsg{Verifier: fred, Beneficiary: bob}) - ) - - specs := map[string]struct { - srcActor sdk.AccAddress - expError bool - fundAddr bool - }{ - "address with funds": { - srcActor: bob, - fundAddr: true, - }, - "address without funds": { - srcActor: bob, - expError: true, - }, - "blocked address": { - srcActor: authtypes.NewModuleAddress(authtypes.FeeCollectorName), - fundAddr: true, - expError: false, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.ContractKeeper - - if spec.fundAddr { - fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, sdk.NewCoins(sdk.NewInt64Coin("denom", 200))) - } - contractID, _, err := keeper.Create(ctx, spec.srcActor, hackatomWasm, nil) - require.NoError(t, err) - - // when - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, spec.srcActor, nil, initMsg, "my label", deposit) - // then - if spec.expError { - require.Error(t, err) - return - } - require.NoError(t, err) - balances := bankKeeper.GetAllBalances(ctx, addr) - assert.Equal(t, deposit, balances) - }) - } -} - -func TestInstantiateWithPermissions(t *testing.T) { - var ( - deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - myAddr = bytes.Repeat([]byte{1}, types.SDKAddrLen) - otherAddr = bytes.Repeat([]byte{2}, types.SDKAddrLen) - anyAddr = bytes.Repeat([]byte{3}, types.SDKAddrLen) - ) - - initMsg := HackatomExampleInitMsg{ - Verifier: anyAddr, - Beneficiary: anyAddr, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - specs := map[string]struct { - srcPermission types.AccessConfig - srcActor sdk.AccAddress - expError *errorsmod.Error - }{ - "default": { - srcPermission: types.DefaultUploadAccess, - srcActor: anyAddr, - }, - "everybody": { - srcPermission: types.AllowEverybody, - srcActor: anyAddr, - }, - "nobody": { - srcPermission: types.AllowNobody, - srcActor: myAddr, - expError: sdkerrors.ErrUnauthorized, - }, - "onlyAddress with matching address": { - srcPermission: types.AccessTypeOnlyAddress.With(myAddr), - srcActor: myAddr, - }, - "onlyAddress with non matching address": { - srcActor: myAddr, - srcPermission: types.AccessTypeOnlyAddress.With(otherAddr), - expError: sdkerrors.ErrUnauthorized, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.ContractKeeper - fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, deposit) - - contractID, _, err := keeper.Create(ctx, myAddr, hackatomWasm, &spec.srcPermission) - require.NoError(t, err) - - _, _, err = keepers.ContractKeeper.Instantiate(ctx, contractID, spec.srcActor, nil, initMsgBz, "demo contract 1", nil) - assert.True(t, spec.expError.Is(err), "got %+v", err) - }) - } -} - -func TestInstantiateWithAccounts(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - example := StoreHackatomExampleContract(t, parentCtx, keepers) - require.Equal(t, uint64(1), example.CodeID) - initMsg := mustMarshal(t, HackatomExampleInitMsg{Verifier: RandomAccountAddress(t), Beneficiary: RandomAccountAddress(t)}) - - senderAddr := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(parentCtx, senderAddr, sdk.NewInt64Coin("denom", 100000000)) - mySalt := []byte(`my salt`) - contractAddr := BuildContractAddressPredictable(example.Checksum, senderAddr, mySalt, []byte{}) - - lastAccountNumber := keepers.AccountKeeper.GetAccount(parentCtx, senderAddr).GetAccountNumber() - - specs := map[string]struct { - option Option - account authtypes.AccountI - initBalance sdk.Coin - deposit sdk.Coins - expErr error - expAccount authtypes.AccountI - expBalance sdk.Coins - }{ - "unused BaseAccount exists": { - account: authtypes.NewBaseAccount(contractAddr, nil, 0, 0), - initBalance: sdk.NewInt64Coin("denom", 100000000), - expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+1, 0), // +1 for next seq - expBalance: sdk.NewCoins(sdk.NewInt64Coin("denom", 100000000)), - }, - "BaseAccount with sequence exists": { - account: authtypes.NewBaseAccount(contractAddr, nil, 0, 1), - expErr: types.ErrAccountExists, - }, - "BaseAccount with pubkey exists": { - account: authtypes.NewBaseAccount(contractAddr, &ed25519.PubKey{}, 0, 0), - expErr: types.ErrAccountExists, - }, - "no account existed": { - expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+1, 0), // +1 for next seq, - expBalance: sdk.NewCoins(), - }, - "no account existed before create with deposit": { - expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+1, 0), // +1 for next seq - deposit: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), - expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), - }, - "prunable DelayedVestingAccount gets overwritten": { - account: vestingtypes.NewDelayedVestingAccount( - authtypes.NewBaseAccount(contractAddr, nil, 0, 0), - sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix()), - initBalance: sdk.NewCoin("denom", sdk.NewInt(1_000)), - deposit: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1))), - expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+2, 0), // +1 for next seq, +1 for spec.account created - expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1))), - }, - "prunable ContinuousVestingAccount gets overwritten": { - account: vestingtypes.NewContinuousVestingAccount( - authtypes.NewBaseAccount(contractAddr, nil, 0, 0), - sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), time.Now().Add(time.Hour).Unix(), time.Now().Add(2*time.Hour).Unix()), - initBalance: sdk.NewCoin("denom", sdk.NewInt(1_000)), - deposit: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1))), - expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+2, 0), // +1 for next seq, +1 for spec.account created - expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1))), - }, - "prunable account without balance gets overwritten": { - account: vestingtypes.NewContinuousVestingAccount( - authtypes.NewBaseAccount(contractAddr, nil, 0, 0), - sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(0))), time.Now().Add(time.Hour).Unix(), time.Now().Add(2*time.Hour).Unix()), - expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+2, 0), // +1 for next seq, +1 for spec.account created - expBalance: sdk.NewCoins(), - }, - "unknown account type is rejected with error": { - account: authtypes.NewModuleAccount( - authtypes.NewBaseAccount(contractAddr, nil, 0, 0), - "testing", - ), - initBalance: sdk.NewCoin("denom", sdk.NewInt(1_000)), - expErr: types.ErrAccountExists, - }, - "with option used to set non default type to accept list": { - option: WithAcceptedAccountTypesOnContractInstantiation(&vestingtypes.DelayedVestingAccount{}), - account: vestingtypes.NewDelayedVestingAccount( - authtypes.NewBaseAccount(contractAddr, nil, 0, 0), - sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix()), - initBalance: sdk.NewCoin("denom", sdk.NewInt(1_000)), - deposit: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1))), - expAccount: vestingtypes.NewDelayedVestingAccount(authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+1, 0), - sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix()), - expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_001))), - }, - "pruning account fails": { - option: WithAccountPruner(wasmtesting.AccountPrunerMock{CleanupExistingAccountFn: func(ctx sdk.Context, existingAccount authtypes.AccountI) (handled bool, err error) { - return false, types.ErrUnsupportedForContract.Wrap("testing") - }}), - account: vestingtypes.NewDelayedVestingAccount( - authtypes.NewBaseAccount(contractAddr, nil, 0, 0), - sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix()), - expErr: types.ErrUnsupportedForContract, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - if spec.account != nil { - keepers.AccountKeeper.SetAccount(ctx, keepers.AccountKeeper.NewAccount(ctx, spec.account)) - } - if !spec.initBalance.IsNil() { - keepers.Faucet.Fund(ctx, spec.account.GetAddress(), spec.initBalance) - } - if spec.option != nil { - spec.option.apply(keepers.WasmKeeper) - } - defer func() { - if spec.option != nil { // reset - WithAcceptedAccountTypesOnContractInstantiation(&authtypes.BaseAccount{}).apply(keepers.WasmKeeper) - WithAccountPruner(NewVestingCoinBurner(keepers.BankKeeper)).apply(keepers.WasmKeeper) - } - }() - // when - gotAddr, _, gotErr := keepers.ContractKeeper.Instantiate2(ctx, 1, senderAddr, nil, initMsg, myTestLabel, spec.deposit, mySalt, false) - if spec.expErr != nil { - assert.ErrorIs(t, gotErr, spec.expErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, contractAddr, gotAddr) - // and - gotAcc := keepers.AccountKeeper.GetAccount(ctx, contractAddr) - assert.Equal(t, spec.expAccount, gotAcc) - // and - gotBalance := keepers.BankKeeper.GetAllBalances(ctx, contractAddr) - assert.Equal(t, spec.expBalance, gotBalance) - }) - } -} - -func TestInstantiateWithNonExistingCodeID(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - initMsg := HackatomExampleInitMsg{} - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - const nonExistingCodeID = 9999 - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, nonExistingCodeID, creator, nil, initMsgBz, "demo contract 2", nil) - require.True(t, types.ErrNotFound.Is(err), err) - require.Nil(t, addr) -} - -func TestInstantiateWithContractDataResponse(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - - wasmerMock := &wasmtesting.MockWasmer{ - InstantiateFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - return &wasmvmtypes.Response{Data: []byte("my-response-data")}, 0, nil - }, - AnalyzeCodeFn: wasmtesting.WithoutIBCAnalyzeFn, - CreateFn: wasmtesting.NoOpCreateFn, - } - - example := StoreRandomContract(t, ctx, keepers, wasmerMock) - _, data, err := keepers.ContractKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, nil, nil, "test", nil) - require.NoError(t, err) - assert.Equal(t, []byte("my-response-data"), data) -} - -func TestInstantiateWithContractFactoryChildQueriesParent(t *testing.T) { - // Scenario: - // given a factory contract stored - // when instantiated, the contract creates a new child contract instance - // and the child contracts queries the senders ContractInfo on instantiation - // then the factory contract's ContractInfo should be returned to the child contract - // - // see also: https://github.com/terpnetwork/terp-core/issues/896 - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - var instantiationCount int - callbacks := make([]func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error), 2) - wasmerMock := &wasmtesting.MockWasmer{ - // dispatch instantiation calls to callbacks - InstantiateFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - require.Greater(t, len(callbacks), instantiationCount, "unexpected call to instantiation") - do := callbacks[instantiationCount] - instantiationCount++ - return do(codeID, env, info, initMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) - }, - AnalyzeCodeFn: wasmtesting.WithoutIBCAnalyzeFn, - CreateFn: wasmtesting.NoOpCreateFn, - } - - // overwrite wasmvm in router - router := baseapp.NewMsgServiceRouter() - router.SetInterfaceRegistry(keepers.EncodingConfig.InterfaceRegistry) - types.RegisterMsgServer(router, NewMsgServerImpl(keeper)) - keeper.messenger = NewDefaultMessageHandler(router, nil, nil, nil, keepers.EncodingConfig.Marshaler, nil) - // overwrite wasmvm in response handler - keeper.wasmVMResponseHandler = NewDefaultWasmVMContractResponseHandler(NewMessageDispatcher(keeper.messenger, keeper)) - - example := StoreRandomContract(t, ctx, keepers, wasmerMock) - // factory contract - callbacks[0] = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - t.Log("called factory") - return &wasmvmtypes.Response{Data: []byte("parent"), Messages: []wasmvmtypes.SubMsg{ - { - ID: 1, ReplyOn: wasmvmtypes.ReplyNever, - Msg: wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - Instantiate: &wasmvmtypes.InstantiateMsg{CodeID: example.CodeID, Msg: []byte(`{}`), Label: "child"}, - }, - }, - }, - }}, 0, nil - } - - // child contract - var capturedSenderAddr string - var capturedCodeInfo []byte - callbacks[1] = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - t.Log("called child") - capturedSenderAddr = info.Sender - var err error - capturedCodeInfo, err = querier.Query(wasmvmtypes.QueryRequest{ - Wasm: &wasmvmtypes.WasmQuery{ - ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: info.Sender}, - }, - }, gasLimit) - require.NoError(t, err) - return &wasmvmtypes.Response{Data: []byte("child")}, 0, nil - } - - // when - parentAddr, data, err := keepers.ContractKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, nil, nil, "test", nil) - - // then - require.NoError(t, err) - assert.Equal(t, []byte("parent"), data) - require.Equal(t, parentAddr.String(), capturedSenderAddr) - expCodeInfo := fmt.Sprintf(`{"code_id":%d,"creator":%q,"pinned":false}`, example.CodeID, example.CreatorAddr.String()) - assert.JSONEq(t, expCodeInfo, string(capturedCodeInfo)) -} - -func TestExecute(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...) - fred := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...) - bob := RandomAccountAddress(t) - - contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit) - require.NoError(t, err) - require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", addr.String()) - - // ensure bob doesn't exist - bobAcct := accKeeper.GetAccount(ctx, bob) - require.Nil(t, bobAcct) - - // ensure funder has reduced balance - creatorAcct := accKeeper.GetAccount(ctx, creator) - require.NotNil(t, creatorAcct) - // we started at 2*deposit, should have spent one above - assert.Equal(t, deposit, bankKeeper.GetAllBalances(ctx, creatorAcct.GetAddress())) - - // ensure contract has updated balance - contractAcct := accKeeper.GetAccount(ctx, addr) - require.NotNil(t, contractAcct) - assert.Equal(t, deposit, bankKeeper.GetAllBalances(ctx, contractAcct.GetAddress())) - - // unauthorized - trialCtx so we don't change state - trialCtx := ctx.WithMultiStore(ctx.MultiStore().CacheWrap().(sdk.MultiStore)) - _, err = keepers.ContractKeeper.Execute(trialCtx, addr, creator, []byte(`{"release":{}}`), nil) - require.Error(t, err) - require.True(t, errors.Is(err, types.ErrExecuteFailed)) - require.Equal(t, "Unauthorized: execute wasm contract failed", err.Error()) - - // verifier can execute, and get proper gas amount - start := time.Now() - gasBefore := ctx.GasMeter().GasConsumed() - em := sdk.NewEventManager() - // when - res, err := keepers.ContractKeeper.Execute(ctx.WithEventManager(em), addr, fred, []byte(`{"release":{}}`), topUp) - diff := time.Since(start) - require.NoError(t, err) - require.NotNil(t, res) - - // make sure gas is properly deducted from ctx - gasAfter := ctx.GasMeter().GasConsumed() - if types.EnableGasVerification { - require.Equal(t, uint64(0x1a154), gasAfter-gasBefore) - } - // ensure bob now exists and got both payments released - bobAcct = accKeeper.GetAccount(ctx, bob) - require.NotNil(t, bobAcct) - balance := bankKeeper.GetAllBalances(ctx, bobAcct.GetAddress()) - assert.Equal(t, deposit.Add(topUp...), balance) - - // ensure contract has updated balance - contractAcct = accKeeper.GetAccount(ctx, addr) - require.NotNil(t, contractAcct) - assert.Equal(t, sdk.Coins{}, bankKeeper.GetAllBalances(ctx, contractAcct.GetAddress())) - - // and events emitted - require.Len(t, em.Events(), 9) - expEvt := sdk.NewEvent("execute", - sdk.NewAttribute("_contract_address", addr.String())) - assert.Equal(t, expEvt, em.Events()[3], prettyEvents(t, em.Events())) - - t.Logf("Duration: %v (%d gas)\n", diff, gasAfter-gasBefore) -} - -func TestExecuteWithDeposit(t *testing.T) { - var ( - bob = bytes.Repeat([]byte{1}, types.SDKAddrLen) - fred = bytes.Repeat([]byte{2}, types.SDKAddrLen) - blockedAddr = authtypes.NewModuleAddress(distributiontypes.ModuleName) - deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100)) - ) - - specs := map[string]struct { - srcActor sdk.AccAddress - beneficiary sdk.AccAddress - newBankParams *banktypes.Params - expError bool - fundAddr bool - }{ - "actor with funds": { - srcActor: bob, - fundAddr: true, - beneficiary: fred, - }, - "actor without funds": { - srcActor: bob, - beneficiary: fred, - expError: true, - }, - "blocked address as actor": { - srcActor: blockedAddr, - fundAddr: true, - beneficiary: fred, - expError: false, - }, - "coin transfer with all transfers disabled": { - srcActor: bob, - fundAddr: true, - beneficiary: fred, - newBankParams: &banktypes.Params{DefaultSendEnabled: false}, - expError: true, - }, - "coin transfer with transfer denom disabled": { - srcActor: bob, - fundAddr: true, - beneficiary: fred, - newBankParams: &banktypes.Params{ - DefaultSendEnabled: true, - SendEnabled: []*banktypes.SendEnabled{{Denom: "denom", Enabled: false}}, - }, - expError: true, - }, - "blocked address as beneficiary": { - srcActor: bob, - fundAddr: true, - beneficiary: blockedAddr, - expError: true, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.ContractKeeper - if spec.newBankParams != nil { - err := bankKeeper.SetParams(ctx, *spec.newBankParams) - require.NoError(t, err) - } - if spec.fundAddr { - fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, sdk.NewCoins(sdk.NewInt64Coin("denom", 200))) - } - codeID, _, err := keeper.Create(ctx, spec.srcActor, hackatomWasm, nil) - require.NoError(t, err) - - initMsg := HackatomExampleInitMsg{Verifier: spec.srcActor, Beneficiary: spec.beneficiary} - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, spec.srcActor, nil, initMsgBz, "my label", nil) - require.NoError(t, err) - - // when - _, err = keepers.ContractKeeper.Execute(ctx, contractAddr, spec.srcActor, []byte(`{"release":{}}`), deposit) - - // then - if spec.expError { - require.Error(t, err) - return - } - require.NoError(t, err) - balances := bankKeeper.GetAllBalances(ctx, spec.beneficiary) - assert.Equal(t, deposit, balances) - }) - } -} - -func TestExecuteWithNonExistingAddress(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...) - - // unauthorized - trialCtx so we don't change state - nonExistingAddress := RandomAccountAddress(t) - _, err := keeper.Execute(ctx, nonExistingAddress, creator, []byte(`{}`), nil) - require.True(t, types.ErrNotFound.Is(err), err) -} - -func TestExecuteWithPanic(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...) - fred := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...) - - contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - - _, bob := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 4", deposit) - require.NoError(t, err) - - // let's make sure we get a reasonable error, no panic/crash - _, err = keepers.ContractKeeper.Execute(ctx, addr, fred, []byte(`{"panic":{}}`), topUp) - require.Error(t, err) - require.True(t, errors.Is(err, types.ErrExecuteFailed)) - // test with contains as "Display" implementation of the Wasmer "RuntimeError" is different for Mac and Linux - assert.Contains(t, err.Error(), "Error calling the VM: Error executing Wasm: Wasmer runtime error: RuntimeError: Aborted: panicked at 'This page intentionally faulted', src/contract.rs:169:5: execute wasm contract failed") -} - -func TestExecuteWithCpuLoop(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...) - fred := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...) - - contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - - _, bob := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 5", deposit) - require.NoError(t, err) - - // make sure we set a limit before calling - var gasLimit uint64 = 400_000 - ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimit)) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - - // ensure we get an out of gas panic - defer func() { - r := recover() - require.NotNil(t, r) - _, ok := r.(sdk.ErrorOutOfGas) - require.True(t, ok, "%v", r) - }() - - // this should throw out of gas exception (panic) - _, _ = keepers.ContractKeeper.Execute(ctx, addr, fred, []byte(`{"cpu_loop":{}}`), nil) - require.True(t, false, "We must panic before this line") -} - -func TestExecuteWithStorageLoop(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...) - fred := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...) - - contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - - _, bob := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 6", deposit) - require.NoError(t, err) - - // make sure we set a limit before calling - var gasLimit uint64 = 400_002 - ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimit)) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - - // ensure we get an out of gas panic - defer func() { - r := recover() - require.NotNil(t, r) - _, ok := r.(sdk.ErrorOutOfGas) - require.True(t, ok, "%v", r) - }() - - // this should throw out of gas exception (panic) - _, _ = keepers.ContractKeeper.Execute(ctx, addr, fred, []byte(`{"storage_loop":{}}`), nil) - require.True(t, false, "We must panic before this line") -} - -func TestMigrate(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(parentCtx, creator, deposit.Add(deposit...)...) - fred := DeterministicAccountAddress(t, 2) - keepers.Faucet.Fund(parentCtx, fred, topUp...) - - originalCodeID := StoreHackatomExampleContract(t, parentCtx, keepers).CodeID - newCodeID := StoreHackatomExampleContract(t, parentCtx, keepers).CodeID - ibcCodeID := StoreIBCReflectContract(t, parentCtx, keepers).CodeID - require.NotEqual(t, originalCodeID, newCodeID) - - restrictedCodeExample := StoreHackatomExampleContract(t, parentCtx, keepers) - require.NoError(t, keeper.SetAccessConfig(parentCtx, restrictedCodeExample.CodeID, restrictedCodeExample.CreatorAddr, types.AllowNobody)) - require.NotEqual(t, originalCodeID, restrictedCodeExample.CodeID) - - anyAddr := RandomAccountAddress(t) - newVerifierAddr := RandomAccountAddress(t) - initMsgBz := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: anyAddr, - }.GetBytes(t) - - migMsg := struct { - Verifier sdk.AccAddress `json:"verifier"` - }{Verifier: newVerifierAddr} - migMsgBz, err := json.Marshal(migMsg) - require.NoError(t, err) - - specs := map[string]struct { - admin sdk.AccAddress - overrideContractAddr sdk.AccAddress - caller sdk.AccAddress - fromCodeID uint64 - toCodeID uint64 - migrateMsg []byte - expErr *errorsmod.Error - expVerifier sdk.AccAddress - expIBCPort bool - initMsg []byte - }{ - "all good with same code id": { - admin: creator, - caller: creator, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: originalCodeID, - migrateMsg: migMsgBz, - expVerifier: newVerifierAddr, - }, - "all good with different code id": { - admin: creator, - caller: creator, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: newCodeID, - migrateMsg: migMsgBz, - expVerifier: newVerifierAddr, - }, - "all good with admin set": { - admin: fred, - caller: fred, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: newCodeID, - migrateMsg: migMsgBz, - expVerifier: newVerifierAddr, - }, - "adds IBC port for IBC enabled contracts": { - admin: fred, - caller: fred, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: ibcCodeID, - migrateMsg: []byte(`{}`), - expIBCPort: true, - expVerifier: fred, // not updated - }, - "prevent migration when admin was not set on instantiate": { - caller: creator, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: originalCodeID, - expErr: sdkerrors.ErrUnauthorized, - }, - "prevent migration when not sent by admin": { - caller: creator, - admin: fred, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: originalCodeID, - expErr: sdkerrors.ErrUnauthorized, - }, - "prevent migration when new code is restricted": { - admin: creator, - caller: creator, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: restrictedCodeExample.CodeID, - migrateMsg: migMsgBz, - expErr: sdkerrors.ErrUnauthorized, - }, - "fail with non existing code id": { - admin: creator, - caller: creator, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: 99999, - expErr: sdkerrors.ErrInvalidRequest, - }, - "fail with non existing contract addr": { - admin: creator, - caller: creator, - initMsg: initMsgBz, - overrideContractAddr: anyAddr, - fromCodeID: originalCodeID, - toCodeID: originalCodeID, - expErr: sdkerrors.ErrInvalidRequest, - }, - "fail in contract with invalid migrate msg": { - admin: creator, - caller: creator, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: originalCodeID, - migrateMsg: bytes.Repeat([]byte{0x1}, 7), - expErr: types.ErrMigrationFailed, - }, - "fail in contract without migrate msg": { - admin: creator, - caller: creator, - initMsg: initMsgBz, - fromCodeID: originalCodeID, - toCodeID: originalCodeID, - expErr: types.ErrMigrationFailed, - }, - "fail when no IBC callbacks": { - admin: fred, - caller: fred, - initMsg: IBCReflectInitMsg{ReflectCodeID: StoreReflectContract(t, parentCtx, keepers).CodeID}.GetBytes(t), - fromCodeID: ibcCodeID, - toCodeID: newCodeID, - migrateMsg: migMsgBz, - expErr: types.ErrMigrationFailed, - }, - } - - blockHeight := parentCtx.BlockHeight() - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - // given a contract instance - ctx, _ := parentCtx.WithBlockHeight(blockHeight + 1).CacheContext() - blockHeight++ - - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, spec.fromCodeID, creator, spec.admin, spec.initMsg, "demo contract", nil) - require.NoError(t, err) - if spec.overrideContractAddr != nil { - contractAddr = spec.overrideContractAddr - } - // when - _, err = keeper.Migrate(ctx, contractAddr, spec.caller, spec.toCodeID, spec.migrateMsg) - - // then - require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err) - if spec.expErr != nil { - return - } - cInfo := keepers.WasmKeeper.GetContractInfo(ctx, contractAddr) - assert.Equal(t, spec.toCodeID, cInfo.CodeID) - assert.Equal(t, spec.expIBCPort, cInfo.IBCPortID != "", cInfo.IBCPortID) - - expHistory := []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: spec.fromCodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: initMsgBz, - }, { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: spec.toCodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: spec.migrateMsg, - }} - assert.Equal(t, expHistory, keepers.WasmKeeper.GetContractHistory(ctx, contractAddr)) - - // and verify contract state - raw := keepers.WasmKeeper.QueryRaw(ctx, contractAddr, []byte("config")) - var stored map[string]string - require.NoError(t, json.Unmarshal(raw, &stored)) - require.Contains(t, stored, "verifier") - require.NoError(t, err) - assert.Equal(t, spec.expVerifier.String(), stored["verifier"]) - }) - } -} - -func TestMigrateReplacesTheSecondIndex(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - example := InstantiateHackatomExampleContract(t, ctx, keepers) - - // then assert a second index exists - store := ctx.KVStore(keepers.WasmKeeper.storeKey) - oldContractInfo := keepers.WasmKeeper.GetContractInfo(ctx, example.Contract) - require.NotNil(t, oldContractInfo) - createHistoryEntry := types.ContractCodeHistoryEntry{ - CodeID: example.CodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - } - exists := store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, createHistoryEntry)) - require.True(t, exists) - - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // increment for different block - // when do migrate - newCodeExample := StoreBurnerExampleContract(t, ctx, keepers) - migMsgBz := BurnerExampleInitMsg{Payout: example.CreatorAddr}.GetBytes(t) - _, err := keepers.ContractKeeper.Migrate(ctx, example.Contract, example.CreatorAddr, newCodeExample.CodeID, migMsgBz) - require.NoError(t, err) - - // then the new index exists - migrateHistoryEntry := types.ContractCodeHistoryEntry{ - CodeID: newCodeExample.CodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - } - exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, migrateHistoryEntry)) - require.True(t, exists) - // and the old index was removed - exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, createHistoryEntry)) - require.False(t, exists) -} - -func TestMigrateWithDispatchedMessage(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...) - fred := keepers.Faucet.NewFundedRandomAccount(ctx, sdk.NewInt64Coin("denom", 5000)) - - burnerCode, err := os.ReadFile("./testdata/burner.wasm") - require.NoError(t, err) - - originalContractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - burnerContractID, _, err := keeper.Create(ctx, creator, burnerCode, nil) - require.NoError(t, err) - require.NotEqual(t, originalContractID, burnerContractID) - - _, myPayoutAddr := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: fred, - } - initMsgBz := initMsg.GetBytes(t) - - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, originalContractID, creator, fred, initMsgBz, "demo contract", deposit) - require.NoError(t, err) - - migMsgBz := BurnerExampleInitMsg{Payout: myPayoutAddr}.GetBytes(t) - ctx = ctx.WithEventManager(sdk.NewEventManager()).WithBlockHeight(ctx.BlockHeight() + 1) - data, err := keeper.Migrate(ctx, contractAddr, fred, burnerContractID, migMsgBz) - require.NoError(t, err) - assert.Equal(t, "burnt 1 keys", string(data)) - type dict map[string]interface{} - expEvents := []dict{ - { - "Type": "migrate", - "Attr": []dict{ - {"code_id": "2"}, - {"_contract_address": contractAddr}, - }, - }, - { - "Type": "wasm", - "Attr": []dict{ - {"_contract_address": contractAddr}, - {"action": "burn"}, - {"payout": myPayoutAddr}, - }, - }, - { - "Type": "coin_spent", - "Attr": []dict{ - {"spender": contractAddr}, - {"amount": "100000denom"}, - }, - }, - { - "Type": "coin_received", - "Attr": []dict{ - {"receiver": myPayoutAddr}, - {"amount": "100000denom"}, - }, - }, - { - "Type": "transfer", - "Attr": []dict{ - {"recipient": myPayoutAddr}, - {"sender": contractAddr}, - {"amount": "100000denom"}, - }, - }, - } - expJSONEvts := string(mustMarshal(t, expEvents)) - assert.JSONEq(t, expJSONEvts, prettyEvents(t, ctx.EventManager().Events()), prettyEvents(t, ctx.EventManager().Events())) - - // all persistent data cleared - m := keepers.WasmKeeper.QueryRaw(ctx, contractAddr, []byte("config")) - require.Len(t, m, 0) - - // and all deposit tokens sent to myPayoutAddr - balance := keepers.BankKeeper.GetAllBalances(ctx, myPayoutAddr) - assert.Equal(t, deposit, balance) -} - -func TestIterateContractsByCode(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - k, c := keepers.WasmKeeper, keepers.ContractKeeper - example1 := InstantiateHackatomExampleContract(t, ctx, keepers) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - example2 := InstantiateIBCReflectContract(t, ctx, keepers) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - initMsg := HackatomExampleInitMsg{ - Verifier: RandomAccountAddress(t), - Beneficiary: RandomAccountAddress(t), - }.GetBytes(t) - contractAddr3, _, err := c.Instantiate(ctx, example1.CodeID, example1.CreatorAddr, nil, initMsg, "foo", nil) - require.NoError(t, err) - specs := map[string]struct { - codeID uint64 - exp []sdk.AccAddress - }{ - "multiple results": { - codeID: example1.CodeID, - exp: []sdk.AccAddress{example1.Contract, contractAddr3}, - }, - "single results": { - codeID: example2.CodeID, - exp: []sdk.AccAddress{example2.Contract}, - }, - "empty results": { - codeID: 99999, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - var gotAddr []sdk.AccAddress - k.IterateContractsByCode(ctx, spec.codeID, func(address sdk.AccAddress) bool { - gotAddr = append(gotAddr, address) - return false - }) - assert.Equal(t, spec.exp, gotAddr) - }) - } -} - -func TestIterateContractsByCodeWithMigration(t *testing.T) { - // mock migration so that it does not fail when migrate example1 to example2.codeID - mockWasmVM := wasmtesting.MockWasmer{MigrateFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - return &wasmvmtypes.Response{}, 1, nil - }} - wasmtesting.MakeInstantiable(&mockWasmVM) - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithWasmEngine(&mockWasmVM)) - k, c := keepers.WasmKeeper, keepers.ContractKeeper - example1 := InstantiateHackatomExampleContract(t, ctx, keepers) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - example2 := InstantiateIBCReflectContract(t, ctx, keepers) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - _, err := c.Migrate(ctx, example1.Contract, example1.CreatorAddr, example2.CodeID, []byte("{}")) - require.NoError(t, err) - - // when - var gotAddr []sdk.AccAddress - k.IterateContractsByCode(ctx, example2.CodeID, func(address sdk.AccAddress) bool { - gotAddr = append(gotAddr, address) - return false - }) - - // then - exp := []sdk.AccAddress{example2.Contract, example1.Contract} - assert.Equal(t, exp, gotAddr) -} - -type sudoMsg struct { - // This is a tongue-in-check demo command. This is not the intended purpose of Sudo. - // Here we show that some priviledged Go module can make a call that should never be exposed - // to end users (via Tx/Execute). - // - // The contract developer can choose to expose anything to sudo. This functionality is not a true - // backdoor (it can never be called by end users), but allows the developers of the native blockchain - // code to make special calls. This can also be used as an authentication mechanism, if you want to expose - // some callback that only can be triggered by some system module and not faked by external users. - StealFunds stealFundsMsg `json:"steal_funds"` -} - -type stealFundsMsg struct { - Recipient string `json:"recipient"` - Amount wasmvmtypes.Coins `json:"amount"` -} - -func TestSudo(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...) - - contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil) - require.NoError(t, err) - - _, bob := keyPubAddr() - _, fred := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit) - require.NoError(t, err) - require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", addr.String()) - - // the community is broke - _, community := keyPubAddr() - comAcct := accKeeper.GetAccount(ctx, community) - require.Nil(t, comAcct) - - // now the community wants to get paid via sudo - msg := sudoMsg{ - // This is a tongue-in-check demo command. This is not the intended purpose of Sudo. - // Here we show that some priviledged Go module can make a call that should never be exposed - // to end users (via Tx/Execute). - StealFunds: stealFundsMsg{ - Recipient: community.String(), - Amount: wasmvmtypes.Coins{wasmvmtypes.NewCoin(76543, "denom")}, - }, - } - sudoMsg, err := json.Marshal(msg) - require.NoError(t, err) - - em := sdk.NewEventManager() - - // when - _, err = keepers.WasmKeeper.Sudo(ctx.WithEventManager(em), addr, sudoMsg) - require.NoError(t, err) - - // ensure community now exists and got paid - comAcct = accKeeper.GetAccount(ctx, community) - require.NotNil(t, comAcct) - balance := bankKeeper.GetBalance(ctx, comAcct.GetAddress(), "denom") - assert.Equal(t, sdk.NewInt64Coin("denom", 76543), balance) - // and events emitted - require.Len(t, em.Events(), 4, prettyEvents(t, em.Events())) - expEvt := sdk.NewEvent("sudo", - sdk.NewAttribute("_contract_address", addr.String())) - assert.Equal(t, expEvt, em.Events()[0]) -} - -func prettyEvents(t *testing.T, events sdk.Events) string { - t.Helper() - type prettyEvent struct { - Type string - Attr []map[string]string - } - - r := make([]prettyEvent, len(events)) - for i, e := range events { - attr := make([]map[string]string, len(e.Attributes)) - for j, a := range e.Attributes { - attr[j] = map[string]string{a.Key: a.Value} - } - r[i] = prettyEvent{Type: e.Type, Attr: attr} - } - return string(mustMarshal(t, r)) -} - -func mustMarshal(t *testing.T, r interface{}) []byte { - t.Helper() - bz, err := json.Marshal(r) - require.NoError(t, err) - return bz -} - -func TestUpdateContractAdmin(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(parentCtx, creator, deposit.Add(deposit...)...) - fred := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...) - - originalContractID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil) - require.NoError(t, err) - - _, anyAddr := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: anyAddr, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - specs := map[string]struct { - instAdmin sdk.AccAddress - newAdmin sdk.AccAddress - overrideContractAddr sdk.AccAddress - caller sdk.AccAddress - expErr *errorsmod.Error - }{ - "all good with admin set": { - instAdmin: fred, - newAdmin: anyAddr, - caller: fred, - }, - "prevent update when admin was not set on instantiate": { - caller: creator, - newAdmin: fred, - expErr: sdkerrors.ErrUnauthorized, - }, - "prevent updates from non admin address": { - instAdmin: creator, - newAdmin: fred, - caller: fred, - expErr: sdkerrors.ErrUnauthorized, - }, - "fail with non existing contract addr": { - instAdmin: creator, - newAdmin: anyAddr, - caller: creator, - overrideContractAddr: anyAddr, - expErr: sdkerrors.ErrInvalidRequest, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, originalContractID, creator, spec.instAdmin, initMsgBz, "demo contract", nil) - require.NoError(t, err) - if spec.overrideContractAddr != nil { - addr = spec.overrideContractAddr - } - err = keeper.UpdateContractAdmin(ctx, addr, spec.caller, spec.newAdmin) - require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err) - if spec.expErr != nil { - return - } - cInfo := keepers.WasmKeeper.GetContractInfo(ctx, addr) - assert.Equal(t, spec.newAdmin.String(), cInfo.Admin) - }) - } -} - -func TestClearContractAdmin(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(parentCtx, creator, deposit.Add(deposit...)...) - fred := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...) - - originalContractID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil) - require.NoError(t, err) - - _, anyAddr := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: anyAddr, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - specs := map[string]struct { - instAdmin sdk.AccAddress - overrideContractAddr sdk.AccAddress - caller sdk.AccAddress - expErr *errorsmod.Error - }{ - "all good when called by proper admin": { - instAdmin: fred, - caller: fred, - }, - "prevent update when admin was not set on instantiate": { - caller: creator, - expErr: sdkerrors.ErrUnauthorized, - }, - "prevent updates from non admin address": { - instAdmin: creator, - caller: fred, - expErr: sdkerrors.ErrUnauthorized, - }, - "fail with non existing contract addr": { - instAdmin: creator, - caller: creator, - overrideContractAddr: anyAddr, - expErr: sdkerrors.ErrInvalidRequest, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - addr, _, err := keepers.ContractKeeper.Instantiate(ctx, originalContractID, creator, spec.instAdmin, initMsgBz, "demo contract", nil) - require.NoError(t, err) - if spec.overrideContractAddr != nil { - addr = spec.overrideContractAddr - } - err = keeper.ClearContractAdmin(ctx, addr, spec.caller) - require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err) - if spec.expErr != nil { - return - } - cInfo := keepers.WasmKeeper.GetContractInfo(ctx, addr) - assert.Empty(t, cInfo.Admin) - }) - } -} - -func TestPinCode(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - k := keepers.WasmKeeper - - var capturedChecksums []wasmvm.Checksum - mock := wasmtesting.MockWasmer{PinFn: func(checksum wasmvm.Checksum) error { - capturedChecksums = append(capturedChecksums, checksum) - return nil - }} - wasmtesting.MakeInstantiable(&mock) - myCodeID := StoreRandomContract(t, ctx, keepers, &mock).CodeID - require.Equal(t, uint64(1), myCodeID) - em := sdk.NewEventManager() - - // when - gotErr := k.pinCode(ctx.WithEventManager(em), myCodeID) - - // then - require.NoError(t, gotErr) - assert.NotEmpty(t, capturedChecksums) - assert.True(t, k.IsPinnedCode(ctx, myCodeID)) - - // and events - exp := sdk.Events{sdk.NewEvent("pin_code", sdk.NewAttribute("code_id", "1"))} - assert.Equal(t, exp, em.Events()) -} - -func TestUnpinCode(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - k := keepers.WasmKeeper - - var capturedChecksums []wasmvm.Checksum - mock := wasmtesting.MockWasmer{ - PinFn: func(checksum wasmvm.Checksum) error { - return nil - }, - UnpinFn: func(checksum wasmvm.Checksum) error { - capturedChecksums = append(capturedChecksums, checksum) - return nil - }, - } - wasmtesting.MakeInstantiable(&mock) - myCodeID := StoreRandomContract(t, ctx, keepers, &mock).CodeID - require.Equal(t, uint64(1), myCodeID) - err := k.pinCode(ctx, myCodeID) - require.NoError(t, err) - em := sdk.NewEventManager() - - // when - gotErr := k.unpinCode(ctx.WithEventManager(em), myCodeID) - - // then - require.NoError(t, gotErr) - assert.NotEmpty(t, capturedChecksums) - assert.False(t, k.IsPinnedCode(ctx, myCodeID)) - - // and events - exp := sdk.Events{sdk.NewEvent("unpin_code", sdk.NewAttribute("code_id", "1"))} - assert.Equal(t, exp, em.Events()) -} - -func TestInitializePinnedCodes(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - k := keepers.WasmKeeper - - var capturedChecksums []wasmvm.Checksum - mock := wasmtesting.MockWasmer{PinFn: func(checksum wasmvm.Checksum) error { - capturedChecksums = append(capturedChecksums, checksum) - return nil - }} - wasmtesting.MakeInstantiable(&mock) - - const testItems = 3 - myCodeIDs := make([]uint64, testItems) - for i := 0; i < testItems; i++ { - myCodeIDs[i] = StoreRandomContract(t, ctx, keepers, &mock).CodeID - require.NoError(t, k.pinCode(ctx, myCodeIDs[i])) - } - capturedChecksums = nil - - // when - gotErr := k.InitializePinnedCodes(ctx) - - // then - require.NoError(t, gotErr) - require.Len(t, capturedChecksums, testItems) - for i, c := range myCodeIDs { - var exp wasmvm.Checksum = k.GetCodeInfo(ctx, c).CodeHash - assert.Equal(t, exp, capturedChecksums[i]) - } -} - -func TestPinnedContractLoops(t *testing.T) { - var capturedChecksums []wasmvm.Checksum - mock := wasmtesting.MockWasmer{PinFn: func(checksum wasmvm.Checksum) error { - capturedChecksums = append(capturedChecksums, checksum) - return nil - }} - wasmtesting.MakeInstantiable(&mock) - - // a pinned contract that calls itself via submessages should terminate with an - // error at some point - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithWasmEngine(&mock)) - k := keepers.WasmKeeper - - example := SeedNewContractInstance(t, ctx, keepers, &mock) - require.NoError(t, k.pinCode(ctx, example.CodeID)) - var loops int - anyMsg := []byte(`{}`) - mock.ExecuteFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - loops++ - return &wasmvmtypes.Response{ - Messages: []wasmvmtypes.SubMsg{ - { - ID: 1, - ReplyOn: wasmvmtypes.ReplyNever, - Msg: wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - Execute: &wasmvmtypes.ExecuteMsg{ - ContractAddr: example.Contract.String(), - Msg: anyMsg, - }, - }, - }, - }, - }, - }, 0, nil - } - ctx = ctx.WithGasMeter(sdk.NewGasMeter(20000)) - require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "ReadFlat"}, func() { - _, err := k.execute(ctx, example.Contract, RandomAccountAddress(t), anyMsg, nil) - require.NoError(t, err) - }) - assert.True(t, ctx.GasMeter().IsOutOfGas()) - assert.Greater(t, loops, 2) -} - -func TestNewDefaultWasmVMContractResponseHandler(t *testing.T) { - specs := map[string]struct { - srcData []byte - setup func(m *wasmtesting.MockMsgDispatcher) - expErr bool - expData []byte - expEvts sdk.Events - }{ - "submessage overwrites result when set": { - srcData: []byte("otherData"), - setup: func(m *wasmtesting.MockMsgDispatcher) { - m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) { - return []byte("mySubMsgData"), nil - } - }, - expErr: false, - expData: []byte("mySubMsgData"), - expEvts: sdk.Events{}, - }, - "submessage overwrites result when empty": { - srcData: []byte("otherData"), - setup: func(m *wasmtesting.MockMsgDispatcher) { - m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) { - return []byte(""), nil - } - }, - expErr: false, - expData: []byte(""), - expEvts: sdk.Events{}, - }, - "submessage do not overwrite result when nil": { - srcData: []byte("otherData"), - setup: func(m *wasmtesting.MockMsgDispatcher) { - m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) { - return nil, nil - } - }, - expErr: false, - expData: []byte("otherData"), - expEvts: sdk.Events{}, - }, - "submessage error aborts process": { - setup: func(m *wasmtesting.MockMsgDispatcher) { - m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) { - return nil, errors.New("test - ignore") - } - }, - expErr: true, - }, - "message emit non message events": { - setup: func(m *wasmtesting.MockMsgDispatcher) { - m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) { - ctx.EventManager().EmitEvent(sdk.NewEvent("myEvent")) - return nil, nil - } - }, - expEvts: sdk.Events{sdk.NewEvent("myEvent")}, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - var msgs []wasmvmtypes.SubMsg - var mock wasmtesting.MockMsgDispatcher - spec.setup(&mock) - d := NewDefaultWasmVMContractResponseHandler(&mock) - em := sdk.NewEventManager() - - // when - gotData, gotErr := d.Handle(sdk.Context{}.WithEventManager(em), RandomAccountAddress(t), "ibc-port", msgs, spec.srcData) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expData, gotData) - assert.Equal(t, spec.expEvts, em.Events()) - }) - } -} - -func TestReply(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - k := keepers.WasmKeeper - var mock wasmtesting.MockWasmer - wasmtesting.MakeInstantiable(&mock) - example := SeedNewContractInstance(t, ctx, keepers, &mock) - - specs := map[string]struct { - replyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) - expData []byte - expErr bool - expEvt sdk.Events - }{ - "all good": { - replyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - return &wasmvmtypes.Response{Data: []byte("foo")}, 1, nil - }, - expData: []byte("foo"), - expEvt: sdk.Events{sdk.NewEvent("reply", sdk.NewAttribute("_contract_address", example.Contract.String()))}, - }, - "with query": { - replyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - bzRsp, err := querier.Query(wasmvmtypes.QueryRequest{ - Bank: &wasmvmtypes.BankQuery{ - Balance: &wasmvmtypes.BalanceQuery{Address: env.Contract.Address, Denom: "stake"}, - }, - }, 10_000*DefaultGasMultiplier) - require.NoError(t, err) - var gotBankRsp wasmvmtypes.BalanceResponse - require.NoError(t, json.Unmarshal(bzRsp, &gotBankRsp)) - assert.Equal(t, wasmvmtypes.BalanceResponse{Amount: wasmvmtypes.NewCoin(0, "stake")}, gotBankRsp) - return &wasmvmtypes.Response{Data: []byte("foo")}, 1, nil - }, - expData: []byte("foo"), - expEvt: sdk.Events{sdk.NewEvent("reply", sdk.NewAttribute("_contract_address", example.Contract.String()))}, - }, - "with query error handled": { - replyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - bzRsp, err := querier.Query(wasmvmtypes.QueryRequest{}, 0) - require.Error(t, err) - assert.Nil(t, bzRsp) - return &wasmvmtypes.Response{Data: []byte("foo")}, 1, nil - }, - expData: []byte("foo"), - expEvt: sdk.Events{sdk.NewEvent("reply", sdk.NewAttribute("_contract_address", example.Contract.String()))}, - }, - "error": { - replyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - return nil, 1, errors.New("testing") - }, - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - mock.ReplyFn = spec.replyFn - em := sdk.NewEventManager() - gotData, gotErr := k.reply(ctx.WithEventManager(em), example.Contract, wasmvmtypes.Reply{}) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expData, gotData) - assert.Equal(t, spec.expEvt, em.Events()) - }) - } -} - -func TestQueryIsolation(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - k := keepers.WasmKeeper - var mock wasmtesting.MockWasmer - wasmtesting.MakeInstantiable(&mock) - example := SeedNewContractInstance(t, ctx, keepers, &mock) - WithQueryHandlerDecorator(func(other WasmVMQueryHandler) WasmVMQueryHandler { - return WasmVMQueryHandlerFn(func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { - if request.Custom == nil { - return other.HandleQuery(ctx, caller, request) - } - // here we write to DB which should not be persisted - ctx.KVStore(k.storeKey).Set([]byte(`set_in_query`), []byte(`this_is_allowed`)) - return nil, nil - }) - }).apply(k) - - // when - mock.ReplyFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - _, err := querier.Query(wasmvmtypes.QueryRequest{ - Custom: []byte(`{}`), - }, 10000*DefaultGasMultiplier) - require.NoError(t, err) - return &wasmvmtypes.Response{}, 0, nil - } - em := sdk.NewEventManager() - _, gotErr := k.reply(ctx.WithEventManager(em), example.Contract, wasmvmtypes.Reply{}) - require.NoError(t, gotErr) - assert.Nil(t, ctx.KVStore(k.storeKey).Get([]byte(`set_in_query`))) -} - -func TestSetAccessConfig(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - k := keepers.WasmKeeper - creatorAddr := RandomAccountAddress(t) - nonCreatorAddr := RandomAccountAddress(t) - const codeID = 1 - - specs := map[string]struct { - authz AuthorizationPolicy - chainPermission types.AccessType - newConfig types.AccessConfig - caller sdk.AccAddress - expErr bool - expEvts map[string]string - }{ - "user with new permissions == chain permissions": { - authz: DefaultAuthorizationPolicy{}, - chainPermission: types.AccessTypeEverybody, - newConfig: types.AllowEverybody, - caller: creatorAddr, - expEvts: map[string]string{ - "code_id": "1", - "code_permission": "Everybody", - }, - }, - "user with new permissions < chain permissions": { - authz: DefaultAuthorizationPolicy{}, - chainPermission: types.AccessTypeEverybody, - newConfig: types.AllowNobody, - caller: creatorAddr, - expEvts: map[string]string{ - "code_id": "1", - "code_permission": "Nobody", - }, - }, - "user with new permissions > chain permissions": { - authz: DefaultAuthorizationPolicy{}, - chainPermission: types.AccessTypeNobody, - newConfig: types.AllowEverybody, - caller: creatorAddr, - expErr: true, - }, - "different actor": { - authz: DefaultAuthorizationPolicy{}, - chainPermission: types.AccessTypeEverybody, - newConfig: types.AllowEverybody, - caller: nonCreatorAddr, - expErr: true, - }, - "gov with new permissions == chain permissions": { - authz: GovAuthorizationPolicy{}, - chainPermission: types.AccessTypeEverybody, - newConfig: types.AllowEverybody, - caller: creatorAddr, - expEvts: map[string]string{ - "code_id": "1", - "code_permission": "Everybody", - }, - }, - "gov with new permissions < chain permissions": { - authz: GovAuthorizationPolicy{}, - chainPermission: types.AccessTypeEverybody, - newConfig: types.AllowNobody, - caller: creatorAddr, - expEvts: map[string]string{ - "code_id": "1", - "code_permission": "Nobody", - }, - }, - "gov with new permissions > chain permissions": { - authz: GovAuthorizationPolicy{}, - chainPermission: types.AccessTypeNobody, - newConfig: types.AccessTypeOnlyAddress.With(creatorAddr), - caller: creatorAddr, - expEvts: map[string]string{ - "code_id": "1", - "code_permission": "OnlyAddress", - "authorized_addresses": creatorAddr.String(), - }, - }, - "gov with new permissions > chain permissions - multiple addresses": { - authz: GovAuthorizationPolicy{}, - chainPermission: types.AccessTypeNobody, - newConfig: types.AccessTypeAnyOfAddresses.With(creatorAddr, nonCreatorAddr), - caller: creatorAddr, - expEvts: map[string]string{ - "code_id": "1", - "code_permission": "AnyOfAddresses", - "authorized_addresses": creatorAddr.String() + "," + nonCreatorAddr.String(), - }, - }, - "gov without actor": { - authz: GovAuthorizationPolicy{}, - chainPermission: types.AccessTypeEverybody, - newConfig: types.AllowEverybody, - expEvts: map[string]string{ - "code_id": "1", - "code_permission": "Everybody", - }, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - em := sdk.NewEventManager() - ctx = ctx.WithEventManager(em) - - newParams := types.DefaultParams() - newParams.InstantiateDefaultPermission = spec.chainPermission - err := k.SetParams(ctx, newParams) - require.NoError(t, err) - - k.storeCodeInfo(ctx, codeID, types.NewCodeInfo(nil, creatorAddr, types.AllowNobody)) - // when - gotErr := k.setAccessConfig(ctx, codeID, spec.caller, spec.newConfig, spec.authz) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - // and event emitted - require.Len(t, em.Events(), 1) - assert.Equal(t, "update_code_access_config", em.Events()[0].Type) - assert.Equal(t, spec.expEvts, attrsToStringMap(em.Events()[0].Attributes)) - }) - } -} - -func TestAppendToContractHistory(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - var contractAddr sdk.AccAddress = rand.Bytes(types.ContractAddrLen) - var orderedEntries []types.ContractCodeHistoryEntry - - f := fuzz.New().Funcs(ModelFuzzers...) - for i := 0; i < 10; i++ { - var entry types.ContractCodeHistoryEntry - f.Fuzz(&entry) - keepers.WasmKeeper.appendToContractHistory(ctx, contractAddr, entry) - orderedEntries = append(orderedEntries, entry) - } - // when - gotHistory := keepers.WasmKeeper.GetContractHistory(ctx, contractAddr) - assert.Equal(t, orderedEntries, gotHistory) -} - -func TestCoinBurnerPruneBalances(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - amts := sdk.NewCoins(sdk.NewInt64Coin("denom", 100)) - senderAddr := keepers.Faucet.NewFundedRandomAccount(parentCtx, amts...) - - // create vesting account - var vestingAddr sdk.AccAddress = rand.Bytes(types.ContractAddrLen) - msgCreateVestingAccount := vestingtypes.NewMsgCreateVestingAccount(senderAddr, vestingAddr, amts, time.Now().Add(time.Minute).Unix(), false) - _, err := vesting.NewMsgServerImpl(keepers.AccountKeeper, keepers.BankKeeper).CreateVestingAccount(sdk.WrapSDKContext(parentCtx), msgCreateVestingAccount) - require.NoError(t, err) - myVestingAccount := keepers.AccountKeeper.GetAccount(parentCtx, vestingAddr) - require.NotNil(t, myVestingAccount) - - specs := map[string]struct { - setupAcc func(t *testing.T, ctx sdk.Context) authtypes.AccountI - expBalances sdk.Coins - expHandled bool - expErr *errorsmod.Error - }{ - "vesting account - all removed": { - setupAcc: func(t *testing.T, ctx sdk.Context) authtypes.AccountI { return myVestingAccount }, - - expBalances: sdk.NewCoins(), - expHandled: true, - }, - "vesting account with other tokens - only original denoms removed": { - setupAcc: func(t *testing.T, ctx sdk.Context) authtypes.AccountI { - keepers.Faucet.Fund(ctx, vestingAddr, sdk.NewCoin("other", sdk.NewInt(2))) - return myVestingAccount - }, - expBalances: sdk.NewCoins(sdk.NewCoin("other", sdk.NewInt(2))), - expHandled: true, - }, - "non vesting account - not handled": { - setupAcc: func(t *testing.T, ctx sdk.Context) authtypes.AccountI { - return &authtypes.BaseAccount{Address: myVestingAccount.GetAddress().String()} - }, - expBalances: sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(100))), - expHandled: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - existingAccount := spec.setupAcc(t, ctx) - // overwrite account in store as in keeper before calling prune - keepers.AccountKeeper.SetAccount(ctx, keepers.AccountKeeper.NewAccountWithAddress(ctx, vestingAddr)) - - // when - noGasCtx := ctx.WithGasMeter(sdk.NewGasMeter(0)) // should not use callers gas - gotHandled, gotErr := NewVestingCoinBurner(keepers.BankKeeper).CleanupExistingAccount(noGasCtx, existingAccount) - // then - if spec.expErr != nil { - require.ErrorIs(t, gotErr, spec.expErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expBalances, keepers.BankKeeper.GetAllBalances(ctx, vestingAddr)) - assert.Equal(t, spec.expHandled, gotHandled) - // and no out of gas panic - }) - } -} - -func TestIteratorAllContract(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - example1 := InstantiateHackatomExampleContract(t, ctx, keepers) - example2 := InstantiateHackatomExampleContract(t, ctx, keepers) - example3 := InstantiateHackatomExampleContract(t, ctx, keepers) - example4 := InstantiateHackatomExampleContract(t, ctx, keepers) - - var allContract []string - keepers.WasmKeeper.IterateContractInfo(ctx, func(addr sdk.AccAddress, _ types.ContractInfo) bool { - allContract = append(allContract, addr.String()) - return false - }) - - // IterateContractInfo not ordering - expContracts := []string{example4.Contract.String(), example2.Contract.String(), example1.Contract.String(), example3.Contract.String()} - require.Equal(t, allContract, expContracts) -} - -func TestIteratorContractByCreator(t *testing.T) { - // setup test - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.ContractKeeper - - depositFund := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)) - creator := DeterministicAccountAddress(t, 1) - keepers.Faucet.Fund(parentCtx, creator, depositFund.Add(depositFund...)...) - mockAddress1 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...) - mockAddress2 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...) - mockAddress3 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...) - - contract1ID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil) - require.NoError(t, err) - - contract2ID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil) - require.NoError(t, err) - - initMsgBz := HackatomExampleInitMsg{ - Verifier: mockAddress1, - Beneficiary: mockAddress1, - }.GetBytes(t) - - depositContract := sdk.NewCoins(sdk.NewCoin("denom", sdk.NewInt(1_000))) - - gotAddr1, _, _ := keepers.ContractKeeper.Instantiate(parentCtx, contract1ID, mockAddress1, nil, initMsgBz, "label", depositContract) - ctx := parentCtx.WithBlockHeight(parentCtx.BlockHeight() + 1) - gotAddr2, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract1ID, mockAddress2, nil, initMsgBz, "label", depositContract) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - gotAddr3, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract1ID, gotAddr1, nil, initMsgBz, "label", depositContract) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - gotAddr4, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract2ID, mockAddress2, nil, initMsgBz, "label", depositContract) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - gotAddr5, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract2ID, mockAddress2, nil, initMsgBz, "label", depositContract) - - specs := map[string]struct { - creatorAddr sdk.AccAddress - contractsAddr []string - }{ - "single contract": { - creatorAddr: mockAddress1, - contractsAddr: []string{gotAddr1.String()}, - }, - "multiple contracts": { - creatorAddr: mockAddress2, - contractsAddr: []string{gotAddr2.String(), gotAddr4.String(), gotAddr5.String()}, - }, - "contractAdress": { - creatorAddr: gotAddr1, - contractsAddr: []string{gotAddr3.String()}, - }, - "no contracts- unknown": { - creatorAddr: mockAddress3, - contractsAddr: nil, - }, - } - - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - var allContract []string - keepers.WasmKeeper.IterateContractsByCreator(parentCtx, spec.creatorAddr, func(addr sdk.AccAddress) bool { - allContract = append(allContract, addr.String()) - return false - }) - require.Equal(t, - allContract, - spec.contractsAddr, - ) - }) - } -} - -func TestSetContractAdmin(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities) - k := keepers.WasmKeeper - myAddr := RandomAccountAddress(t) - example := InstantiateReflectExampleContract(t, parentCtx, keepers) - specs := map[string]struct { - newAdmin sdk.AccAddress - caller sdk.AccAddress - policy AuthorizationPolicy - expAdmin string - expErr bool - }{ - "update admin": { - newAdmin: myAddr, - caller: example.CreatorAddr, - policy: DefaultAuthorizationPolicy{}, - expAdmin: myAddr.String(), - }, - "update admin - unauthorized": { - newAdmin: myAddr, - caller: RandomAccountAddress(t), - policy: DefaultAuthorizationPolicy{}, - expErr: true, - }, - "clear admin - default policy": { - caller: example.CreatorAddr, - policy: DefaultAuthorizationPolicy{}, - expAdmin: "", - }, - "clear admin - unauthorized": { - expAdmin: "", - policy: DefaultAuthorizationPolicy{}, - caller: RandomAccountAddress(t), - expErr: true, - }, - "clear admin - gov policy": { - newAdmin: nil, - policy: GovAuthorizationPolicy{}, - caller: example.CreatorAddr, - expAdmin: "", - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - em := sdk.NewEventManager() - ctx = ctx.WithEventManager(em) - gotErr := k.setContractAdmin(ctx, example.Contract, spec.caller, spec.newAdmin, spec.policy) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expAdmin, k.GetContractInfo(ctx, example.Contract).Admin) - // and event emitted - require.Len(t, em.Events(), 1) - assert.Equal(t, "update_contract_admin", em.Events()[0].Type) - exp := map[string]string{ - "_contract_address": example.Contract.String(), - "new_admin_address": spec.expAdmin, - } - assert.Equal(t, exp, attrsToStringMap(em.Events()[0].Attributes)) - }) - } -} - -func attrsToStringMap(attrs []abci.EventAttribute) map[string]string { - r := make(map[string]string, len(attrs)) - for _, v := range attrs { - r[v.Key] = v.Value - } - return r -} diff --git a/x/wasm/keeper/metrics.go b/x/wasm/keeper/metrics.go deleted file mode 100644 index 4c4b959..0000000 --- a/x/wasm/keeper/metrics.go +++ /dev/null @@ -1,72 +0,0 @@ -package keeper - -import ( - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/prometheus/client_golang/prometheus" -) - -const ( - labelPinned = "pinned" - labelMemory = "memory" - labelFs = "fs" -) - -// metricSource source of wasmvm metrics -type metricSource interface { - GetMetrics() (*wasmvmtypes.Metrics, error) -} - -var _ prometheus.Collector = (*WasmVMMetricsCollector)(nil) - -// WasmVMMetricsCollector custom metrics collector to be used with Prometheus -type WasmVMMetricsCollector struct { - source metricSource - CacheHitsDescr *prometheus.Desc - CacheMissesDescr *prometheus.Desc - CacheElementsDescr *prometheus.Desc - CacheSizeDescr *prometheus.Desc -} - -// NewWasmVMMetricsCollector constructor -func NewWasmVMMetricsCollector(s metricSource) *WasmVMMetricsCollector { - return &WasmVMMetricsCollector{ - source: s, - CacheHitsDescr: prometheus.NewDesc("wasmvm_cache_hits_total", "Total number of cache hits", []string{"type"}, nil), - CacheMissesDescr: prometheus.NewDesc("wasmvm_cache_misses_total", "Total number of cache misses", nil, nil), - CacheElementsDescr: prometheus.NewDesc("wasmvm_cache_elements_total", "Total number of elements in the cache", []string{"type"}, nil), - CacheSizeDescr: prometheus.NewDesc("wasmvm_cache_size_bytes", "Total number of elements in the cache", []string{"type"}, nil), - } -} - -// Register registers all metrics -func (p *WasmVMMetricsCollector) Register(r prometheus.Registerer) { - r.MustRegister(p) -} - -// Describe sends the super-set of all possible descriptors of metrics -func (p *WasmVMMetricsCollector) Describe(descs chan<- *prometheus.Desc) { - descs <- p.CacheHitsDescr - descs <- p.CacheMissesDescr - descs <- p.CacheElementsDescr - descs <- p.CacheSizeDescr -} - -// Collect is called by the Prometheus registry when collecting metrics. -func (p *WasmVMMetricsCollector) Collect(c chan<- prometheus.Metric) { - m, err := p.source.GetMetrics() - if err != nil { - return - } - c <- prometheus.MustNewConstMetric(p.CacheHitsDescr, prometheus.CounterValue, float64(m.HitsPinnedMemoryCache), labelPinned) - c <- prometheus.MustNewConstMetric(p.CacheHitsDescr, prometheus.CounterValue, float64(m.HitsMemoryCache), labelMemory) - c <- prometheus.MustNewConstMetric(p.CacheHitsDescr, prometheus.CounterValue, float64(m.HitsFsCache), labelFs) - c <- prometheus.MustNewConstMetric(p.CacheMissesDescr, prometheus.CounterValue, float64(m.Misses)) - c <- prometheus.MustNewConstMetric(p.CacheElementsDescr, prometheus.GaugeValue, float64(m.ElementsPinnedMemoryCache), labelPinned) - c <- prometheus.MustNewConstMetric(p.CacheElementsDescr, prometheus.GaugeValue, float64(m.ElementsMemoryCache), labelMemory) - c <- prometheus.MustNewConstMetric(p.CacheSizeDescr, prometheus.GaugeValue, float64(m.SizeMemoryCache), labelMemory) - c <- prometheus.MustNewConstMetric(p.CacheSizeDescr, prometheus.GaugeValue, float64(m.SizePinnedMemoryCache), labelPinned) - // Node about fs metrics: - // The number of elements and the size of elements in the file system cache cannot easily be obtained. - // We had to either scan the whole directory of potentially thousands of files or track the values when files are added or removed. - // Such a tracking would need to be on disk such that the values are not cleared when the node is restarted. -} diff --git a/x/wasm/keeper/migrations.go b/x/wasm/keeper/migrations.go deleted file mode 100644 index 7ce1211..0000000 --- a/x/wasm/keeper/migrations.go +++ /dev/null @@ -1,32 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - - v1 "github.com/terpnetwork/terp-core/x/wasm/migrations/v1" - v2 "github.com/terpnetwork/terp-core/x/wasm/migrations/v2" - - "github.com/terpnetwork/terp-core/x/wasm/exported" -) - -// Migrator is a struct for handling in-place store migrations. -type Migrator struct { - keeper Keeper - legacySubspace exported.Subspace -} - -// NewMigrator returns a new Migrator. -func NewMigrator(keeper Keeper, legacySubspace exported.Subspace) Migrator { - return Migrator{keeper: keeper, legacySubspace: legacySubspace} -} - -// Migrate1to2 migrates from version 1 to 2. -func (m Migrator) Migrate1to2(ctx sdk.Context) error { - return v1.NewMigrator(m.keeper, m.keeper.addToContractCreatorSecondaryIndex).Migrate1to2(ctx) -} - -// Migrate2to3 migrates the x/wasm module state from the consensus -// version 2 to version 3. -func (m Migrator) Migrate2to3(ctx sdk.Context) error { - return v2.MigrateStore(ctx, m.keeper.storeKey, m.legacySubspace, m.keeper.cdc) -} diff --git a/x/wasm/keeper/migrations_integration_test.go b/x/wasm/keeper/migrations_integration_test.go deleted file mode 100644 index 0e7a033..0000000 --- a/x/wasm/keeper/migrations_integration_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package keeper_test - -import ( - "testing" - - "github.com/terpnetwork/terp-core/x/wasm/types" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/app" - "github.com/terpnetwork/terp-core/x/wasm" -) - -func TestModuleMigrations(t *testing.T) { - wasmApp := app.Setup(t) - upgradeHandler := func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { //nolint:unparam - return wasmApp.ModuleManager.RunMigrations(ctx, wasmApp.Configurator(), fromVM) - } - - specs := map[string]struct { - setup func(ctx sdk.Context) - startVersion uint64 - exp types.Params - }{ - "with legacy params migrated": { - startVersion: 1, - setup: func(ctx sdk.Context) { - params := types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - } - sp, _ := wasmApp.ParamsKeeper.GetSubspace(types.ModuleName) - sp.SetParamSet(ctx, ¶ms) - }, - exp: types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }, - }, - "fresh from genesis": { - startVersion: wasmApp.ModuleManager.GetVersionMap()[types.ModuleName], // latest - setup: func(ctx sdk.Context) {}, - exp: types.DefaultParams(), - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - ctx, _ := wasmApp.BaseApp.NewContext(false, tmproto.Header{}).CacheContext() - spec.setup(ctx) - - fromVM := wasmApp.UpgradeKeeper.GetModuleVersionMap(ctx) - fromVM[wasm.ModuleName] = spec.startVersion - _, err := upgradeHandler(ctx, upgradetypes.Plan{Name: "testing"}, fromVM) - require.NoError(t, err) - - // when - gotVM, err := wasmApp.ModuleManager.RunMigrations(ctx, wasmApp.Configurator(), fromVM) - - // then - require.NoError(t, err) - var expModuleVersion uint64 = 3 - assert.Equal(t, expModuleVersion, gotVM[wasm.ModuleName]) - gotParams := wasmApp.WasmKeeper.GetParams(ctx) - assert.Equal(t, spec.exp, gotParams) - }) - } -} diff --git a/x/wasm/keeper/msg_dispatcher.go b/x/wasm/keeper/msg_dispatcher.go deleted file mode 100644 index 392dbd2..0000000 --- a/x/wasm/keeper/msg_dispatcher.go +++ /dev/null @@ -1,223 +0,0 @@ -package keeper - -import ( - "fmt" - "sort" - "strings" - - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - abci "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// Messenger is an extension point for custom wasmd message handling -type Messenger interface { - // DispatchMsg encodes the wasmVM message and dispatches it. - DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) -} - -// replyer is a subset of keeper that can handle replies to submessages -type replyer interface { - reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) -} - -// MessageDispatcher coordinates message sending and submessage reply/ state commits -type MessageDispatcher struct { - messenger Messenger - keeper replyer -} - -// NewMessageDispatcher constructor -func NewMessageDispatcher(messenger Messenger, keeper replyer) *MessageDispatcher { - return &MessageDispatcher{messenger: messenger, keeper: keeper} -} - -// DispatchMessages sends all messages. -func (d MessageDispatcher) DispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error { - for _, msg := range msgs { - events, _, err := d.messenger.DispatchMsg(ctx, contractAddr, ibcPort, msg) - if err != nil { - return err - } - // redispatch all events, (type sdk.EventTypeMessage will be filtered out in the handler) - ctx.EventManager().EmitEvents(events) - } - return nil -} - -// dispatchMsgWithGasLimit sends a message with gas limit applied -func (d MessageDispatcher) dispatchMsgWithGasLimit(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msg wasmvmtypes.CosmosMsg, gasLimit uint64) (events []sdk.Event, data [][]byte, err error) { - limitedMeter := sdk.NewGasMeter(gasLimit) - subCtx := ctx.WithGasMeter(limitedMeter) - - // catch out of gas panic and just charge the entire gas limit - defer func() { - if r := recover(); r != nil { - // if it's not an OutOfGas error, raise it again - if _, ok := r.(sdk.ErrorOutOfGas); !ok { - // log it to get the original stack trace somewhere (as panic(r) keeps message but stacktrace to here - moduleLogger(ctx).Info("SubMsg rethrowing panic: %#v", r) - panic(r) - } - ctx.GasMeter().ConsumeGas(gasLimit, "Sub-Message OutOfGas panic") - err = errorsmod.Wrap(sdkerrors.ErrOutOfGas, "SubMsg hit gas limit") - } - }() - events, data, err = d.messenger.DispatchMsg(subCtx, contractAddr, ibcPort, msg) - - // make sure we charge the parent what was spent - spent := subCtx.GasMeter().GasConsumed() - ctx.GasMeter().ConsumeGas(spent, "From limited Sub-Message") - - return events, data, err -} - -// DispatchSubmessages builds a sandbox to execute these messages and returns the execution result to the contract -// that dispatched them, both on success as well as failure -func (d MessageDispatcher) DispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) { - var rsp []byte - for _, msg := range msgs { - switch msg.ReplyOn { - case wasmvmtypes.ReplySuccess, wasmvmtypes.ReplyError, wasmvmtypes.ReplyAlways, wasmvmtypes.ReplyNever: - default: - return nil, errorsmod.Wrap(types.ErrInvalid, "replyOn value") - } - // first, we build a sub-context which we can use inside the submessages - subCtx, commit := ctx.CacheContext() - em := sdk.NewEventManager() - subCtx = subCtx.WithEventManager(em) - - // check how much gas left locally, optionally wrap the gas meter - gasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumed() - limitGas := msg.GasLimit != nil && (*msg.GasLimit < gasRemaining) - - var err error - var events []sdk.Event - var data [][]byte - if limitGas { - events, data, err = d.dispatchMsgWithGasLimit(subCtx, contractAddr, ibcPort, msg.Msg, *msg.GasLimit) - } else { - events, data, err = d.messenger.DispatchMsg(subCtx, contractAddr, ibcPort, msg.Msg) - } - - // if it succeeds, commit state changes from submessage, and pass on events to Event Manager - var filteredEvents []sdk.Event - if err == nil { - commit() - filteredEvents = filterEvents(append(em.Events(), events...)) - ctx.EventManager().EmitEvents(filteredEvents) - if msg.Msg.Wasm == nil { - filteredEvents = []sdk.Event{} - } else { - for _, e := range filteredEvents { - attributes := e.Attributes - sort.SliceStable(attributes, func(i, j int) bool { - return strings.Compare(attributes[i].Key, attributes[j].Key) < 0 - }) - } - } - } // on failure, revert state from sandbox, and ignore events (just skip doing the above) - - // we only callback if requested. Short-circuit here the cases we don't want to - if (msg.ReplyOn == wasmvmtypes.ReplySuccess || msg.ReplyOn == wasmvmtypes.ReplyNever) && err != nil { - return nil, err - } - if msg.ReplyOn == wasmvmtypes.ReplyNever || (msg.ReplyOn == wasmvmtypes.ReplyError && err == nil) { - continue - } - - // otherwise, we create a SubMsgResult and pass it into the calling contract - var result wasmvmtypes.SubMsgResult - if err == nil { - // just take the first one for now if there are multiple sub-sdk messages - // and safely return nothing if no data - var responseData []byte - if len(data) > 0 { - responseData = data[0] - } - result = wasmvmtypes.SubMsgResult{ - Ok: &wasmvmtypes.SubMsgResponse{ - Events: sdkEventsToWasmVMEvents(filteredEvents), - Data: responseData, - }, - } - } else { - // Issue #759 - we don't return error string for worries of non-determinism - moduleLogger(ctx).Info("Redacting submessage error", "cause", err) - result = wasmvmtypes.SubMsgResult{ - Err: redactError(err).Error(), - } - } - - // now handle the reply, we use the parent context, and abort on error - reply := wasmvmtypes.Reply{ - ID: msg.ID, - Result: result, - } - - // we can ignore any result returned as there is nothing to do with the data - // and the events are already in the ctx.EventManager() - rspData, err := d.keeper.reply(ctx, contractAddr, reply) - switch { - case err != nil: - return nil, errorsmod.Wrap(err, "reply") - case rspData != nil: - rsp = rspData - } - } - return rsp, nil -} - -// Issue #759 - we don't return error string for worries of non-determinism -func redactError(err error) error { - // Do not redact system errors - // SystemErrors must be created in x/wasm and we can ensure determinism - if wasmvmtypes.ToSystemError(err) != nil { - return err - } - - // FIXME: do we want to hardcode some constant string mappings here as well? - // Or better document them? (SDK error string may change on a patch release to fix wording) - // sdk/11 is out of gas - // sdk/5 is insufficient funds (on bank send) - // (we can theoretically redact less in the future, but this is a first step to safety) - codespace, code, _ := errorsmod.ABCIInfo(err, false) - return fmt.Errorf("codespace: %s, code: %d", codespace, code) -} - -func filterEvents(events []sdk.Event) []sdk.Event { - // pre-allocate space for efficiency - res := make([]sdk.Event, 0, len(events)) - for _, ev := range events { - if ev.Type != "message" { - res = append(res, ev) - } - } - return res -} - -func sdkEventsToWasmVMEvents(events []sdk.Event) []wasmvmtypes.Event { - res := make([]wasmvmtypes.Event, len(events)) - for i, ev := range events { - res[i] = wasmvmtypes.Event{ - Type: ev.Type, - Attributes: sdkAttributesToWasmVMAttributes(ev.Attributes), - } - } - return res -} - -func sdkAttributesToWasmVMAttributes(attrs []abci.EventAttribute) []wasmvmtypes.EventAttribute { - res := make([]wasmvmtypes.EventAttribute, len(attrs)) - for i, attr := range attrs { - res[i] = wasmvmtypes.EventAttribute{ - Key: attr.Key, - Value: attr.Value, - } - } - return res -} diff --git a/x/wasm/keeper/msg_dispatcher_test.go b/x/wasm/keeper/msg_dispatcher_test.go deleted file mode 100644 index 436ad6d..0000000 --- a/x/wasm/keeper/msg_dispatcher_test.go +++ /dev/null @@ -1,430 +0,0 @@ -package keeper - -import ( - "errors" - "fmt" - "testing" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cometbft/cometbft/libs/log" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" -) - -func TestDispatchSubmessages(t *testing.T) { - noReplyCalled := &mockReplyer{} - var anyGasLimit uint64 = 1 - specs := map[string]struct { - msgs []wasmvmtypes.SubMsg - replyer *mockReplyer - msgHandler *wasmtesting.MockMessageHandler - expErr bool - expData []byte - expCommits []bool - expEvents sdk.Events - }{ - "no reply on error without error": { - msgs: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyError}}, - replyer: noReplyCalled, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, [][]byte{[]byte("myData")}, nil - }, - }, - expCommits: []bool{true}, - }, - "no reply on success without success": { - msgs: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplySuccess}}, - replyer: noReplyCalled, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, nil, errors.New("test, ignore") - }, - }, - expCommits: []bool{false}, - expErr: true, - }, - "reply on success - received": { - msgs: []wasmvmtypes.SubMsg{{ - ReplyOn: wasmvmtypes.ReplySuccess, - }}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - return []byte("myReplyData"), nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, [][]byte{[]byte("myData")}, nil - }, - }, - expData: []byte("myReplyData"), - expCommits: []bool{true}, - }, - "reply on error - handled": { - msgs: []wasmvmtypes.SubMsg{{ - ReplyOn: wasmvmtypes.ReplyError, - }}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - return []byte("myReplyData"), nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, nil, errors.New("my error") - }, - }, - expData: []byte("myReplyData"), - expCommits: []bool{false}, - }, - "with reply events": { - msgs: []wasmvmtypes.SubMsg{{ - ReplyOn: wasmvmtypes.ReplySuccess, - }}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - ctx.EventManager().EmitEvent(sdk.NewEvent("wasm-reply")) - return []byte("myReplyData"), nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - myEvents := []sdk.Event{{Type: "myEvent", Attributes: []abci.EventAttribute{{Key: "foo", Value: "bar"}}}} - return myEvents, [][]byte{[]byte("myData")}, nil - }, - }, - expData: []byte("myReplyData"), - expCommits: []bool{true}, - expEvents: []sdk.Event{ - { - Type: "myEvent", - Attributes: []abci.EventAttribute{{Key: "foo", Value: "bar"}}, - }, - sdk.NewEvent("wasm-reply"), - }, - }, - "with context events - released on commit": { - msgs: []wasmvmtypes.SubMsg{{ - ReplyOn: wasmvmtypes.ReplyNever, - }}, - replyer: &mockReplyer{}, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - myEvents := []sdk.Event{{Type: "myEvent", Attributes: []abci.EventAttribute{{Key: "foo", Value: "bar"}}}} - ctx.EventManager().EmitEvents(myEvents) - return nil, nil, nil - }, - }, - expCommits: []bool{true}, - expEvents: []sdk.Event{{ - Type: "myEvent", - Attributes: []abci.EventAttribute{{Key: "foo", Value: "bar"}}, - }}, - }, - "with context events - discarded on failure": { - msgs: []wasmvmtypes.SubMsg{{ - ReplyOn: wasmvmtypes.ReplyNever, - }}, - replyer: &mockReplyer{}, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - myEvents := []sdk.Event{{Type: "myEvent", Attributes: []abci.EventAttribute{{Key: "foo", Value: "bar"}}}} - ctx.EventManager().EmitEvents(myEvents) - return nil, nil, errors.New("testing") - }, - }, - expCommits: []bool{false}, - expErr: true, - }, - "reply returns error": { - msgs: []wasmvmtypes.SubMsg{{ - ReplyOn: wasmvmtypes.ReplySuccess, - }}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - return nil, errors.New("reply failed") - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, nil, nil - }, - }, - expCommits: []bool{false}, - expErr: true, - }, - "with gas limit - out of gas": { - msgs: []wasmvmtypes.SubMsg{{ - GasLimit: &anyGasLimit, - ReplyOn: wasmvmtypes.ReplyError, - }}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - return []byte("myReplyData"), nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - ctx.GasMeter().ConsumeGas(sdk.Gas(101), "testing") - return nil, [][]byte{[]byte("someData")}, nil - }, - }, - expData: []byte("myReplyData"), - expCommits: []bool{false}, - }, - "with gas limit - within limit no error": { - msgs: []wasmvmtypes.SubMsg{{ - GasLimit: &anyGasLimit, - ReplyOn: wasmvmtypes.ReplyError, - }}, - replyer: &mockReplyer{}, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - ctx.GasMeter().ConsumeGas(sdk.Gas(1), "testing") - return nil, [][]byte{[]byte("someData")}, nil - }, - }, - expCommits: []bool{true}, - }, - "never reply - with nil response": { - msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyNever}, {ID: 2, ReplyOn: wasmvmtypes.ReplyNever}}, - replyer: &mockReplyer{}, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, [][]byte{nil}, nil - }, - }, - expCommits: []bool{true, true}, - }, - "never reply - with any non nil response": { - msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyNever}, {ID: 2, ReplyOn: wasmvmtypes.ReplyNever}}, - replyer: &mockReplyer{}, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, [][]byte{{}}, nil - }, - }, - expCommits: []bool{true, true}, - }, - "never reply - with error": { - msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyNever}, {ID: 2, ReplyOn: wasmvmtypes.ReplyNever}}, - replyer: &mockReplyer{}, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, [][]byte{{}}, errors.New("testing") - }, - }, - expCommits: []bool{false, false}, - expErr: true, - }, - "multiple msg - last reply returned": { - msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyError}, {ID: 2, ReplyOn: wasmvmtypes.ReplyError}}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - return []byte(fmt.Sprintf("myReplyData:%d", reply.ID)), nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, nil, errors.New("my error") - }, - }, - expData: []byte("myReplyData:2"), - expCommits: []bool{false, false}, - }, - "multiple msg - last non nil reply returned": { - msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyError}, {ID: 2, ReplyOn: wasmvmtypes.ReplyError}}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - if reply.ID == 2 { - return nil, nil - } - return []byte("myReplyData:1"), nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, nil, errors.New("my error") - }, - }, - expData: []byte("myReplyData:1"), - expCommits: []bool{false, false}, - }, - "multiple msg - empty reply can overwrite result": { - msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyError}, {ID: 2, ReplyOn: wasmvmtypes.ReplyError}}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - if reply.ID == 2 { - return []byte{}, nil - } - return []byte("myReplyData:1"), nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - return nil, nil, errors.New("my error") - }, - }, - expData: []byte{}, - expCommits: []bool{false, false}, - }, - "message event filtered without reply": { - msgs: []wasmvmtypes.SubMsg{{ - ReplyOn: wasmvmtypes.ReplyNever, - }}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - return nil, errors.New("should never be called") - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - myEvents := []sdk.Event{ - sdk.NewEvent("message"), - sdk.NewEvent("execute", sdk.NewAttribute("foo", "bar")), - } - return myEvents, [][]byte{[]byte("myData")}, nil - }, - }, - expData: nil, - expCommits: []bool{true}, - expEvents: []sdk.Event{sdk.NewEvent("execute", sdk.NewAttribute("foo", "bar"))}, - }, - "wasm reply gets proper events": { - // put fake wasmmsg in here to show where it comes from - msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyAlways, Msg: wasmvmtypes.CosmosMsg{Wasm: &wasmvmtypes.WasmMsg{}}}}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - if reply.Result.Err != "" { - return nil, errors.New(reply.Result.Err) - } - res := reply.Result.Ok - - // ensure the input events are what we expect - // I didn't use require.Equal() to act more like a contract... but maybe that would be better - if len(res.Events) != 2 { - return nil, fmt.Errorf("event count: %#v", res.Events) - } - if res.Events[0].Type != "execute" { - return nil, fmt.Errorf("event0: %#v", res.Events[0]) - } - if res.Events[1].Type != "wasm" { - return nil, fmt.Errorf("event1: %#v", res.Events[1]) - } - - // let's add a custom event here and see if it makes it out - ctx.EventManager().EmitEvent(sdk.NewEvent("wasm-reply")) - - // update data from what we got in - return res.Data, nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - events = []sdk.Event{ - sdk.NewEvent("message", sdk.NewAttribute("_contract_address", contractAddr.String())), - // we don't know what the contarctAddr will be so we can't use it in the final tests - sdk.NewEvent("execute", sdk.NewAttribute("_contract_address", "placeholder-random-addr")), - sdk.NewEvent("wasm", sdk.NewAttribute("random", "data")), - } - return events, [][]byte{[]byte("subData")}, nil - }, - }, - expData: []byte("subData"), - expCommits: []bool{true}, - expEvents: []sdk.Event{ - sdk.NewEvent("execute", sdk.NewAttribute("_contract_address", "placeholder-random-addr")), - sdk.NewEvent("wasm", sdk.NewAttribute("random", "data")), - sdk.NewEvent("wasm-reply"), - }, - }, - "non-wasm reply events get filtered": { - // show events from a stargate message gets filtered out - msgs: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyAlways, Msg: wasmvmtypes.CosmosMsg{Stargate: &wasmvmtypes.StargateMsg{}}}}, - replyer: &mockReplyer{ - replyFn: func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - if reply.Result.Err != "" { - return nil, errors.New(reply.Result.Err) - } - res := reply.Result.Ok - - // ensure the input events are what we expect - // I didn't use require.Equal() to act more like a contract... but maybe that would be better - if len(res.Events) != 0 { - return nil, errors.New("events not filtered out") - } - - // let's add a custom event here and see if it makes it out - ctx.EventManager().EmitEvent(sdk.NewEvent("stargate-reply")) - - // update data from what we got in - return res.Data, nil - }, - }, - msgHandler: &wasmtesting.MockMessageHandler{ - DispatchMsgFn: func(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { - events = []sdk.Event{ - // this is filtered out - sdk.NewEvent("message", sdk.NewAttribute("stargate", "something-something")), - // we still emit this to the client, but not the contract - sdk.NewEvent("non-determinstic"), - } - return events, [][]byte{[]byte("subData")}, nil - }, - }, - expData: []byte("subData"), - expCommits: []bool{true}, - expEvents: []sdk.Event{ - sdk.NewEvent("non-determinstic"), - // the event from reply is also exposed - sdk.NewEvent("stargate-reply"), - }, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - var mockStore wasmtesting.MockCommitMultiStore - em := sdk.NewEventManager() - ctx := sdk.Context{}.WithMultiStore(&mockStore). - WithGasMeter(sdk.NewGasMeter(100)). - WithEventManager(em).WithLogger(log.TestingLogger()) - d := NewMessageDispatcher(spec.msgHandler, spec.replyer) - - // run the test - gotData, gotErr := d.DispatchSubmessages(ctx, RandomAccountAddress(t), "any_port", spec.msgs) - if spec.expErr { - require.Error(t, gotErr) - assert.Empty(t, em.Events()) - return - } - - // if we don't expect an error, we should get no error - require.NoError(t, gotErr) - assert.Equal(t, spec.expData, gotData) - - // ensure the commits are what we expect - assert.Equal(t, spec.expCommits, mockStore.Committed) - if len(spec.expEvents) == 0 { - assert.Empty(t, em.Events()) - } else { - assert.Equal(t, spec.expEvents, em.Events()) - } - }) - } -} - -type mockReplyer struct { - replyFn func(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) -} - -func (m mockReplyer) reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) { - if m.replyFn == nil { - panic("not expected to be called") - } - return m.replyFn(ctx, contractAddress, reply) -} diff --git a/x/wasm/keeper/msg_server.go b/x/wasm/keeper/msg_server.go deleted file mode 100644 index 66d1cd6..0000000 --- a/x/wasm/keeper/msg_server.go +++ /dev/null @@ -1,365 +0,0 @@ -package keeper - -import ( - "context" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var _ types.MsgServer = msgServer{} - -// grpc message server implementation -type msgServer struct { - keeper *Keeper -} - -// NewMsgServerImpl default constructor -func NewMsgServerImpl(k *Keeper) types.MsgServer { - return &msgServer{keeper: k} -} - -// StoreCode stores a new wasm code on chain -func (m msgServer) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*types.MsgStoreCodeResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - ctx := sdk.UnwrapSDKContext(goCtx) - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - - policy := m.selectAuthorizationPolicy(msg.Sender) - - codeID, checksum, err := m.keeper.create(ctx, senderAddr, msg.WASMByteCode, msg.InstantiatePermission, policy) - if err != nil { - return nil, err - } - - return &types.MsgStoreCodeResponse{ - CodeID: codeID, - Checksum: checksum, - }, nil -} - -// InstantiateContract instantiate a new contract with classic sequence based address generation -func (m msgServer) InstantiateContract(goCtx context.Context, msg *types.MsgInstantiateContract) (*types.MsgInstantiateContractResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - ctx := sdk.UnwrapSDKContext(goCtx) - - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - var adminAddr sdk.AccAddress - if msg.Admin != "" { - if adminAddr, err = sdk.AccAddressFromBech32(msg.Admin); err != nil { - return nil, errorsmod.Wrap(err, "admin") - } - } - - policy := m.selectAuthorizationPolicy(msg.Sender) - - contractAddr, data, err := m.keeper.instantiate(ctx, msg.CodeID, senderAddr, adminAddr, msg.Msg, msg.Label, msg.Funds, m.keeper.ClassicAddressGenerator(), policy) - if err != nil { - return nil, err - } - - return &types.MsgInstantiateContractResponse{ - Address: contractAddr.String(), - Data: data, - }, nil -} - -// InstantiateContract2 instantiate a new contract with predicatable address generated -func (m msgServer) InstantiateContract2(goCtx context.Context, msg *types.MsgInstantiateContract2) (*types.MsgInstantiateContract2Response, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - ctx := sdk.UnwrapSDKContext(goCtx) - - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - var adminAddr sdk.AccAddress - if msg.Admin != "" { - if adminAddr, err = sdk.AccAddressFromBech32(msg.Admin); err != nil { - return nil, errorsmod.Wrap(err, "admin") - } - } - - policy := m.selectAuthorizationPolicy(msg.Sender) - - addrGenerator := PredicableAddressGenerator(senderAddr, msg.Salt, msg.Msg, msg.FixMsg) - - contractAddr, data, err := m.keeper.instantiate(ctx, msg.CodeID, senderAddr, adminAddr, msg.Msg, msg.Label, msg.Funds, addrGenerator, policy) - if err != nil { - return nil, err - } - - return &types.MsgInstantiateContract2Response{ - Address: contractAddr.String(), - Data: data, - }, nil -} - -func (m msgServer) ExecuteContract(goCtx context.Context, msg *types.MsgExecuteContract) (*types.MsgExecuteContractResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - ctx := sdk.UnwrapSDKContext(goCtx) - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - contractAddr, err := sdk.AccAddressFromBech32(msg.Contract) - if err != nil { - return nil, errorsmod.Wrap(err, "contract") - } - - data, err := m.keeper.execute(ctx, contractAddr, senderAddr, msg.Msg, msg.Funds) - if err != nil { - return nil, err - } - - return &types.MsgExecuteContractResponse{ - Data: data, - }, nil -} - -func (m msgServer) MigrateContract(goCtx context.Context, msg *types.MsgMigrateContract) (*types.MsgMigrateContractResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - ctx := sdk.UnwrapSDKContext(goCtx) - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - contractAddr, err := sdk.AccAddressFromBech32(msg.Contract) - if err != nil { - return nil, errorsmod.Wrap(err, "contract") - } - - policy := m.selectAuthorizationPolicy(msg.Sender) - - data, err := m.keeper.migrate(ctx, contractAddr, senderAddr, msg.CodeID, msg.Msg, policy) - if err != nil { - return nil, err - } - - return &types.MsgMigrateContractResponse{ - Data: data, - }, nil -} - -func (m msgServer) UpdateAdmin(goCtx context.Context, msg *types.MsgUpdateAdmin) (*types.MsgUpdateAdminResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - ctx := sdk.UnwrapSDKContext(goCtx) - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - contractAddr, err := sdk.AccAddressFromBech32(msg.Contract) - if err != nil { - return nil, errorsmod.Wrap(err, "contract") - } - newAdminAddr, err := sdk.AccAddressFromBech32(msg.NewAdmin) - if err != nil { - return nil, errorsmod.Wrap(err, "new admin") - } - - policy := m.selectAuthorizationPolicy(msg.Sender) - - if err := m.keeper.setContractAdmin(ctx, contractAddr, senderAddr, newAdminAddr, policy); err != nil { - return nil, err - } - - return &types.MsgUpdateAdminResponse{}, nil -} - -func (m msgServer) ClearAdmin(goCtx context.Context, msg *types.MsgClearAdmin) (*types.MsgClearAdminResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - ctx := sdk.UnwrapSDKContext(goCtx) - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - contractAddr, err := sdk.AccAddressFromBech32(msg.Contract) - if err != nil { - return nil, errorsmod.Wrap(err, "contract") - } - - policy := m.selectAuthorizationPolicy(msg.Sender) - - if err := m.keeper.setContractAdmin(ctx, contractAddr, senderAddr, nil, policy); err != nil { - return nil, err - } - - return &types.MsgClearAdminResponse{}, nil -} - -func (m msgServer) UpdateInstantiateConfig(goCtx context.Context, msg *types.MsgUpdateInstantiateConfig) (*types.MsgUpdateInstantiateConfigResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - ctx := sdk.UnwrapSDKContext(goCtx) - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - policy := m.selectAuthorizationPolicy(msg.Sender) - - if err := m.keeper.setAccessConfig(ctx, msg.CodeID, senderAddr, *msg.NewInstantiatePermission, policy); err != nil { - return nil, err - } - - return &types.MsgUpdateInstantiateConfigResponse{}, nil -} - -// UpdateParams updates the module parameters -func (m msgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { - if err := req.ValidateBasic(); err != nil { - return nil, err - } - authority := m.keeper.GetAuthority() - if authority != req.Authority { - return nil, errorsmod.Wrapf(types.ErrInvalid, "invalid authority; expected %s, got %s", authority, req.Authority) - } - - ctx := sdk.UnwrapSDKContext(goCtx) - if err := m.keeper.SetParams(ctx, req.Params); err != nil { - return nil, err - } - - return &types.MsgUpdateParamsResponse{}, nil -} - -// PinCodes pins a set of code ids in the wasmvm cache. -func (m msgServer) PinCodes(goCtx context.Context, req *types.MsgPinCodes) (*types.MsgPinCodesResponse, error) { - if err := req.ValidateBasic(); err != nil { - return nil, err - } - - authority := m.keeper.GetAuthority() - if authority != req.Authority { - return nil, errorsmod.Wrapf(types.ErrInvalid, "invalid authority; expected %s, got %s", authority, req.Authority) - } - - ctx := sdk.UnwrapSDKContext(goCtx) - for _, codeID := range req.CodeIDs { - if err := m.keeper.pinCode(ctx, codeID); err != nil { - return nil, err - } - } - - return &types.MsgPinCodesResponse{}, nil -} - -// UnpinCodes unpins a set of code ids in the wasmvm cache. -func (m msgServer) UnpinCodes(goCtx context.Context, req *types.MsgUnpinCodes) (*types.MsgUnpinCodesResponse, error) { - if err := req.ValidateBasic(); err != nil { - return nil, err - } - - authority := m.keeper.GetAuthority() - if authority != req.Authority { - return nil, errorsmod.Wrapf(types.ErrInvalid, "invalid authority; expected %s, got %s", authority, req.Authority) - } - - ctx := sdk.UnwrapSDKContext(goCtx) - for _, codeID := range req.CodeIDs { - if err := m.keeper.unpinCode(ctx, codeID); err != nil { - return nil, err - } - } - - return &types.MsgUnpinCodesResponse{}, nil -} - -// SudoContract calls sudo on a contract. -func (m msgServer) SudoContract(goCtx context.Context, req *types.MsgSudoContract) (*types.MsgSudoContractResponse, error) { - if err := req.ValidateBasic(); err != nil { - return nil, err - } - authority := m.keeper.GetAuthority() - if authority != req.Authority { - return nil, errorsmod.Wrapf(types.ErrInvalid, "invalid authority; expected %s, got %s", authority, req.Authority) - } - - contractAddr, err := sdk.AccAddressFromBech32(req.Contract) - if err != nil { - return nil, errorsmod.Wrap(err, "contract") - } - - ctx := sdk.UnwrapSDKContext(goCtx) - data, err := m.keeper.Sudo(ctx, contractAddr, req.Msg) - if err != nil { - return nil, err - } - - return &types.MsgSudoContractResponse{Data: data}, nil -} - -// StoreAndInstantiateContract stores and instantiates the contract. -func (m msgServer) StoreAndInstantiateContract(goCtx context.Context, req *types.MsgStoreAndInstantiateContract) (*types.MsgStoreAndInstantiateContractResponse, error) { - if err := req.ValidateBasic(); err != nil { - return nil, err - } - - authorityAddr, err := sdk.AccAddressFromBech32(req.Authority) - if err != nil { - return nil, errorsmod.Wrap(err, "authority") - } - - if err = req.ValidateBasic(); err != nil { - return nil, err - } - - var adminAddr sdk.AccAddress - if req.Admin != "" { - if adminAddr, err = sdk.AccAddressFromBech32(req.Admin); err != nil { - return nil, errorsmod.Wrap(err, "admin") - } - } - - ctx := sdk.UnwrapSDKContext(goCtx) - policy := m.selectAuthorizationPolicy(req.Authority) - - codeID, _, err := m.keeper.create(ctx, authorityAddr, req.WASMByteCode, req.InstantiatePermission, policy) - if err != nil { - return nil, err - } - - contractAddr, data, err := m.keeper.instantiate(ctx, codeID, authorityAddr, adminAddr, req.Msg, req.Label, req.Funds, m.keeper.ClassicAddressGenerator(), policy) - if err != nil { - return nil, err - } - - return &types.MsgStoreAndInstantiateContractResponse{ - Address: contractAddr.String(), - Data: data, - }, nil -} - -func (m msgServer) selectAuthorizationPolicy(actor string) AuthorizationPolicy { - if actor == m.keeper.GetAuthority() { - return GovAuthorizationPolicy{} - } - return DefaultAuthorizationPolicy{} -} diff --git a/x/wasm/keeper/msg_server_integration_test.go b/x/wasm/keeper/msg_server_integration_test.go deleted file mode 100644 index 11003c0..0000000 --- a/x/wasm/keeper/msg_server_integration_test.go +++ /dev/null @@ -1,855 +0,0 @@ -package keeper_test - -import ( - "crypto/sha256" - _ "embed" - "encoding/json" - "testing" - "time" - - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/app" - "github.com/terpnetwork/terp-core/x/wasm/keeper" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -//go:embed testdata/reflect.wasm -var wasmContract []byte - -//go:embed testdata/hackatom.wasm -var hackatomContract []byte - -func TestStoreCode(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{}) - _, _, sender := testdata.KeyTestPubAddr() - msg := types.MsgStoreCodeFixture(func(m *types.MsgStoreCode) { - m.WASMByteCode = wasmContract - m.Sender = sender.String() - }) - - // when - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - - // then - require.NoError(t, err) - var result types.MsgStoreCodeResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &result)) - assert.Equal(t, uint64(1), result.CodeID) - expHash := sha256.Sum256(wasmContract) - assert.Equal(t, expHash[:], result.Checksum) - // and - info := wasmApp.WasmKeeper.GetCodeInfo(ctx, 1) - assert.NotNil(t, info) - assert.Equal(t, expHash[:], info.CodeHash) - assert.Equal(t, sender.String(), info.Creator) - assert.Equal(t, types.DefaultParams().InstantiateDefaultPermission.With(sender), info.InstantiateConfig) -} - -func TestUpdateParams(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - oneAddressAccessConfig = types.AccessTypeOnlyAddress.With(myAddress) - govAuthority = wasmApp.WasmKeeper.GetAuthority() - ) - - specs := map[string]struct { - src types.MsgUpdateParams - expUploadConfig types.AccessConfig - expInstantiateType types.AccessType - }{ - "update upload permission param": { - src: types.MsgUpdateParams{ - Authority: govAuthority, - Params: types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeEverybody, - }, - }, - expUploadConfig: types.AllowNobody, - expInstantiateType: types.AccessTypeEverybody, - }, - "update upload permission with same as current value": { - src: types.MsgUpdateParams{ - Authority: govAuthority, - Params: types.Params{ - CodeUploadAccess: types.AllowEverybody, - InstantiateDefaultPermission: types.AccessTypeEverybody, - }, - }, - expUploadConfig: types.AllowEverybody, - expInstantiateType: types.AccessTypeEverybody, - }, - "update upload permission param with address": { - src: types.MsgUpdateParams{ - Authority: govAuthority, - Params: types.Params{ - CodeUploadAccess: oneAddressAccessConfig, - InstantiateDefaultPermission: types.AccessTypeEverybody, - }, - }, - expUploadConfig: oneAddressAccessConfig, - expInstantiateType: types.AccessTypeEverybody, - }, - "update instantiate param": { - src: types.MsgUpdateParams{ - Authority: govAuthority, - Params: types.Params{ - CodeUploadAccess: types.AllowEverybody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }, - }, - expUploadConfig: types.AllowEverybody, - expInstantiateType: types.AccessTypeNobody, - }, - "update instantiate param as default": { - src: types.MsgUpdateParams{ - Authority: govAuthority, - Params: types.Params{ - CodeUploadAccess: types.AllowEverybody, - InstantiateDefaultPermission: types.AccessTypeEverybody, - }, - }, - expUploadConfig: types.AllowEverybody, - expInstantiateType: types.AccessTypeEverybody, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - err := wasmApp.WasmKeeper.SetParams(ctx, types.DefaultParams()) - require.NoError(t, err) - - // when - rsp, err := wasmApp.MsgServiceRouter().Handler(&spec.src)(ctx, &spec.src) - require.NoError(t, err) - var result types.MsgUpdateParamsResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &result)) - - // then - assert.True(t, spec.expUploadConfig.Equals(wasmApp.WasmKeeper.GetParams(ctx).CodeUploadAccess), - "got %#v not %#v", wasmApp.WasmKeeper.GetParams(ctx).CodeUploadAccess, spec.expUploadConfig) - assert.Equal(t, spec.expInstantiateType, wasmApp.WasmKeeper.GetParams(ctx).InstantiateDefaultPermission) - }) - } -} - -func TestPinCodes(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - ) - - specs := map[string]struct { - addr string - expErr bool - }{ - "authority can pin codes": { - addr: authority, - expErr: false, - }, - "other address cannot pin codes": { - addr: myAddress.String(), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup - _, _, sender := testdata.KeyTestPubAddr() - msg := types.MsgStoreCodeFixture(func(m *types.MsgStoreCode) { - m.WASMByteCode = wasmContract - m.Sender = sender.String() - }) - - // store code - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var result types.MsgStoreCodeResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &result)) - require.False(t, wasmApp.WasmKeeper.IsPinnedCode(ctx, result.CodeID)) - - // when - msgPinCodes := &types.MsgPinCodes{ - Authority: spec.addr, - CodeIDs: []uint64{result.CodeID}, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgPinCodes)(ctx, msgPinCodes) - - // then - if spec.expErr { - require.Error(t, err) - assert.False(t, wasmApp.WasmKeeper.IsPinnedCode(ctx, result.CodeID)) - } else { - require.NoError(t, err) - assert.True(t, wasmApp.WasmKeeper.IsPinnedCode(ctx, result.CodeID)) - } - }) - } -} - -func TestUnpinCodes(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - ) - - specs := map[string]struct { - addr string - expErr bool - }{ - "authority can unpin codes": { - addr: authority, - expErr: false, - }, - "other address cannot unpin codes": { - addr: myAddress.String(), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup - _, _, sender := testdata.KeyTestPubAddr() - msg := types.MsgStoreCodeFixture(func(m *types.MsgStoreCode) { - m.WASMByteCode = wasmContract - m.Sender = sender.String() - }) - - // store code - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var result types.MsgStoreCodeResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &result)) - - // pin code - msgPin := &types.MsgPinCodes{ - Authority: authority, - CodeIDs: []uint64{result.CodeID}, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgPin)(ctx, msgPin) - require.NoError(t, err) - assert.True(t, wasmApp.WasmKeeper.IsPinnedCode(ctx, result.CodeID)) - - // when - msgUnpinCodes := &types.MsgUnpinCodes{ - Authority: spec.addr, - CodeIDs: []uint64{result.CodeID}, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgUnpinCodes)(ctx, msgUnpinCodes) - - // then - if spec.expErr { - require.Error(t, err) - assert.True(t, wasmApp.WasmKeeper.IsPinnedCode(ctx, result.CodeID)) - } else { - require.NoError(t, err) - assert.False(t, wasmApp.WasmKeeper.IsPinnedCode(ctx, result.CodeID)) - } - }) - } -} - -func TestSudoContract(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - ) - - type StealMsg struct { - Recipient string `json:"recipient"` - Amount []sdk.Coin `json:"amount"` - } - - stealMsg := struct { - Steal StealMsg `json:"steal_funds"` - }{Steal: StealMsg{ - Recipient: myAddress.String(), - Amount: []sdk.Coin{}, - }} - - stealMsgBz, err := json.Marshal(stealMsg) - require.NoError(t, err) - - specs := map[string]struct { - addr string - expErr bool - }{ - "authority can call sudo on a contract": { - addr: authority, - expErr: false, - }, - "other address cannot call sudo on a contract": { - addr: myAddress.String(), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup - _, _, sender := testdata.KeyTestPubAddr() - msg := types.MsgStoreCodeFixture(func(m *types.MsgStoreCode) { - m.WASMByteCode = hackatomContract - m.Sender = sender.String() - }) - - // store code - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var storeCodeResponse types.MsgStoreCodeResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &storeCodeResponse)) - - // instantiate contract - initMsg := keeper.HackatomExampleInitMsg{ - Verifier: sender, - Beneficiary: myAddress, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - msgInstantiate := &types.MsgInstantiateContract{ - Sender: sender.String(), - Admin: sender.String(), - CodeID: storeCodeResponse.CodeID, - Label: "test", - Msg: initMsgBz, - Funds: sdk.Coins{}, - } - rsp, err = wasmApp.MsgServiceRouter().Handler(msgInstantiate)(ctx, msgInstantiate) - require.NoError(t, err) - var instantiateResponse types.MsgInstantiateContractResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &instantiateResponse)) - - // when - msgSudoContract := &types.MsgSudoContract{ - Authority: spec.addr, - Msg: stealMsgBz, - Contract: instantiateResponse.Address, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgSudoContract)(ctx, msgSudoContract) - - // then - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestStoreAndInstantiateContract(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - ) - - specs := map[string]struct { - addr string - permission *types.AccessConfig - expErr bool - }{ - "authority can store and instantiate a contract when permission is nobody": { - addr: authority, - permission: &types.AllowNobody, - expErr: false, - }, - "other address cannot store and instantiate a contract when permission is nobody": { - addr: myAddress.String(), - permission: &types.AllowNobody, - expErr: true, - }, - "authority can store and instantiate a contract when permission is everybody": { - addr: authority, - permission: &types.AllowEverybody, - expErr: false, - }, - "other address can store and instantiate a contract when permission is everybody": { - addr: myAddress.String(), - permission: &types.AllowEverybody, - expErr: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // when - msg := &types.MsgStoreAndInstantiateContract{ - Authority: spec.addr, - WASMByteCode: wasmContract, - InstantiatePermission: spec.permission, - Admin: myAddress.String(), - UnpinCode: false, - Label: "test", - Msg: []byte(`{}`), - Funds: sdk.Coins{}, - } - _, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - - // then - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestUpdateAdmin(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - _, _, otherAddr = testdata.KeyTestPubAddr() - ) - - specs := map[string]struct { - addr string - expErr bool - }{ - "authority can update admin": { - addr: authority, - expErr: false, - }, - "admin can update admin": { - addr: myAddress.String(), - expErr: false, - }, - "other address cannot update admin": { - addr: otherAddr.String(), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - _, _, newAdmin := testdata.KeyTestPubAddr() - - // setup - msg := &types.MsgStoreAndInstantiateContract{ - Authority: spec.addr, - WASMByteCode: wasmContract, - InstantiatePermission: &types.AllowEverybody, - Admin: myAddress.String(), - UnpinCode: false, - Label: "test", - Msg: []byte(`{}`), - Funds: sdk.Coins{}, - } - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var storeAndInstantiateResponse types.MsgStoreAndInstantiateContractResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &storeAndInstantiateResponse)) - - // when - msgUpdateAdmin := &types.MsgUpdateAdmin{ - Sender: spec.addr, - NewAdmin: newAdmin.String(), - Contract: storeAndInstantiateResponse.Address, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgUpdateAdmin)(ctx, msgUpdateAdmin) - - // then - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestClearAdmin(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - _, _, otherAddr = testdata.KeyTestPubAddr() - ) - - specs := map[string]struct { - addr string - expErr bool - }{ - "authority can clear admin": { - addr: authority, - expErr: false, - }, - "admin can clear admin": { - addr: myAddress.String(), - expErr: false, - }, - "other address cannot clear admin": { - addr: otherAddr.String(), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup - msg := &types.MsgStoreAndInstantiateContract{ - Authority: spec.addr, - WASMByteCode: wasmContract, - InstantiatePermission: &types.AllowEverybody, - Admin: myAddress.String(), - UnpinCode: false, - Label: "test", - Msg: []byte(`{}`), - Funds: sdk.Coins{}, - } - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var storeAndInstantiateResponse types.MsgStoreAndInstantiateContractResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &storeAndInstantiateResponse)) - - // when - msgClearAdmin := &types.MsgClearAdmin{ - Sender: spec.addr, - Contract: storeAndInstantiateResponse.Address, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgClearAdmin)(ctx, msgClearAdmin) - - // then - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestMigrateContract(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - _, _, otherAddr = testdata.KeyTestPubAddr() - ) - - specs := map[string]struct { - addr string - expErr bool - }{ - "authority can migrate a contract": { - addr: authority, - expErr: false, - }, - "admin can migrate a contract": { - addr: myAddress.String(), - expErr: false, - }, - "other address cannot migrate a contract": { - addr: otherAddr.String(), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup - _, _, sender := testdata.KeyTestPubAddr() - msg := types.MsgStoreCodeFixture(func(m *types.MsgStoreCode) { - m.WASMByteCode = hackatomContract - m.Sender = sender.String() - }) - - // store code - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var storeCodeResponse types.MsgStoreCodeResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &storeCodeResponse)) - - // instantiate contract - initMsg := keeper.HackatomExampleInitMsg{ - Verifier: sender, - Beneficiary: myAddress, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - msgInstantiate := &types.MsgInstantiateContract{ - Sender: sender.String(), - Admin: myAddress.String(), - CodeID: storeCodeResponse.CodeID, - Label: "test", - Msg: initMsgBz, - Funds: sdk.Coins{}, - } - rsp, err = wasmApp.MsgServiceRouter().Handler(msgInstantiate)(ctx, msgInstantiate) - require.NoError(t, err) - var instantiateResponse types.MsgInstantiateContractResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &instantiateResponse)) - - // when - migMsg := struct { - Verifier sdk.AccAddress `json:"verifier"` - }{Verifier: myAddress} - migMsgBz, err := json.Marshal(migMsg) - require.NoError(t, err) - msgMigrateContract := &types.MsgMigrateContract{ - Sender: spec.addr, - Msg: migMsgBz, - Contract: instantiateResponse.Address, - CodeID: storeCodeResponse.CodeID, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgMigrateContract)(ctx, msgMigrateContract) - - // then - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestInstantiateContract(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - ) - - specs := map[string]struct { - addr string - permission *types.AccessConfig - expErr bool - }{ - "authority can instantiate a contract when permission is nobody": { - addr: authority, - permission: &types.AllowNobody, - expErr: false, - }, - "other address cannot instantiate a contract when permission is nobody": { - addr: myAddress.String(), - permission: &types.AllowNobody, - expErr: true, - }, - "authority can instantiate a contract when permission is everybody": { - addr: authority, - permission: &types.AllowEverybody, - expErr: false, - }, - "other address can instantiate a contract when permission is everybody": { - addr: myAddress.String(), - permission: &types.AllowEverybody, - expErr: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup - _, _, sender := testdata.KeyTestPubAddr() - msg := types.MsgStoreCodeFixture(func(m *types.MsgStoreCode) { - m.WASMByteCode = wasmContract - m.Sender = sender.String() - m.InstantiatePermission = spec.permission - }) - - // store code - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var result types.MsgStoreCodeResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &result)) - - // when - msgInstantiate := &types.MsgInstantiateContract{ - Sender: spec.addr, - Admin: myAddress.String(), - CodeID: result.CodeID, - Label: "test", - Msg: []byte(`{}`), - Funds: sdk.Coins{}, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgInstantiate)(ctx, msgInstantiate) - - // then - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestInstantiateContract2(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) - - var ( - myAddress sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - ) - - specs := map[string]struct { - addr string - salt string - permission *types.AccessConfig - expErr bool - }{ - "authority can instantiate a contract when permission is nobody": { - addr: authority, - permission: &types.AllowNobody, - salt: "salt1", - expErr: false, - }, - "other address cannot instantiate a contract when permission is nobody": { - addr: myAddress.String(), - permission: &types.AllowNobody, - salt: "salt2", - expErr: true, - }, - "authority can instantiate a contract when permission is everybody": { - addr: authority, - permission: &types.AllowEverybody, - salt: "salt3", - expErr: false, - }, - "other address can instantiate a contract when permission is everybody": { - addr: myAddress.String(), - permission: &types.AllowEverybody, - salt: "salt4", - expErr: false, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup - _, _, sender := testdata.KeyTestPubAddr() - msg := types.MsgStoreCodeFixture(func(m *types.MsgStoreCode) { - m.WASMByteCode = wasmContract - m.Sender = sender.String() - m.InstantiatePermission = spec.permission - }) - - // store code - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var result types.MsgStoreCodeResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &result)) - - // when - msgInstantiate := &types.MsgInstantiateContract2{ - Sender: spec.addr, - Admin: myAddress.String(), - CodeID: result.CodeID, - Label: "label", - Msg: []byte(`{}`), - Funds: sdk.Coins{}, - Salt: []byte(spec.salt), - FixMsg: true, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgInstantiate)(ctx, msgInstantiate) - - // then - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestUpdateInstantiateConfig(t *testing.T) { - wasmApp := app.Setup(t) - ctx := wasmApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()}) - - var ( - creator sdk.AccAddress = make([]byte, types.ContractAddrLen) - authority = wasmApp.WasmKeeper.GetAuthority() - ) - - specs := map[string]struct { - addr string - permission *types.AccessConfig - expErr bool - }{ - "authority can update instantiate config when permission is subset": { - addr: authority, - permission: &types.AllowNobody, - expErr: false, - }, - "creator can update instantiate config when permission is subset": { - addr: creator.String(), - permission: &types.AllowNobody, - expErr: false, - }, - "authority can update instantiate config when permission is not subset": { - addr: authority, - permission: &types.AllowEverybody, - expErr: false, - }, - "creator cannot update instantiate config when permission is not subset": { - addr: creator.String(), - permission: &types.AllowEverybody, - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup - err := wasmApp.WasmKeeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowEverybody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - - msg := types.MsgStoreCodeFixture(func(m *types.MsgStoreCode) { - m.WASMByteCode = wasmContract - m.Sender = creator.String() - m.InstantiatePermission = &types.AllowNobody - }) - - // store code - rsp, err := wasmApp.MsgServiceRouter().Handler(msg)(ctx, msg) - require.NoError(t, err) - var result types.MsgStoreCodeResponse - require.NoError(t, wasmApp.AppCodec().Unmarshal(rsp.Data, &result)) - - // when - msgUpdateInstantiateConfig := &types.MsgUpdateInstantiateConfig{ - Sender: spec.addr, - CodeID: result.CodeID, - NewInstantiatePermission: spec.permission, - } - _, err = wasmApp.MsgServiceRouter().Handler(msgUpdateInstantiateConfig)(ctx, msgUpdateInstantiateConfig) - - // then - if spec.expErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} diff --git a/x/wasm/keeper/options.go b/x/wasm/keeper/options.go deleted file mode 100644 index 1ce15c5..0000000 --- a/x/wasm/keeper/options.go +++ /dev/null @@ -1,170 +0,0 @@ -package keeper - -import ( - "fmt" - "reflect" - - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/prometheus/client_golang/prometheus" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -type optsFn func(*Keeper) - -func (f optsFn) apply(keeper *Keeper) { - f(keeper) -} - -// WithWasmEngine is an optional constructor parameter to replace the default wasmVM engine with the -// given one. -func WithWasmEngine(x types.WasmerEngine) Option { - return optsFn(func(k *Keeper) { - k.wasmVM = x - }) -} - -// WithMessageHandler is an optional constructor parameter to set a custom handler for wasmVM messages. -// This option should not be combined with Option `WithMessageEncoders` or `WithMessageHandlerDecorator` -func WithMessageHandler(x Messenger) Option { - return optsFn(func(k *Keeper) { - k.messenger = x - }) -} - -// WithMessageHandlerDecorator is an optional constructor parameter to decorate the wasm handler for wasmVM messages. -// This option should not be combined with Option `WithMessageEncoders` or `WithMessageHandler` -func WithMessageHandlerDecorator(d func(old Messenger) Messenger) Option { - return optsFn(func(k *Keeper) { - k.messenger = d(k.messenger) - }) -} - -// WithQueryHandler is an optional constructor parameter to set custom query handler for wasmVM requests. -// This option should not be combined with Option `WithQueryPlugins` or `WithQueryHandlerDecorator` -func WithQueryHandler(x WasmVMQueryHandler) Option { - return optsFn(func(k *Keeper) { - k.wasmVMQueryHandler = x - }) -} - -// WithQueryHandlerDecorator is an optional constructor parameter to decorate the default wasm query handler for wasmVM requests. -// This option should not be combined with Option `WithQueryPlugins` or `WithQueryHandler` -func WithQueryHandlerDecorator(d func(old WasmVMQueryHandler) WasmVMQueryHandler) Option { - return optsFn(func(k *Keeper) { - k.wasmVMQueryHandler = d(k.wasmVMQueryHandler) - }) -} - -// WithQueryPlugins is an optional constructor parameter to pass custom query plugins for wasmVM requests. -// This option expects the default `QueryHandler` set and should not be combined with Option `WithQueryHandler` or `WithQueryHandlerDecorator`. -func WithQueryPlugins(x *QueryPlugins) Option { - return optsFn(func(k *Keeper) { - q, ok := k.wasmVMQueryHandler.(QueryPlugins) - if !ok { - panic(fmt.Sprintf("Unsupported query handler type: %T", k.wasmVMQueryHandler)) - } - k.wasmVMQueryHandler = q.Merge(x) - }) -} - -// WithMessageEncoders is an optional constructor parameter to pass custom message encoder to the default wasm message handler. -// This option expects the `DefaultMessageHandler` set and should not be combined with Option `WithMessageHandler` or `WithMessageHandlerDecorator`. -func WithMessageEncoders(x *MessageEncoders) Option { - return optsFn(func(k *Keeper) { - q, ok := k.messenger.(*MessageHandlerChain) - if !ok { - panic(fmt.Sprintf("Unsupported message handler type: %T", k.messenger)) - } - s, ok := q.handlers[0].(SDKMessageHandler) - if !ok { - panic(fmt.Sprintf("Unexpected message handler type: %T", q.handlers[0])) - } - e, ok := s.encoders.(MessageEncoders) - if !ok { - panic(fmt.Sprintf("Unsupported encoder type: %T", s.encoders)) - } - s.encoders = e.Merge(x) - q.handlers[0] = s - }) -} - -// WithCoinTransferrer is an optional constructor parameter to set a custom coin transferrer -func WithCoinTransferrer(x CoinTransferrer) Option { - if x == nil { - panic("must not be nil") - } - return optsFn(func(k *Keeper) { - k.bank = x - }) -} - -// WithAccountPruner is an optional constructor parameter to set a custom type that handles balances and data cleanup -// for accounts pruned on contract instantiate -func WithAccountPruner(x AccountPruner) Option { - if x == nil { - panic("must not be nil") - } - return optsFn(func(k *Keeper) { - k.accountPruner = x - }) -} - -func WithVMCacheMetrics(r prometheus.Registerer) Option { - return optsFn(func(k *Keeper) { - NewWasmVMMetricsCollector(k.wasmVM).Register(r) - }) -} - -// WithGasRegister set a new gas register to implement custom gas costs. -// When the "gas multiplier" for wasmvm gas conversion is modified inside the new register, -// make sure to also use `WithApiCosts` option for non default values -func WithGasRegister(x GasRegister) Option { - if x == nil { - panic("must not be nil") - } - return optsFn(func(k *Keeper) { - k.gasRegister = x - }) -} - -// WithAPICosts sets custom api costs. Amounts are in cosmwasm gas Not SDK gas. -func WithAPICosts(human, canonical uint64) Option { - return optsFn(func(_ *Keeper) { - costHumanize = human - costCanonical = canonical - }) -} - -// WithMaxQueryStackSize overwrites the default limit for maximum query stacks -func WithMaxQueryStackSize(m uint32) Option { - return optsFn(func(k *Keeper) { - k.maxQueryStackSize = m - }) -} - -// WithAcceptedAccountTypesOnContractInstantiation sets the accepted account types. Account types of this list won't be overwritten or cause a failure -// when they exist for an address on contract instantiation. -// -// Values should be references and contain the `*authtypes.BaseAccount` as default bank account type. -func WithAcceptedAccountTypesOnContractInstantiation(accts ...authtypes.AccountI) Option { - m := asTypeMap(accts) - return optsFn(func(k *Keeper) { - k.acceptedAccountTypes = m - }) -} - -func asTypeMap(accts []authtypes.AccountI) map[reflect.Type]struct{} { - m := make(map[reflect.Type]struct{}, len(accts)) - for _, a := range accts { - if a == nil { - panic(types.ErrEmpty.Wrap("address")) - } - at := reflect.TypeOf(a) - if _, exists := m[at]; exists { - panic(types.ErrDuplicate.Wrapf("%T", a)) - } - m[at] = struct{}{} - } - return m -} diff --git a/x/wasm/keeper/options_test.go b/x/wasm/keeper/options_test.go deleted file mode 100644 index e2c6142..0000000 --- a/x/wasm/keeper/options_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package keeper - -import ( - "reflect" - "testing" - - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestConstructorOptions(t *testing.T) { - specs := map[string]struct { - srcOpt Option - verify func(*testing.T, Keeper) - }{ - "wasm engine": { - srcOpt: WithWasmEngine(&wasmtesting.MockWasmer{}), - verify: func(t *testing.T, k Keeper) { - assert.IsType(t, &wasmtesting.MockWasmer{}, k.wasmVM) - }, - }, - "message handler": { - srcOpt: WithMessageHandler(&wasmtesting.MockMessageHandler{}), - verify: func(t *testing.T, k Keeper) { - assert.IsType(t, &wasmtesting.MockMessageHandler{}, k.messenger) - }, - }, - "query plugins": { - srcOpt: WithQueryHandler(&wasmtesting.MockQueryHandler{}), - verify: func(t *testing.T, k Keeper) { - assert.IsType(t, &wasmtesting.MockQueryHandler{}, k.wasmVMQueryHandler) - }, - }, - "message handler decorator": { - srcOpt: WithMessageHandlerDecorator(func(old Messenger) Messenger { - require.IsType(t, &MessageHandlerChain{}, old) - return &wasmtesting.MockMessageHandler{} - }), - verify: func(t *testing.T, k Keeper) { - assert.IsType(t, &wasmtesting.MockMessageHandler{}, k.messenger) - }, - }, - "query plugins decorator": { - srcOpt: WithQueryHandlerDecorator(func(old WasmVMQueryHandler) WasmVMQueryHandler { - require.IsType(t, QueryPlugins{}, old) - return &wasmtesting.MockQueryHandler{} - }), - verify: func(t *testing.T, k Keeper) { - assert.IsType(t, &wasmtesting.MockQueryHandler{}, k.wasmVMQueryHandler) - }, - }, - "coin transferrer": { - srcOpt: WithCoinTransferrer(&wasmtesting.MockCoinTransferrer{}), - verify: func(t *testing.T, k Keeper) { - assert.IsType(t, &wasmtesting.MockCoinTransferrer{}, k.bank) - }, - }, - "costs": { - srcOpt: WithGasRegister(&wasmtesting.MockGasRegister{}), - verify: func(t *testing.T, k Keeper) { - assert.IsType(t, &wasmtesting.MockGasRegister{}, k.gasRegister) - }, - }, - "api costs": { - srcOpt: WithAPICosts(1, 2), - verify: func(t *testing.T, k Keeper) { - t.Cleanup(setAPIDefaults) - assert.Equal(t, uint64(1), costHumanize) - assert.Equal(t, uint64(2), costCanonical) - }, - }, - "max recursion query limit": { - srcOpt: WithMaxQueryStackSize(1), - verify: func(t *testing.T, k Keeper) { - assert.IsType(t, uint32(1), k.maxQueryStackSize) - }, - }, - "accepted account types": { - srcOpt: WithAcceptedAccountTypesOnContractInstantiation(&authtypes.BaseAccount{}, &vestingtypes.ContinuousVestingAccount{}), - verify: func(t *testing.T, k Keeper) { - exp := map[reflect.Type]struct{}{ - reflect.TypeOf(&authtypes.BaseAccount{}): {}, - reflect.TypeOf(&vestingtypes.ContinuousVestingAccount{}): {}, - } - assert.Equal(t, exp, k.acceptedAccountTypes) - }, - }, - "account pruner": { - srcOpt: WithAccountPruner(VestingCoinBurner{}), - verify: func(t *testing.T, k Keeper) { - assert.Equal(t, VestingCoinBurner{}, k.accountPruner) - }, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - k := NewKeeper(nil, nil, authkeeper.AccountKeeper{}, &bankkeeper.BaseKeeper{}, stakingkeeper.Keeper{}, nil, nil, nil, nil, nil, nil, nil, "tempDir", types.DefaultWasmConfig(), AvailableCapabilities, "", spec.srcOpt) - spec.verify(t, k) - }) - } -} - -func setAPIDefaults() { - costHumanize = DefaultGasCostHumanAddress * DefaultGasMultiplier - costCanonical = DefaultGasCostCanonicalAddress * DefaultGasMultiplier -} diff --git a/x/wasm/keeper/proposal_handler.go b/x/wasm/keeper/proposal_handler.go deleted file mode 100644 index 6cc6214..0000000 --- a/x/wasm/keeper/proposal_handler.go +++ /dev/null @@ -1,342 +0,0 @@ -package keeper - -import ( - "bytes" - "encoding/hex" - "fmt" - - errorsmod "cosmossdk.io/errors" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// NewWasmProposalHandler creates a new governance Handler for wasm proposals -// -// Deprecated: Do not use. -func NewWasmProposalHandler(k decoratedKeeper, enabledProposalTypes []types.ProposalType) v1beta1.Handler { - return NewWasmProposalHandlerX(NewGovPermissionKeeper(k), enabledProposalTypes) -} - -// NewWasmProposalHandlerX creates a new governance Handler for wasm proposals -// -// Deprecated: Do not use. -func NewWasmProposalHandlerX(k types.ContractOpsKeeper, enabledProposalTypes []types.ProposalType) v1beta1.Handler { - enabledTypes := make(map[string]struct{}, len(enabledProposalTypes)) - for i := range enabledProposalTypes { - enabledTypes[string(enabledProposalTypes[i])] = struct{}{} - } - return func(ctx sdk.Context, content v1beta1.Content) error { - if content == nil { - return errorsmod.Wrap(sdkerrors.ErrUnknownRequest, "content must not be empty") - } - if _, ok := enabledTypes[content.ProposalType()]; !ok { - return errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unsupported wasm proposal content type: %q", content.ProposalType()) - } - switch c := content.(type) { - case *types.StoreCodeProposal: - return handleStoreCodeProposal(ctx, k, *c) - case *types.InstantiateContractProposal: - return handleInstantiateProposal(ctx, k, *c) - case *types.InstantiateContract2Proposal: - return handleInstantiate2Proposal(ctx, k, *c) - case *types.MigrateContractProposal: - return handleMigrateProposal(ctx, k, *c) - case *types.SudoContractProposal: - return handleSudoProposal(ctx, k, *c) - case *types.ExecuteContractProposal: - return handleExecuteProposal(ctx, k, *c) - case *types.UpdateAdminProposal: - return handleUpdateAdminProposal(ctx, k, *c) - case *types.ClearAdminProposal: - return handleClearAdminProposal(ctx, k, *c) - case *types.PinCodesProposal: - return handlePinCodesProposal(ctx, k, *c) - case *types.UnpinCodesProposal: - return handleUnpinCodesProposal(ctx, k, *c) - case *types.UpdateInstantiateConfigProposal: - return handleUpdateInstantiateConfigProposal(ctx, k, *c) - case *types.StoreAndInstantiateContractProposal: - return handleStoreAndInstantiateContractProposal(ctx, k, *c) - default: - return errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized wasm proposal content type: %T", c) - } - } -} - -//nolint:staticcheck -func handleStoreCodeProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.StoreCodeProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - - runAsAddr, err := sdk.AccAddressFromBech32(p.RunAs) - if err != nil { - return errorsmod.Wrap(err, "run as address") - } - codeID, checksum, err := k.Create(ctx, runAsAddr, p.WASMByteCode, p.InstantiatePermission) - if err != nil { - return err - } - - if len(p.CodeHash) != 0 && !bytes.Equal(checksum, p.CodeHash) { - return fmt.Errorf("code-hash mismatch: %X, checksum: %X", p.CodeHash, checksum) - } - - // if code should not be pinned return earlier - if p.UnpinCode { - return nil - } - return k.PinCode(ctx, codeID) -} - -//nolint:staticcheck -func handleInstantiateProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.InstantiateContractProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - runAsAddr, err := sdk.AccAddressFromBech32(p.RunAs) - if err != nil { - return errorsmod.Wrap(err, "run as address") - } - var adminAddr sdk.AccAddress - if p.Admin != "" { - if adminAddr, err = sdk.AccAddressFromBech32(p.Admin); err != nil { - return errorsmod.Wrap(err, "admin") - } - } - - _, data, err := k.Instantiate(ctx, p.CodeID, runAsAddr, adminAddr, p.Msg, p.Label, p.Funds) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeGovContractResult, - sdk.NewAttribute(types.AttributeKeyResultDataHex, hex.EncodeToString(data)), - )) - return nil -} - -//nolint:staticcheck -func handleInstantiate2Proposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.InstantiateContract2Proposal) error { - // Validatebasic with proposal - if err := p.ValidateBasic(); err != nil { - return err - } - - // Get runAsAddr as AccAddress - runAsAddr, err := sdk.AccAddressFromBech32(p.RunAs) - if err != nil { - return errorsmod.Wrap(err, "run as address") - } - - // Get admin address - var adminAddr sdk.AccAddress - if p.Admin != "" { - if adminAddr, err = sdk.AccAddressFromBech32(p.Admin); err != nil { - return errorsmod.Wrap(err, "admin") - } - } - - _, data, err := k.Instantiate2(ctx, p.CodeID, runAsAddr, adminAddr, p.Msg, p.Label, p.Funds, p.Salt, p.FixMsg) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeGovContractResult, - sdk.NewAttribute(types.AttributeKeyResultDataHex, hex.EncodeToString(data)), - )) - return nil -} - -//nolint:staticcheck -func handleStoreAndInstantiateContractProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.StoreAndInstantiateContractProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - runAsAddr, err := sdk.AccAddressFromBech32(p.RunAs) - if err != nil { - return errorsmod.Wrap(err, "run as address") - } - var adminAddr sdk.AccAddress - if p.Admin != "" { - if adminAddr, err = sdk.AccAddressFromBech32(p.Admin); err != nil { - return errorsmod.Wrap(err, "admin") - } - } - - codeID, checksum, err := k.Create(ctx, runAsAddr, p.WASMByteCode, p.InstantiatePermission) - if err != nil { - return err - } - - if p.CodeHash != nil && !bytes.Equal(checksum, p.CodeHash) { - return errorsmod.Wrap(fmt.Errorf("code-hash mismatch: %X, checksum: %X", p.CodeHash, checksum), "code-hash mismatch") - } - - if !p.UnpinCode { - if err := k.PinCode(ctx, codeID); err != nil { - return err - } - } - - _, data, err := k.Instantiate(ctx, codeID, runAsAddr, adminAddr, p.Msg, p.Label, p.Funds) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeGovContractResult, - sdk.NewAttribute(types.AttributeKeyResultDataHex, hex.EncodeToString(data)), - )) - return nil -} - -//nolint:staticcheck -func handleMigrateProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.MigrateContractProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - - contractAddr, err := sdk.AccAddressFromBech32(p.Contract) - if err != nil { - return errorsmod.Wrap(err, "contract") - } - - // runAs is not used if this is permissioned, so just put any valid address there (second contractAddr) - data, err := k.Migrate(ctx, contractAddr, contractAddr, p.CodeID, p.Msg) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeGovContractResult, - sdk.NewAttribute(types.AttributeKeyResultDataHex, hex.EncodeToString(data)), - )) - return nil -} - -//nolint:staticcheck -func handleSudoProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.SudoContractProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - - contractAddr, err := sdk.AccAddressFromBech32(p.Contract) - if err != nil { - return errorsmod.Wrap(err, "contract") - } - data, err := k.Sudo(ctx, contractAddr, p.Msg) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeGovContractResult, - sdk.NewAttribute(types.AttributeKeyResultDataHex, hex.EncodeToString(data)), - )) - return nil -} - -//nolint:staticcheck -func handleExecuteProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.ExecuteContractProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - - contractAddr, err := sdk.AccAddressFromBech32(p.Contract) - if err != nil { - return errorsmod.Wrap(err, "contract") - } - runAsAddr, err := sdk.AccAddressFromBech32(p.RunAs) - if err != nil { - return errorsmod.Wrap(err, "run as address") - } - data, err := k.Execute(ctx, contractAddr, runAsAddr, p.Msg, p.Funds) - if err != nil { - return err - } - - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypeGovContractResult, - sdk.NewAttribute(types.AttributeKeyResultDataHex, hex.EncodeToString(data)), - )) - return nil -} - -//nolint:staticcheck -func handleUpdateAdminProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.UpdateAdminProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - contractAddr, err := sdk.AccAddressFromBech32(p.Contract) - if err != nil { - return errorsmod.Wrap(err, "contract") - } - newAdminAddr, err := sdk.AccAddressFromBech32(p.NewAdmin) - if err != nil { - return errorsmod.Wrap(err, "run as address") - } - - return k.UpdateContractAdmin(ctx, contractAddr, nil, newAdminAddr) -} - -//nolint:staticcheck -func handleClearAdminProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.ClearAdminProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - - contractAddr, err := sdk.AccAddressFromBech32(p.Contract) - if err != nil { - return errorsmod.Wrap(err, "contract") - } - err = k.ClearContractAdmin(ctx, contractAddr, nil) - return err -} - -//nolint:staticcheck -func handlePinCodesProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.PinCodesProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - for _, v := range p.CodeIDs { - if err := k.PinCode(ctx, v); err != nil { - return errorsmod.Wrapf(err, "code id: %d", v) - } - } - return nil -} - -//nolint:staticcheck -func handleUnpinCodesProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.UnpinCodesProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - for _, v := range p.CodeIDs { - if err := k.UnpinCode(ctx, v); err != nil { - return errorsmod.Wrapf(err, "code id: %d", v) - } - } - return nil -} - -//nolint:staticcheck -func handleUpdateInstantiateConfigProposal(ctx sdk.Context, k types.ContractOpsKeeper, p types.UpdateInstantiateConfigProposal) error { - if err := p.ValidateBasic(); err != nil { - return err - } - - var emptyCaller sdk.AccAddress - for _, accessConfigUpdate := range p.AccessConfigUpdates { - if err := k.SetAccessConfig(ctx, accessConfigUpdate.CodeID, emptyCaller, accessConfigUpdate.InstantiatePermission); err != nil { - return errorsmod.Wrapf(err, "code id: %d", accessConfigUpdate.CodeID) - } - } - return nil -} diff --git a/x/wasm/keeper/proposal_integration_test.go b/x/wasm/keeper/proposal_integration_test.go deleted file mode 100644 index 1b8d93d..0000000 --- a/x/wasm/keeper/proposal_integration_test.go +++ /dev/null @@ -1,926 +0,0 @@ -package keeper - -import ( - "bytes" - "encoding/hex" - "encoding/json" - "errors" - "os" - "testing" - - wasmvm "github.com/CosmWasm/wasmvm" - sdk "github.com/cosmos/cosmos-sdk/types" - govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -const myTestLabel = "testing" - -func TestStoreCodeProposal(t *testing.T) { - parentCtx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - err := wasmKeeper.SetParams(parentCtx, types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - rawWasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - gzippedWasmCode, err := os.ReadFile("./testdata/hackatom.wasm.gzip") - require.NoError(t, err) - checksum, err := hex.DecodeString("beb3de5e9b93b52e514c74ce87ccddb594b9bcd33b7f1af1bb6da63fc883917b") - require.NoError(t, err) - - specs := map[string]struct { - codeID int64 - code []byte - unpinCode bool - }{ - "upload with pinning (default)": { - unpinCode: false, - code: rawWasmCode, - }, - "upload with code unpin": { - unpinCode: true, - code: rawWasmCode, - }, - "upload with raw wasm code": { - code: rawWasmCode, - }, - "upload with zipped wasm code": { - code: gzippedWasmCode, - }, - } - - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - myActorAddress := RandomBech32AccountAddress(t) - - src := types.StoreCodeProposalFixture(func(p *types.StoreCodeProposal) { //nolint:staticcheck // testing a deprecated library - p.RunAs = myActorAddress - p.WASMByteCode = spec.code - p.UnpinCode = spec.unpinCode - p.CodeHash = checksum - }) - - // when - mustSubmitAndExecuteLegacyProposal(t, ctx, src, myActorAddress, keepers) - - // then - cInfo := wasmKeeper.GetCodeInfo(ctx, 1) - require.NotNil(t, cInfo) - assert.Equal(t, myActorAddress, cInfo.Creator) - assert.Equal(t, !spec.unpinCode, wasmKeeper.IsPinnedCode(ctx, 1)) - - storedCode, err := wasmKeeper.GetByteCode(ctx, 1) - require.NoError(t, err) - assert.Equal(t, rawWasmCode, storedCode) - }) - } -} - -func mustSubmitAndExecuteLegacyProposal(t *testing.T, ctx sdk.Context, content v1beta1.Content, myActorAddress string, keepers TestKeepers) { - t.Helper() - govAuthority := keepers.AccountKeeper.GetModuleAddress(govtypes.ModuleName).String() - msgServer := govkeeper.NewMsgServerImpl(keepers.GovKeeper) - // ignore all submit events - contentMsg, err := submitLegacyProposal(t, ctx.WithEventManager(sdk.NewEventManager()), content, myActorAddress, govAuthority, msgServer) - require.NoError(t, err) - - _, err = msgServer.ExecLegacyContent(sdk.WrapSDKContext(ctx), v1.NewMsgExecLegacyContent(contentMsg.Content, govAuthority)) - require.NoError(t, err) -} - -// does not fail on submit proposal -func submitLegacyProposal(t *testing.T, ctx sdk.Context, content v1beta1.Content, myActorAddress string, govAuthority string, msgServer v1.MsgServer) (*v1.MsgExecLegacyContent, error) { - t.Helper() - contentMsg, err := v1.NewLegacyContent(content, govAuthority) - require.NoError(t, err) - - proposal, err := v1.NewMsgSubmitProposal( - []sdk.Msg{contentMsg}, - sdk.Coins{}, - myActorAddress, - "", - "my title", - "my description", - ) - require.NoError(t, err) - - // when stored - _, err = msgServer.SubmitProposal(sdk.WrapSDKContext(ctx), proposal) - return contentMsg, err -} - -func TestInstantiateProposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - err := wasmKeeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - require.NoError(t, wasmKeeper.importCode(ctx, 1, - types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)), - wasmCode), - ) - - var ( - oneAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, types.ContractAddrLen) - otherAddress sdk.AccAddress = bytes.Repeat([]byte{0x2}, types.ContractAddrLen) - ) - src := types.InstantiateContractProposalFixture(func(p *types.InstantiateContractProposal) { //nolint:staticcheck // testing a deprecated library - p.CodeID = firstCodeID - p.RunAs = oneAddress.String() - p.Admin = otherAddress.String() - p.Label = myTestLabel - }) - em := sdk.NewEventManager() - - // when - mustSubmitAndExecuteLegacyProposal(t, ctx.WithEventManager(em), src, oneAddress.String(), keepers) - - // then - contractAddr, err := sdk.AccAddressFromBech32("cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr") - require.NoError(t, err) - - cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr) - require.NotNil(t, cInfo) - assert.Equal(t, uint64(1), cInfo.CodeID) - assert.Equal(t, oneAddress.String(), cInfo.Creator) - assert.Equal(t, otherAddress.String(), cInfo.Admin) - assert.Equal(t, myTestLabel, cInfo.Label) - expHistory := []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: src.CodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: src.Msg, - }} - assert.Equal(t, expHistory, wasmKeeper.GetContractHistory(ctx, contractAddr)) - // and event - require.Len(t, em.Events(), 3, "%#v", em.Events()) - require.Equal(t, types.EventTypeInstantiate, em.Events()[0].Type) - require.Equal(t, types.WasmModuleEventType, em.Events()[1].Type) - require.Equal(t, types.EventTypeGovContractResult, em.Events()[2].Type) - require.Len(t, em.Events()[2].Attributes, 1) - require.NotEmpty(t, em.Events()[2].Attributes[0]) -} - -func TestInstantiate2Proposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - err := wasmKeeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - codeInfo := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)) - err = wasmKeeper.importCode(ctx, 1, codeInfo, wasmCode) - require.NoError(t, err) - - var ( - oneAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, types.ContractAddrLen) - otherAddress sdk.AccAddress = bytes.Repeat([]byte{0x2}, types.ContractAddrLen) - label = "label" - salt = []byte("mySalt") - ) - src := types.InstantiateContract2ProposalFixture(func(p *types.InstantiateContract2Proposal) { //nolint:staticcheck // testing a deprecated library - p.CodeID = firstCodeID - p.RunAs = oneAddress.String() - p.Admin = otherAddress.String() - p.Label = label - p.Salt = salt - }) - contractAddress := BuildContractAddressPredictable(codeInfo.CodeHash, oneAddress, salt, []byte{}) - - em := sdk.NewEventManager() - - // when - mustSubmitAndExecuteLegacyProposal(t, ctx.WithEventManager(em), src, oneAddress.String(), keepers) - - cInfo := wasmKeeper.GetContractInfo(ctx, contractAddress) - require.NotNil(t, cInfo) - - assert.Equal(t, uint64(1), cInfo.CodeID) - assert.Equal(t, oneAddress.String(), cInfo.Creator) - assert.Equal(t, otherAddress.String(), cInfo.Admin) - assert.Equal(t, "label", cInfo.Label) - expHistory := []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: src.CodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: src.Msg, - }} - assert.Equal(t, expHistory, wasmKeeper.GetContractHistory(ctx, contractAddress)) - // and event - require.Len(t, em.Events(), 3, prettyEvents(t, em.Events())) - require.Equal(t, types.EventTypeInstantiate, em.Events()[0].Type) - require.Equal(t, types.WasmModuleEventType, em.Events()[1].Type) - require.Equal(t, types.EventTypeGovContractResult, em.Events()[2].Type) - require.Len(t, em.Events()[2].Attributes, 1) - require.NotEmpty(t, em.Events()[2].Attributes[0]) -} - -func TestInstantiateProposal_NoAdmin(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - err := wasmKeeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - require.NoError(t, wasmKeeper.importCode(ctx, 1, - types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)), - wasmCode), - ) - - var oneAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, types.ContractAddrLen) - - specs := map[string]struct { - srcAdmin string - expErr bool - }{ - "empty admin": { - srcAdmin: "", - }, - "invalid admin": { - srcAdmin: "invalid", - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - src := types.InstantiateContractProposalFixture(func(p *types.InstantiateContractProposal) { //nolint:staticcheck // testing a deprecated library - p.CodeID = firstCodeID - p.RunAs = oneAddress.String() - p.Admin = spec.srcAdmin - p.Label = myTestLabel - }) - govAuthority := keepers.AccountKeeper.GetModuleAddress(govtypes.ModuleName).String() - msgServer := govkeeper.NewMsgServerImpl(keepers.GovKeeper) - // when - contentMsg, gotErr := submitLegacyProposal(t, ctx, src, oneAddress.String(), govAuthority, msgServer) - // then - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - // and when - em := sdk.NewEventManager() - _, err = msgServer.ExecLegacyContent(sdk.WrapSDKContext(ctx.WithEventManager(em)), v1.NewMsgExecLegacyContent(contentMsg.Content, govAuthority)) - // then - require.NoError(t, err) - contractAddr, err := sdk.AccAddressFromBech32("cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr") - require.NoError(t, err) - - cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr) - require.NotNil(t, cInfo) - assert.Equal(t, uint64(1), cInfo.CodeID) - assert.Equal(t, oneAddress.String(), cInfo.Creator) - assert.Equal(t, "", cInfo.Admin) - assert.Equal(t, myTestLabel, cInfo.Label) - expHistory := []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: src.CodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: src.Msg, - }} - assert.Equal(t, expHistory, wasmKeeper.GetContractHistory(ctx, contractAddr)) - // and event - require.Len(t, em.Events(), 3, "%#v", em.Events()) - require.Equal(t, types.EventTypeInstantiate, em.Events()[0].Type) - require.Equal(t, types.WasmModuleEventType, em.Events()[1].Type) - require.Equal(t, types.EventTypeGovContractResult, em.Events()[2].Type) - require.Len(t, em.Events()[2].Attributes, 1) - require.NotEmpty(t, em.Events()[2].Attributes[0]) - }) - } -} - -func TestStoreAndInstantiateContractProposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - err := wasmKeeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - checksum, err := hex.DecodeString("beb3de5e9b93b52e514c74ce87ccddb594b9bcd33b7f1af1bb6da63fc883917b") - require.NoError(t, err) - - var ( - oneAddress sdk.AccAddress = bytes.Repeat([]byte{0x1}, types.ContractAddrLen) - otherAddress sdk.AccAddress = bytes.Repeat([]byte{0x2}, types.ContractAddrLen) - ) - - src := types.StoreAndInstantiateContractProposalFixture(func(p *types.StoreAndInstantiateContractProposal) { //nolint:staticcheck // testing a deprecated library - p.WASMByteCode = wasmCode - p.RunAs = oneAddress.String() - p.Admin = otherAddress.String() - p.Label = myTestLabel - p.CodeHash = checksum - }) - em := sdk.NewEventManager() - - // when - mustSubmitAndExecuteLegacyProposal(t, ctx.WithEventManager(em), src, oneAddress.String(), keepers) - - // then - contractAddr, err := sdk.AccAddressFromBech32("cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr") - require.NoError(t, err) - - cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr) - require.NotNil(t, cInfo) - assert.Equal(t, oneAddress.String(), cInfo.Creator) - assert.Equal(t, otherAddress.String(), cInfo.Admin) - assert.Equal(t, myTestLabel, cInfo.Label) - expHistory := []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: cInfo.CodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: src.Msg, - }} - assert.Equal(t, expHistory, wasmKeeper.GetContractHistory(ctx, contractAddr)) - // and event - require.Len(t, em.Events(), 5, "%#v", em.Events()) - require.Equal(t, types.EventTypeStoreCode, em.Events()[0].Type) - require.Equal(t, types.EventTypePinCode, em.Events()[1].Type) - require.Equal(t, types.EventTypeInstantiate, em.Events()[2].Type) - require.Equal(t, types.WasmModuleEventType, em.Events()[3].Type) - require.Equal(t, types.EventTypeGovContractResult, em.Events()[4].Type) - require.Len(t, em.Events()[4].Attributes, 1) - require.NotEmpty(t, em.Events()[4].Attributes[0]) -} - -func TestMigrateProposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - err := wasmKeeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - codeInfoFixture := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)) - require.NoError(t, wasmKeeper.importCode(ctx, 1, codeInfoFixture, wasmCode)) - require.NoError(t, wasmKeeper.importCode(ctx, 2, codeInfoFixture, wasmCode)) - - var ( - anyAddress = DeterministicAccountAddress(t, 1) - otherAddress = DeterministicAccountAddress(t, 2) - contractAddr = BuildContractAddressClassic(1, 1) - ) - - contractInfo := types.ContractInfoFixture(func(c *types.ContractInfo) { - c.Label = myTestLabel - c.Admin = anyAddress.String() - c.Created = types.NewAbsoluteTxPosition(ctx) - }) - entries := []types.ContractCodeHistoryEntry{ - {Operation: types.ContractCodeHistoryOperationTypeInit, CodeID: 1, Updated: contractInfo.Created}, - } - key, err := hex.DecodeString("636F6E666967") - require.NoError(t, err) - m := types.Model{Key: key, Value: []byte(`{"verifier":"AAAAAAAAAAAAAAAAAAAAAAAAAAA=","beneficiary":"AAAAAAAAAAAAAAAAAAAAAAAAAAA=","funder":"AQEBAQEBAQEBAQEBAQEBAQEBAQE="}`)} - require.NoError(t, wasmKeeper.importContract(ctx, contractAddr, &contractInfo, []types.Model{m}, entries)) - - migMsg := struct { - Verifier sdk.AccAddress `json:"verifier"` - }{Verifier: otherAddress} - migMsgBz, err := json.Marshal(migMsg) - require.NoError(t, err) - - src := &types.MigrateContractProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Foo", - Description: "Bar", - CodeID: 2, - Contract: contractAddr.String(), - Msg: migMsgBz, - } - - em := sdk.NewEventManager() - - // when - mustSubmitAndExecuteLegacyProposal(t, ctx.WithEventManager(em), src, anyAddress.String(), keepers) - - // then - require.NoError(t, err) - cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr) - require.NotNil(t, cInfo) - assert.Equal(t, uint64(2), cInfo.CodeID) - assert.Equal(t, anyAddress.String(), cInfo.Admin) - assert.Equal(t, myTestLabel, cInfo.Label) - expHistory := []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: firstCodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - }, { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: src.CodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: src.Msg, - }} - assert.Equal(t, expHistory, wasmKeeper.GetContractHistory(ctx, contractAddr)) - // and events emitted - require.Len(t, em.Events(), 2) - assert.Equal(t, types.EventTypeMigrate, em.Events()[0].Type) - require.Equal(t, types.EventTypeGovContractResult, em.Events()[1].Type) - require.Len(t, em.Events()[1].Attributes, 1) - assert.Equal(t, types.AttributeKeyResultDataHex, em.Events()[1].Attributes[0].Key) -} - -func TestExecuteProposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - bankKeeper := keepers.BankKeeper - - exampleContract := InstantiateHackatomExampleContract(t, ctx, keepers) - contractAddr := exampleContract.Contract - - // check balance - bal := bankKeeper.GetBalance(ctx, contractAddr, "denom") - require.Equal(t, bal.Amount, sdk.NewInt(100)) - - releaseMsg := struct { - Release struct{} `json:"release"` - }{} - releaseMsgBz, err := json.Marshal(releaseMsg) - require.NoError(t, err) - - // try with runAs that doesn't have pemission - badSrc := &types.ExecuteContractProposal{ //nolint:staticcheck // testing a deprecated library - Title: "First", - Description: "Beneficiary has no permission to run", - Contract: contractAddr.String(), - Msg: releaseMsgBz, - RunAs: exampleContract.BeneficiaryAddr.String(), - } - - // fails on store - this doesn't have permission - govAuthority := keepers.AccountKeeper.GetModuleAddress(govtypes.ModuleName).String() - msgServer := govkeeper.NewMsgServerImpl(keepers.GovKeeper) - _, err = submitLegacyProposal(t, ctx, badSrc, exampleContract.BeneficiaryAddr.String(), govAuthority, msgServer) - require.Error(t, err) - - // balance should not change - bal = bankKeeper.GetBalance(ctx, contractAddr, "denom") - require.Equal(t, bal.Amount, sdk.NewInt(100)) - - // try again with the proper run-as - src := &types.ExecuteContractProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Second", - Description: "Verifier can execute", - Contract: contractAddr.String(), - Msg: releaseMsgBz, - RunAs: exampleContract.VerifierAddr.String(), - } - - em := sdk.NewEventManager() - - // when - mustSubmitAndExecuteLegacyProposal(t, ctx.WithEventManager(em), src, exampleContract.BeneficiaryAddr.String(), keepers) - - // balance should be empty (proper release) - bal = bankKeeper.GetBalance(ctx, contractAddr, "denom") - require.Equal(t, bal.Amount, sdk.NewInt(0)) -} - -func TestSudoProposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - bankKeeper := keepers.BankKeeper - - exampleContract := InstantiateHackatomExampleContract(t, ctx, keepers) - contractAddr := exampleContract.Contract - _, anyAddr := keyPubAddr() - - // check balance - bal := bankKeeper.GetBalance(ctx, contractAddr, "denom") - require.Equal(t, bal.Amount, sdk.NewInt(100)) - bal = bankKeeper.GetBalance(ctx, anyAddr, "denom") - require.Equal(t, bal.Amount, sdk.NewInt(0)) - - type StealMsg struct { - Recipient string `json:"recipient"` - Amount []sdk.Coin `json:"amount"` - } - stealMsg := struct { - Steal StealMsg `json:"steal_funds"` - }{Steal: StealMsg{ - Recipient: anyAddr.String(), - Amount: []sdk.Coin{sdk.NewInt64Coin("denom", 75)}, - }} - stealMsgBz, err := json.Marshal(stealMsg) - require.NoError(t, err) - - // sudo can do anything - src := &types.SudoContractProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Sudo", - Description: "Steal funds for the verifier", - Contract: contractAddr.String(), - Msg: stealMsgBz, - } - - em := sdk.NewEventManager() - - // when - mustSubmitAndExecuteLegacyProposal(t, ctx.WithEventManager(em), src, exampleContract.BeneficiaryAddr.String(), keepers) - - // balance should be empty (and verifier richer) - bal = bankKeeper.GetBalance(ctx, contractAddr, "denom") - require.Equal(t, bal.Amount, sdk.NewInt(25)) - bal = bankKeeper.GetBalance(ctx, anyAddr, "denom") - require.Equal(t, bal.Amount, sdk.NewInt(75)) -} - -func TestAdminProposals(t *testing.T) { - var ( - otherAddress sdk.AccAddress = bytes.Repeat([]byte{0x2}, types.ContractAddrLen) - contractAddr = BuildContractAddressClassic(1, 1) - ) - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - specs := map[string]struct { - state types.ContractInfo - srcProposal v1beta1.Content - expAdmin sdk.AccAddress - }{ - "update with different admin": { - state: types.ContractInfoFixture(), - srcProposal: &types.UpdateAdminProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Foo", - Description: "Bar", - Contract: contractAddr.String(), - NewAdmin: otherAddress.String(), - }, - expAdmin: otherAddress, - }, - "update with old admin empty": { - state: types.ContractInfoFixture(func(info *types.ContractInfo) { - info.Admin = "" - }), - srcProposal: &types.UpdateAdminProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Foo", - Description: "Bar", - Contract: contractAddr.String(), - NewAdmin: otherAddress.String(), - }, - expAdmin: otherAddress, - }, - "clear admin": { - state: types.ContractInfoFixture(), - srcProposal: &types.ClearAdminProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Foo", - Description: "Bar", - Contract: contractAddr.String(), - }, - expAdmin: nil, - }, - "clear with old admin empty": { - state: types.ContractInfoFixture(func(info *types.ContractInfo) { - info.Admin = "" - }), - srcProposal: &types.ClearAdminProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Foo", - Description: "Bar", - Contract: contractAddr.String(), - }, - expAdmin: nil, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - err := wasmKeeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - - codeInfo := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)) - require.NoError(t, wasmKeeper.importCode(ctx, 1, codeInfo, wasmCode)) - - entries := []types.ContractCodeHistoryEntry{ - { - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: 1, - Updated: spec.state.Created, - }, - } - - require.NoError(t, wasmKeeper.importContract(ctx, contractAddr, &spec.state, []types.Model{}, entries)) - - // when - mustSubmitAndExecuteLegacyProposal(t, ctx, spec.srcProposal, otherAddress.String(), keepers) - - // then - cInfo := wasmKeeper.GetContractInfo(ctx, contractAddr) - require.NotNil(t, cInfo) - assert.Equal(t, spec.expAdmin.String(), cInfo.Admin) - }) - } -} - -func TestPinCodesProposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - - mock := wasmtesting.MockWasmer{ - CreateFn: wasmtesting.NoOpCreateFn, - AnalyzeCodeFn: wasmtesting.WithoutIBCAnalyzeFn, - } - var ( - hackatom = StoreHackatomExampleContract(t, ctx, keepers) - hackatomDuplicate = StoreHackatomExampleContract(t, ctx, keepers) - otherContract = StoreRandomContract(t, ctx, keepers, &mock) - gotPinnedChecksums []wasmvm.Checksum - ) - checksumCollector := func(checksum wasmvm.Checksum) error { - gotPinnedChecksums = append(gotPinnedChecksums, checksum) - return nil - } - specs := map[string]struct { - srcCodeIDs []uint64 - mockFn func(checksum wasmvm.Checksum) error - expPinned []wasmvm.Checksum - expErr bool - }{ - "pin one": { - srcCodeIDs: []uint64{hackatom.CodeID}, - mockFn: checksumCollector, - }, - "pin multiple": { - srcCodeIDs: []uint64{hackatom.CodeID, otherContract.CodeID}, - mockFn: checksumCollector, - }, - "pin same code id": { - srcCodeIDs: []uint64{hackatom.CodeID, hackatomDuplicate.CodeID}, - mockFn: checksumCollector, - }, - "pin non existing code id": { - srcCodeIDs: []uint64{999}, - mockFn: checksumCollector, - expErr: true, - }, - "pin empty code id list": { - srcCodeIDs: []uint64{}, - mockFn: checksumCollector, - expErr: true, - }, - "wasmvm failed with error": { - srcCodeIDs: []uint64{hackatom.CodeID}, - mockFn: func(_ wasmvm.Checksum) error { - return errors.New("test, ignore") - }, - expErr: true, - }, - } - parentCtx := ctx - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - myActorAddress := RandomBech32AccountAddress(t) - gotPinnedChecksums = nil - ctx, _ := parentCtx.CacheContext() - mock.PinFn = spec.mockFn - proposal := &types.PinCodesProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Foo", - Description: "Bar", - CodeIDs: spec.srcCodeIDs, - } - - govAuthority := keepers.AccountKeeper.GetModuleAddress(govtypes.ModuleName).String() - msgServer := govkeeper.NewMsgServerImpl(keepers.GovKeeper) - - // when - contentMsg, gotErr := submitLegacyProposal(t, ctx, proposal, myActorAddress, govAuthority, msgServer) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - - // and proposal execute - _, err := msgServer.ExecLegacyContent(sdk.WrapSDKContext(ctx), v1.NewMsgExecLegacyContent(contentMsg.Content, govAuthority)) - require.NoError(t, err) - - // then - for i := range spec.srcCodeIDs { - c := wasmKeeper.GetCodeInfo(ctx, spec.srcCodeIDs[i]) - require.Equal(t, wasmvm.Checksum(c.CodeHash), gotPinnedChecksums[i]) - } - }) - } -} - -func TestUnpinCodesProposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - - mock := wasmtesting.MockWasmer{ - CreateFn: wasmtesting.NoOpCreateFn, - AnalyzeCodeFn: wasmtesting.WithoutIBCAnalyzeFn, - } - var ( - hackatom = StoreHackatomExampleContract(t, ctx, keepers) - hackatomDuplicate = StoreHackatomExampleContract(t, ctx, keepers) - otherContract = StoreRandomContract(t, ctx, keepers, &mock) - gotUnpinnedChecksums []wasmvm.Checksum - ) - checksumCollector := func(checksum wasmvm.Checksum) error { - gotUnpinnedChecksums = append(gotUnpinnedChecksums, checksum) - return nil - } - specs := map[string]struct { - srcCodeIDs []uint64 - mockFn func(checksum wasmvm.Checksum) error - expUnpinned []wasmvm.Checksum - expErr bool - }{ - "unpin one": { - srcCodeIDs: []uint64{hackatom.CodeID}, - mockFn: checksumCollector, - }, - "unpin multiple": { - srcCodeIDs: []uint64{hackatom.CodeID, otherContract.CodeID}, - mockFn: checksumCollector, - }, - "unpin same code id": { - srcCodeIDs: []uint64{hackatom.CodeID, hackatomDuplicate.CodeID}, - mockFn: checksumCollector, - }, - "unpin non existing code id": { - srcCodeIDs: []uint64{999}, - mockFn: checksumCollector, - expErr: true, - }, - "unpin empty code id list": { - srcCodeIDs: []uint64{}, - mockFn: checksumCollector, - expErr: true, - }, - "wasmvm failed with error": { - srcCodeIDs: []uint64{hackatom.CodeID}, - mockFn: func(_ wasmvm.Checksum) error { - return errors.New("test, ignore") - }, - expErr: true, - }, - } - parentCtx := ctx - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - gotUnpinnedChecksums = nil - ctx, _ := parentCtx.CacheContext() - mock.UnpinFn = spec.mockFn - proposal := &types.UnpinCodesProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Foo", - Description: "Bar", - CodeIDs: spec.srcCodeIDs, - } - - govAuthority := keepers.AccountKeeper.GetModuleAddress(govtypes.ModuleName).String() - msgServer := govkeeper.NewMsgServerImpl(keepers.GovKeeper) - - // when - contentMsg, gotErr := submitLegacyProposal(t, ctx, proposal, RandomBech32AccountAddress(t), govAuthority, msgServer) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - - // and proposal execute - _, err := msgServer.ExecLegacyContent(sdk.WrapSDKContext(ctx), v1.NewMsgExecLegacyContent(contentMsg.Content, govAuthority)) - require.NoError(t, err) - - // then - for i := range spec.srcCodeIDs { - c := wasmKeeper.GetCodeInfo(ctx, spec.srcCodeIDs[i]) - require.Equal(t, wasmvm.Checksum(c.CodeHash), gotUnpinnedChecksums[i]) - } - }) - } -} - -func TestUpdateInstantiateConfigProposal(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, "staking") - wasmKeeper := keepers.WasmKeeper - - mock := wasmtesting.MockWasmer{ - CreateFn: wasmtesting.NoOpCreateFn, - AnalyzeCodeFn: wasmtesting.WithoutIBCAnalyzeFn, - } - anyAddress, err := sdk.AccAddressFromBech32("cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz") - require.NoError(t, err) - - withAddressAccessConfig := types.AccessTypeAnyOfAddresses.With(anyAddress) - var ( - nobody = StoreRandomContractWithAccessConfig(t, ctx, keepers, &mock, &types.AllowNobody) - everybody = StoreRandomContractWithAccessConfig(t, ctx, keepers, &mock, &types.AllowEverybody) - withAddress = StoreRandomContractWithAccessConfig(t, ctx, keepers, &mock, &withAddressAccessConfig) - ) - - specs := map[string]struct { - accessConfigUpdates []types.AccessConfigUpdate - expErr bool - }{ - "update one": { - accessConfigUpdates: []types.AccessConfigUpdate{ - {CodeID: nobody.CodeID, InstantiatePermission: types.AllowEverybody}, - }, - }, - "update multiple": { - accessConfigUpdates: []types.AccessConfigUpdate{ - {CodeID: everybody.CodeID, InstantiatePermission: types.AllowNobody}, - {CodeID: nobody.CodeID, InstantiatePermission: withAddressAccessConfig}, - {CodeID: withAddress.CodeID, InstantiatePermission: types.AllowEverybody}, - }, - }, - "update same code id": { - accessConfigUpdates: []types.AccessConfigUpdate{ - {CodeID: everybody.CodeID, InstantiatePermission: types.AllowNobody}, - {CodeID: everybody.CodeID, InstantiatePermission: types.AllowEverybody}, - }, - expErr: true, - }, - "update non existing code id": { - accessConfigUpdates: []types.AccessConfigUpdate{ - {CodeID: 100, InstantiatePermission: types.AllowNobody}, - {CodeID: everybody.CodeID, InstantiatePermission: types.AllowEverybody}, - }, - expErr: true, - }, - "update empty list": { - accessConfigUpdates: make([]types.AccessConfigUpdate, 0), - expErr: true, - }, - } - parentCtx := ctx - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - ctx, _ := parentCtx.CacheContext() - - updates := make([]types.AccessConfigUpdate, 0) - for _, cu := range spec.accessConfigUpdates { - updates = append(updates, types.AccessConfigUpdate{ - CodeID: cu.CodeID, - InstantiatePermission: cu.InstantiatePermission, - }) - } - - govAuthority := keepers.AccountKeeper.GetModuleAddress(govtypes.ModuleName).String() - msgServer := govkeeper.NewMsgServerImpl(keepers.GovKeeper) - proposal := &types.UpdateInstantiateConfigProposal{ //nolint:staticcheck // testing a deprecated library - Title: "Foo", - Description: "Bar", - AccessConfigUpdates: updates, - } - - // when - contentMsg, gotErr := submitLegacyProposal(t, ctx, proposal, RandomBech32AccountAddress(t), govAuthority, msgServer) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - - // and proposal execute - _, err := msgServer.ExecLegacyContent(sdk.WrapSDKContext(ctx), v1.NewMsgExecLegacyContent(contentMsg.Content, govAuthority)) - require.NoError(t, err) - - // then - for i := range spec.accessConfigUpdates { - c := wasmKeeper.GetCodeInfo(ctx, spec.accessConfigUpdates[i].CodeID) - require.Equal(t, spec.accessConfigUpdates[i].InstantiatePermission, c.InstantiateConfig) - } - }) - } -} diff --git a/x/wasm/keeper/querier.go b/x/wasm/keeper/querier.go deleted file mode 100644 index ae90461..0000000 --- a/x/wasm/keeper/querier.go +++ /dev/null @@ -1,347 +0,0 @@ -package keeper - -import ( - "context" - "encoding/binary" - "runtime/debug" - - errorsmod "cosmossdk.io/errors" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store/prefix" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/query" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var _ types.QueryServer = &GrpcQuerier{} - -type GrpcQuerier struct { - cdc codec.Codec - storeKey storetypes.StoreKey - keeper types.ViewKeeper - queryGasLimit sdk.Gas -} - -// NewGrpcQuerier constructor -func NewGrpcQuerier(cdc codec.Codec, storeKey storetypes.StoreKey, keeper types.ViewKeeper, queryGasLimit sdk.Gas) *GrpcQuerier { - return &GrpcQuerier{cdc: cdc, storeKey: storeKey, keeper: keeper, queryGasLimit: queryGasLimit} -} - -func (q GrpcQuerier) ContractInfo(c context.Context, req *types.QueryContractInfoRequest) (*types.QueryContractInfoResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - contractAddr, err := sdk.AccAddressFromBech32(req.Address) - if err != nil { - return nil, err - } - rsp, err := queryContractInfo(sdk.UnwrapSDKContext(c), contractAddr, q.keeper) - switch { - case err != nil: - return nil, err - case rsp == nil: - return nil, types.ErrNotFound - } - return rsp, nil -} - -func (q GrpcQuerier) ContractHistory(c context.Context, req *types.QueryContractHistoryRequest) (*types.QueryContractHistoryResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - contractAddr, err := sdk.AccAddressFromBech32(req.Address) - if err != nil { - return nil, err - } - - ctx := sdk.UnwrapSDKContext(c) - r := make([]types.ContractCodeHistoryEntry, 0) - - prefixStore := prefix.NewStore(ctx.KVStore(q.storeKey), types.GetContractCodeHistoryElementPrefix(contractAddr)) - pageRes, err := query.FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { - if accumulate { - var e types.ContractCodeHistoryEntry - if err := q.cdc.Unmarshal(value, &e); err != nil { - return false, err - } - r = append(r, e) - } - return true, nil - }) - if err != nil { - return nil, err - } - return &types.QueryContractHistoryResponse{ - Entries: r, - Pagination: pageRes, - }, nil -} - -// ContractsByCode lists all smart contracts for a code id -func (q GrpcQuerier) ContractsByCode(c context.Context, req *types.QueryContractsByCodeRequest) (*types.QueryContractsByCodeResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - if req.CodeId == 0 { - return nil, errorsmod.Wrap(types.ErrInvalid, "code id") - } - ctx := sdk.UnwrapSDKContext(c) - r := make([]string, 0) - - prefixStore := prefix.NewStore(ctx.KVStore(q.storeKey), types.GetContractByCodeIDSecondaryIndexPrefix(req.CodeId)) - pageRes, err := query.FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { - if accumulate { - var contractAddr sdk.AccAddress = key[types.AbsoluteTxPositionLen:] - r = append(r, contractAddr.String()) - } - return true, nil - }) - if err != nil { - return nil, err - } - return &types.QueryContractsByCodeResponse{ - Contracts: r, - Pagination: pageRes, - }, nil -} - -func (q GrpcQuerier) AllContractState(c context.Context, req *types.QueryAllContractStateRequest) (*types.QueryAllContractStateResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - contractAddr, err := sdk.AccAddressFromBech32(req.Address) - if err != nil { - return nil, err - } - ctx := sdk.UnwrapSDKContext(c) - if !q.keeper.HasContractInfo(ctx, contractAddr) { - return nil, types.ErrNotFound - } - - r := make([]types.Model, 0) - prefixStore := prefix.NewStore(ctx.KVStore(q.storeKey), types.GetContractStorePrefix(contractAddr)) - pageRes, err := query.FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { - if accumulate { - r = append(r, types.Model{ - Key: key, - Value: value, - }) - } - return true, nil - }) - if err != nil { - return nil, err - } - return &types.QueryAllContractStateResponse{ - Models: r, - Pagination: pageRes, - }, nil -} - -func (q GrpcQuerier) RawContractState(c context.Context, req *types.QueryRawContractStateRequest) (*types.QueryRawContractStateResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - ctx := sdk.UnwrapSDKContext(c) - - contractAddr, err := sdk.AccAddressFromBech32(req.Address) - if err != nil { - return nil, err - } - - if !q.keeper.HasContractInfo(ctx, contractAddr) { - return nil, types.ErrNotFound - } - rsp := q.keeper.QueryRaw(ctx, contractAddr, req.QueryData) - return &types.QueryRawContractStateResponse{Data: rsp}, nil -} - -func (q GrpcQuerier) SmartContractState(c context.Context, req *types.QuerySmartContractStateRequest) (rsp *types.QuerySmartContractStateResponse, err error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - if err := req.QueryData.ValidateBasic(); err != nil { - return nil, status.Error(codes.InvalidArgument, "invalid query data") - } - contractAddr, err := sdk.AccAddressFromBech32(req.Address) - if err != nil { - return nil, err - } - ctx := sdk.UnwrapSDKContext(c).WithGasMeter(sdk.NewGasMeter(q.queryGasLimit)) - // recover from out-of-gas panic - defer func() { - if r := recover(); r != nil { - switch rType := r.(type) { - case sdk.ErrorOutOfGas: - err = errorsmod.Wrapf(sdkerrors.ErrOutOfGas, - "out of gas in location: %v; gasWanted: %d, gasUsed: %d", - rType.Descriptor, ctx.GasMeter().Limit(), ctx.GasMeter().GasConsumed(), - ) - default: - err = sdkerrors.ErrPanic - } - rsp = nil - moduleLogger(ctx). - Debug("smart query contract", - "error", "recovering panic", - "contract-address", req.Address, - "stacktrace", string(debug.Stack())) - } - }() - - bz, err := q.keeper.QuerySmart(ctx, contractAddr, req.QueryData) - switch { - case err != nil: - return nil, err - case bz == nil: - return nil, types.ErrNotFound - } - return &types.QuerySmartContractStateResponse{Data: bz}, nil -} - -func (q GrpcQuerier) Code(c context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - if req.CodeId == 0 { - return nil, errorsmod.Wrap(types.ErrInvalid, "code id") - } - rsp, err := queryCode(sdk.UnwrapSDKContext(c), req.CodeId, q.keeper) - switch { - case err != nil: - return nil, err - case rsp == nil: - return nil, types.ErrNotFound - } - return &types.QueryCodeResponse{ - CodeInfoResponse: rsp.CodeInfoResponse, - Data: rsp.Data, - }, nil -} - -func (q GrpcQuerier) Codes(c context.Context, req *types.QueryCodesRequest) (*types.QueryCodesResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - ctx := sdk.UnwrapSDKContext(c) - r := make([]types.CodeInfoResponse, 0) - prefixStore := prefix.NewStore(ctx.KVStore(q.storeKey), types.CodeKeyPrefix) - pageRes, err := query.FilteredPaginate(prefixStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) { - if accumulate { - var c types.CodeInfo - if err := q.cdc.Unmarshal(value, &c); err != nil { - return false, err - } - r = append(r, types.CodeInfoResponse{ - CodeID: binary.BigEndian.Uint64(key), - Creator: c.Creator, - DataHash: c.CodeHash, - InstantiatePermission: c.InstantiateConfig, - }) - } - return true, nil - }) - if err != nil { - return nil, err - } - return &types.QueryCodesResponse{CodeInfos: r, Pagination: pageRes}, nil -} - -func queryContractInfo(ctx sdk.Context, addr sdk.AccAddress, keeper types.ViewKeeper) (*types.QueryContractInfoResponse, error) { - info := keeper.GetContractInfo(ctx, addr) - if info == nil { - return nil, types.ErrNotFound - } - return &types.QueryContractInfoResponse{ - Address: addr.String(), - ContractInfo: *info, - }, nil -} - -func queryCode(ctx sdk.Context, codeID uint64, keeper types.ViewKeeper) (*types.QueryCodeResponse, error) { - if codeID == 0 { - return nil, nil - } - res := keeper.GetCodeInfo(ctx, codeID) - if res == nil { - // nil, nil leads to 404 in rest handler - return nil, nil - } - info := types.CodeInfoResponse{ - CodeID: codeID, - Creator: res.Creator, - DataHash: res.CodeHash, - InstantiatePermission: res.InstantiateConfig, - } - - code, err := keeper.GetByteCode(ctx, codeID) - if err != nil { - return nil, errorsmod.Wrap(err, "loading wasm code") - } - - return &types.QueryCodeResponse{CodeInfoResponse: &info, Data: code}, nil -} - -func (q GrpcQuerier) PinnedCodes(c context.Context, req *types.QueryPinnedCodesRequest) (*types.QueryPinnedCodesResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - ctx := sdk.UnwrapSDKContext(c) - r := make([]uint64, 0) - - prefixStore := prefix.NewStore(ctx.KVStore(q.storeKey), types.PinnedCodeIndexPrefix) - pageRes, err := query.FilteredPaginate(prefixStore, req.Pagination, func(key []byte, _ []byte, accumulate bool) (bool, error) { - if accumulate { - r = append(r, sdk.BigEndianToUint64(key)) - } - return true, nil - }) - if err != nil { - return nil, err - } - return &types.QueryPinnedCodesResponse{ - CodeIDs: r, - Pagination: pageRes, - }, nil -} - -// Params returns params of the module. -func (q GrpcQuerier) Params(c context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { - ctx := sdk.UnwrapSDKContext(c) - params := q.keeper.GetParams(ctx) - return &types.QueryParamsResponse{Params: params}, nil -} - -func (q GrpcQuerier) ContractsByCreator(c context.Context, req *types.QueryContractsByCreatorRequest) (*types.QueryContractsByCreatorResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "empty request") - } - ctx := sdk.UnwrapSDKContext(c) - contracts := make([]string, 0) - - creatorAddress, err := sdk.AccAddressFromBech32(req.CreatorAddress) - if err != nil { - return nil, err - } - prefixStore := prefix.NewStore(ctx.KVStore(q.storeKey), types.GetContractsByCreatorPrefix(creatorAddress)) - pageRes, err := query.FilteredPaginate(prefixStore, req.Pagination, func(key []byte, _ []byte, accumulate bool) (bool, error) { - if accumulate { - accAddres := sdk.AccAddress(key[types.AbsoluteTxPositionLen:]) - contracts = append(contracts, accAddres.String()) - } - return true, nil - }) - if err != nil { - return nil, err - } - - return &types.QueryContractsByCreatorResponse{ - ContractAddresses: contracts, - Pagination: pageRes, - }, nil -} diff --git a/x/wasm/keeper/querier_test.go b/x/wasm/keeper/querier_test.go deleted file mode 100644 index 31cd190..0000000 --- a/x/wasm/keeper/querier_test.go +++ /dev/null @@ -1,919 +0,0 @@ -package keeper - -import ( - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "os" - "testing" - "time" - - errorsmod "cosmossdk.io/errors" - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/cometbft/cometbft/libs/log" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkErrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/query" - govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestQueryAllContractState(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - exampleContract := InstantiateHackatomExampleContract(t, ctx, keepers) - contractAddr := exampleContract.Contract - contractModel := []types.Model{ - {Key: []byte{0x0, 0x1}, Value: []byte(`{"count":8}`)}, - {Key: []byte("foo"), Value: []byte(`"bar"`)}, - } - require.NoError(t, keeper.importContractState(ctx, contractAddr, contractModel)) - - q := Querier(keeper) - specs := map[string]struct { - srcQuery *types.QueryAllContractStateRequest - expModelContains []types.Model - expModelContainsNot []types.Model - expErr *errorsmod.Error - }{ - "query all": { - srcQuery: &types.QueryAllContractStateRequest{Address: contractAddr.String()}, - expModelContains: contractModel, - }, - "query all with unknown address": { - srcQuery: &types.QueryAllContractStateRequest{Address: RandomBech32AccountAddress(t)}, - expErr: types.ErrNotFound, - }, - "with pagination offset": { - srcQuery: &types.QueryAllContractStateRequest{ - Address: contractAddr.String(), - Pagination: &query.PageRequest{ - Offset: 1, - }, - }, - expModelContains: []types.Model{ - {Key: []byte("foo"), Value: []byte(`"bar"`)}, - }, - expModelContainsNot: []types.Model{ - {Key: []byte{0x0, 0x1}, Value: []byte(`{"count":8}`)}, - }, - }, - "with pagination limit": { - srcQuery: &types.QueryAllContractStateRequest{ - Address: contractAddr.String(), - Pagination: &query.PageRequest{ - Limit: 1, - }, - }, - expModelContains: []types.Model{ - {Key: []byte{0x0, 0x1}, Value: []byte(`{"count":8}`)}, - }, - expModelContainsNot: []types.Model{ - {Key: []byte("foo"), Value: []byte(`"bar"`)}, - }, - }, - "with pagination next key": { - srcQuery: &types.QueryAllContractStateRequest{ - Address: contractAddr.String(), - Pagination: &query.PageRequest{ - Key: fromBase64("Y29uZmln"), - }, - }, - expModelContains: []types.Model{ - {Key: []byte("foo"), Value: []byte(`"bar"`)}, - }, - expModelContainsNot: []types.Model{ - {Key: []byte{0x0, 0x1}, Value: []byte(`{"count":8}`)}, - }, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - got, err := q.AllContractState(sdk.WrapSDKContext(ctx), spec.srcQuery) - require.True(t, spec.expErr.Is(err), err) - if spec.expErr != nil { - return - } - for _, exp := range spec.expModelContains { - assert.Contains(t, got.Models, exp) - } - for _, exp := range spec.expModelContainsNot { - assert.NotContains(t, got.Models, exp) - } - }) - } -} - -func TestQuerySmartContractState(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - exampleContract := InstantiateHackatomExampleContract(t, ctx, keepers) - contractAddr := exampleContract.Contract.String() - - q := Querier(keeper) - specs := map[string]struct { - srcAddr sdk.AccAddress - srcQuery *types.QuerySmartContractStateRequest - expResp string - expErr error - }{ - "query smart": { - srcQuery: &types.QuerySmartContractStateRequest{Address: contractAddr, QueryData: []byte(`{"verifier":{}}`)}, - expResp: fmt.Sprintf(`{"verifier":"%s"}`, exampleContract.VerifierAddr.String()), - }, - "query smart invalid request": { - srcQuery: &types.QuerySmartContractStateRequest{Address: contractAddr, QueryData: []byte(`{"raw":{"key":"config"}}`)}, - expErr: types.ErrQueryFailed, - }, - "query smart with invalid json": { - srcQuery: &types.QuerySmartContractStateRequest{Address: contractAddr, QueryData: []byte(`not a json string`)}, - expErr: status.Error(codes.InvalidArgument, "invalid query data"), - }, - "query smart with unknown address": { - srcQuery: &types.QuerySmartContractStateRequest{Address: RandomBech32AccountAddress(t), QueryData: []byte(`{"verifier":{}}`)}, - expErr: types.ErrNotFound, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - got, err := q.SmartContractState(sdk.WrapSDKContext(ctx), spec.srcQuery) - require.True(t, errors.Is(err, spec.expErr), "but got %+v", err) - if spec.expErr != nil { - return - } - assert.JSONEq(t, string(got.Data), spec.expResp) - }) - } -} - -func TestQuerySmartContractPanics(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - contractAddr := BuildContractAddressClassic(1, 1) - keepers.WasmKeeper.storeCodeInfo(ctx, 1, types.CodeInfo{}) - keepers.WasmKeeper.storeContractInfo(ctx, contractAddr, &types.ContractInfo{ - CodeID: 1, - Created: types.NewAbsoluteTxPosition(ctx), - }) - ctx = ctx.WithGasMeter(sdk.NewGasMeter(DefaultInstanceCost)).WithLogger(log.TestingLogger()) - - specs := map[string]struct { - doInContract func() - expErr *errorsmod.Error - }{ - "out of gas": { - doInContract: func() { - ctx.GasMeter().ConsumeGas(ctx.GasMeter().Limit()+1, "test - consume more than limit") - }, - expErr: sdkErrors.ErrOutOfGas, - }, - "other panic": { - doInContract: func() { - panic("my panic") - }, - expErr: sdkErrors.ErrPanic, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - keepers.WasmKeeper.wasmVM = &wasmtesting.MockWasmer{QueryFn: func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { - spec.doInContract() - return nil, 0, nil - }} - // when - q := Querier(keepers.WasmKeeper) - got, err := q.SmartContractState(sdk.WrapSDKContext(ctx), &types.QuerySmartContractStateRequest{ - Address: contractAddr.String(), - QueryData: types.RawContractMessage("{}"), - }) - require.True(t, spec.expErr.Is(err), "got error: %+v", err) - assert.Nil(t, got) - }) - } -} - -func TestQueryRawContractState(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - exampleContract := InstantiateHackatomExampleContract(t, ctx, keepers) - contractAddr := exampleContract.Contract.String() - contractModel := []types.Model{ - {Key: []byte("foo"), Value: []byte(`"bar"`)}, - {Key: []byte{0x0, 0x1}, Value: []byte(`{"count":8}`)}, - } - require.NoError(t, keeper.importContractState(ctx, exampleContract.Contract, contractModel)) - - q := Querier(keeper) - specs := map[string]struct { - srcQuery *types.QueryRawContractStateRequest - expData []byte - expErr *errorsmod.Error - }{ - "query raw key": { - srcQuery: &types.QueryRawContractStateRequest{Address: contractAddr, QueryData: []byte("foo")}, - expData: []byte(`"bar"`), - }, - "query raw contract binary key": { - srcQuery: &types.QueryRawContractStateRequest{Address: contractAddr, QueryData: []byte{0x0, 0x1}}, - expData: []byte(`{"count":8}`), - }, - "query non-existent raw key": { - srcQuery: &types.QueryRawContractStateRequest{Address: contractAddr, QueryData: []byte("not existing key")}, - expData: nil, - }, - "query empty raw key": { - srcQuery: &types.QueryRawContractStateRequest{Address: contractAddr, QueryData: []byte("")}, - expData: nil, - }, - "query nil raw key": { - srcQuery: &types.QueryRawContractStateRequest{Address: contractAddr}, - expData: nil, - }, - "query raw with unknown address": { - srcQuery: &types.QueryRawContractStateRequest{Address: RandomBech32AccountAddress(t), QueryData: []byte("foo")}, - expErr: types.ErrNotFound, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - got, err := q.RawContractState(sdk.WrapSDKContext(ctx), spec.srcQuery) - require.True(t, spec.expErr.Is(err), err) - if spec.expErr != nil { - return - } - assert.Equal(t, spec.expData, got.Data) - }) - } -} - -func TestQueryContractListByCodeOrdering(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 500)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - anyAddr := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, wasmCode, nil) - require.NoError(t, err) - - _, bob := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: anyAddr, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - // manage some realistic block settings - var h int64 = 10 - setBlock := func(ctx sdk.Context, height int64) sdk.Context { - ctx = ctx.WithBlockHeight(height) - meter := sdk.NewGasMeter(1000000) - ctx = ctx.WithGasMeter(meter) - ctx = ctx.WithBlockGasMeter(meter) - return ctx - } - - // create 10 contracts with real block/gas setup - for i := 0; i < 10; i++ { - // 3 tx per block, so we ensure both comparisons work - if i%3 == 0 { - ctx = setBlock(ctx, h) - h++ - } - _, _, err = keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, fmt.Sprintf("contract %d", i), topUp) - require.NoError(t, err) - } - - // query and check the results are properly sorted - q := Querier(keeper) - res, err := q.ContractsByCode(sdk.WrapSDKContext(ctx), &types.QueryContractsByCodeRequest{CodeId: codeID}) - require.NoError(t, err) - - require.Equal(t, 10, len(res.Contracts)) - - for _, contractAddr := range res.Contracts { - assert.NotEmpty(t, contractAddr) - } -} - -func TestQueryContractHistory(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - var ( - myContractBech32Addr = RandomBech32AccountAddress(t) - otherBech32Addr = RandomBech32AccountAddress(t) - ) - - specs := map[string]struct { - srcHistory []types.ContractCodeHistoryEntry - req types.QueryContractHistoryRequest - expContent []types.ContractCodeHistoryEntry - }{ - "response with internal fields cleared": { - srcHistory: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeGenesis, - CodeID: firstCodeID, - Updated: &types.AbsoluteTxPosition{BlockHeight: 1, TxIndex: 2}, - Msg: []byte(`"init message"`), - }}, - req: types.QueryContractHistoryRequest{Address: myContractBech32Addr}, - expContent: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeGenesis, - CodeID: firstCodeID, - Msg: []byte(`"init message"`), - Updated: &types.AbsoluteTxPosition{BlockHeight: 1, TxIndex: 2}, - }}, - }, - "response with multiple entries": { - srcHistory: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: firstCodeID, - Updated: &types.AbsoluteTxPosition{BlockHeight: 1, TxIndex: 2}, - Msg: []byte(`"init message"`), - }, { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 2, - Updated: &types.AbsoluteTxPosition{BlockHeight: 3, TxIndex: 4}, - Msg: []byte(`"migrate message 1"`), - }, { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 3, - Updated: &types.AbsoluteTxPosition{BlockHeight: 5, TxIndex: 6}, - Msg: []byte(`"migrate message 2"`), - }}, - req: types.QueryContractHistoryRequest{Address: myContractBech32Addr}, - expContent: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: firstCodeID, - Msg: []byte(`"init message"`), - Updated: &types.AbsoluteTxPosition{BlockHeight: 1, TxIndex: 2}, - }, { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 2, - Msg: []byte(`"migrate message 1"`), - Updated: &types.AbsoluteTxPosition{BlockHeight: 3, TxIndex: 4}, - }, { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 3, - Msg: []byte(`"migrate message 2"`), - Updated: &types.AbsoluteTxPosition{BlockHeight: 5, TxIndex: 6}, - }}, - }, - "with pagination offset": { - srcHistory: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: firstCodeID, - Updated: &types.AbsoluteTxPosition{BlockHeight: 1, TxIndex: 2}, - Msg: []byte(`"init message"`), - }, { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 2, - Updated: &types.AbsoluteTxPosition{BlockHeight: 3, TxIndex: 4}, - Msg: []byte(`"migrate message 1"`), - }}, - req: types.QueryContractHistoryRequest{ - Address: myContractBech32Addr, - Pagination: &query.PageRequest{ - Offset: 1, - }, - }, - expContent: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 2, - Msg: []byte(`"migrate message 1"`), - Updated: &types.AbsoluteTxPosition{BlockHeight: 3, TxIndex: 4}, - }}, - }, - "with pagination limit": { - srcHistory: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: firstCodeID, - Updated: &types.AbsoluteTxPosition{BlockHeight: 1, TxIndex: 2}, - Msg: []byte(`"init message"`), - }, { - Operation: types.ContractCodeHistoryOperationTypeMigrate, - CodeID: 2, - Updated: &types.AbsoluteTxPosition{BlockHeight: 3, TxIndex: 4}, - Msg: []byte(`"migrate message 1"`), - }}, - req: types.QueryContractHistoryRequest{ - Address: myContractBech32Addr, - Pagination: &query.PageRequest{ - Limit: 1, - }, - }, - expContent: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeInit, - CodeID: firstCodeID, - Msg: []byte(`"init message"`), - Updated: &types.AbsoluteTxPosition{BlockHeight: 1, TxIndex: 2}, - }}, - }, - "unknown contract address": { - req: types.QueryContractHistoryRequest{Address: otherBech32Addr}, - srcHistory: []types.ContractCodeHistoryEntry{{ - Operation: types.ContractCodeHistoryOperationTypeGenesis, - CodeID: firstCodeID, - Updated: types.NewAbsoluteTxPosition(ctx), - Msg: []byte(`"init message"`), - }}, - expContent: nil, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - xCtx, _ := ctx.CacheContext() - - cAddr, _ := sdk.AccAddressFromBech32(myContractBech32Addr) - keeper.appendToContractHistory(xCtx, cAddr, spec.srcHistory...) - - // when - q := Querier(keeper) - got, err := q.ContractHistory(sdk.WrapSDKContext(xCtx), &spec.req) - - // then - if spec.expContent == nil { - require.Error(t, types.ErrEmpty) - return - } - require.NoError(t, err) - assert.Equal(t, spec.expContent, got.Entries) - }) - } -} - -func TestQueryCodeList(t *testing.T) { - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - specs := map[string]struct { - storedCodeIDs []uint64 - req types.QueryCodesRequest - expCodeIDs []uint64 - }{ - "none": {}, - "no gaps": { - storedCodeIDs: []uint64{1, 2, 3}, - expCodeIDs: []uint64{1, 2, 3}, - }, - "with gaps": { - storedCodeIDs: []uint64{2, 4, 6}, - expCodeIDs: []uint64{2, 4, 6}, - }, - "with pagination offset": { - storedCodeIDs: []uint64{1, 2, 3}, - req: types.QueryCodesRequest{ - Pagination: &query.PageRequest{ - Offset: 1, - }, - }, - expCodeIDs: []uint64{2, 3}, - }, - "with pagination limit": { - storedCodeIDs: []uint64{1, 2, 3}, - req: types.QueryCodesRequest{ - Pagination: &query.PageRequest{ - Limit: 2, - }, - }, - expCodeIDs: []uint64{1, 2}, - }, - "with pagination next key": { - storedCodeIDs: []uint64{1, 2, 3}, - req: types.QueryCodesRequest{ - Pagination: &query.PageRequest{ - Key: fromBase64("AAAAAAAAAAI="), - }, - }, - expCodeIDs: []uint64{2, 3}, - }, - } - - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - xCtx, _ := ctx.CacheContext() - - for _, codeID := range spec.storedCodeIDs { - require.NoError(t, keeper.importCode(xCtx, codeID, - types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)), - wasmCode), - ) - } - // when - q := Querier(keeper) - got, err := q.Codes(sdk.WrapSDKContext(xCtx), &spec.req) - - // then - require.NoError(t, err) - require.NotNil(t, got.CodeInfos) - require.Len(t, got.CodeInfos, len(spec.expCodeIDs)) - for i, exp := range spec.expCodeIDs { - assert.EqualValues(t, exp, got.CodeInfos[i].CodeID) - } - }) - } -} - -func TestQueryContractInfo(t *testing.T) { - var ( - contractAddr = RandomAccountAddress(t) - anyDate = time.Now().UTC() - ) - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - // register an example extension. must be protobuf - keepers.EncodingConfig.InterfaceRegistry.RegisterImplementations( - (*types.ContractInfoExtension)(nil), - &govv1beta1.Proposal{}, - ) - govv1beta1.RegisterInterfaces(keepers.EncodingConfig.InterfaceRegistry) - - k := keepers.WasmKeeper - querier := NewGrpcQuerier(k.cdc, k.storeKey, k, k.queryGasLimit) - myExtension := func(info *types.ContractInfo) { - // abuse gov proposal as a random protobuf extension with an Any type - myExt, err := govv1beta1.NewProposal(&govv1beta1.TextProposal{Title: "foo", Description: "bar"}, 1, anyDate, anyDate) - require.NoError(t, err) - myExt.TotalDeposit = nil - err = info.SetExtension(&myExt) - require.NoError(t, err) - } - specs := map[string]struct { - src *types.QueryContractInfoRequest - stored types.ContractInfo - expRsp *types.QueryContractInfoResponse - expErr bool - }{ - "found": { - src: &types.QueryContractInfoRequest{Address: contractAddr.String()}, - stored: types.ContractInfoFixture(), - expRsp: &types.QueryContractInfoResponse{ - Address: contractAddr.String(), - ContractInfo: types.ContractInfoFixture(), - }, - }, - "with extension": { - src: &types.QueryContractInfoRequest{Address: contractAddr.String()}, - stored: types.ContractInfoFixture(myExtension), - expRsp: &types.QueryContractInfoResponse{ - Address: contractAddr.String(), - ContractInfo: types.ContractInfoFixture(myExtension), - }, - }, - "not found": { - src: &types.QueryContractInfoRequest{Address: RandomBech32AccountAddress(t)}, - stored: types.ContractInfoFixture(), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - xCtx, _ := ctx.CacheContext() - k.storeContractInfo(xCtx, contractAddr, &spec.stored) - // when - gotRsp, gotErr := querier.ContractInfo(sdk.WrapSDKContext(xCtx), spec.src) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.Equal(t, spec.expRsp, gotRsp) - }) - } -} - -func TestQueryPinnedCodes(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - exampleContract1 := InstantiateHackatomExampleContract(t, ctx, keepers) - exampleContract2 := InstantiateIBCReflectContract(t, ctx, keepers) - require.NoError(t, keeper.pinCode(ctx, exampleContract1.CodeID)) - require.NoError(t, keeper.pinCode(ctx, exampleContract2.CodeID)) - - q := Querier(keeper) - specs := map[string]struct { - srcQuery *types.QueryPinnedCodesRequest - expCodeIDs []uint64 - expErr *errorsmod.Error - }{ - "query all": { - srcQuery: &types.QueryPinnedCodesRequest{}, - expCodeIDs: []uint64{exampleContract1.CodeID, exampleContract2.CodeID}, - }, - "with pagination offset": { - srcQuery: &types.QueryPinnedCodesRequest{ - Pagination: &query.PageRequest{ - Offset: 1, - }, - }, - expCodeIDs: []uint64{exampleContract2.CodeID}, - }, - "with pagination limit": { - srcQuery: &types.QueryPinnedCodesRequest{ - Pagination: &query.PageRequest{ - Limit: 1, - }, - }, - expCodeIDs: []uint64{exampleContract1.CodeID}, - }, - "with pagination next key": { - srcQuery: &types.QueryPinnedCodesRequest{ - Pagination: &query.PageRequest{ - Key: fromBase64("AAAAAAAAAAM="), - }, - }, - expCodeIDs: []uint64{exampleContract2.CodeID}, - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - got, err := q.PinnedCodes(sdk.WrapSDKContext(ctx), spec.srcQuery) - require.True(t, spec.expErr.Is(err), err) - if spec.expErr != nil { - return - } - require.NotNil(t, got) - assert.Equal(t, spec.expCodeIDs, got.CodeIDs) - }) - } -} - -func TestQueryParams(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - q := Querier(keeper) - - paramsResponse, err := q.Params(sdk.WrapSDKContext(ctx), &types.QueryParamsRequest{}) - require.NoError(t, err) - require.NotNil(t, paramsResponse) - - defaultParams := types.DefaultParams() - - require.Equal(t, paramsResponse.Params.CodeUploadAccess, defaultParams.CodeUploadAccess) - require.Equal(t, paramsResponse.Params.InstantiateDefaultPermission, defaultParams.InstantiateDefaultPermission) - - err = keeper.SetParams(ctx, types.Params{ - CodeUploadAccess: types.AllowNobody, - InstantiateDefaultPermission: types.AccessTypeNobody, - }) - require.NoError(t, err) - - paramsResponse, err = q.Params(sdk.WrapSDKContext(ctx), &types.QueryParamsRequest{}) - require.NoError(t, err) - require.NotNil(t, paramsResponse) - - require.Equal(t, paramsResponse.Params.CodeUploadAccess, types.AllowNobody) - require.Equal(t, paramsResponse.Params.InstantiateDefaultPermission, types.AccessTypeNobody) -} - -func TestQueryCodeInfo(t *testing.T) { - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - anyAddress, err := sdk.AccAddressFromBech32("cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz") - require.NoError(t, err) - specs := map[string]struct { - codeID uint64 - accessConfig types.AccessConfig - }{ - "everybody": { - codeID: 1, - accessConfig: types.AllowEverybody, - }, - "nobody": { - codeID: 10, - accessConfig: types.AllowNobody, - }, - "with_address": { - codeID: 20, - accessConfig: types.AccessTypeOnlyAddress.With(anyAddress), - }, - } - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - codeInfo := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)) - codeInfo.InstantiateConfig = spec.accessConfig - require.NoError(t, keeper.importCode(ctx, spec.codeID, - codeInfo, - wasmCode), - ) - - q := Querier(keeper) - got, err := q.Code(sdk.WrapSDKContext(ctx), &types.QueryCodeRequest{ - CodeId: spec.codeID, - }) - require.NoError(t, err) - expectedResponse := &types.QueryCodeResponse{ - CodeInfoResponse: &types.CodeInfoResponse{ - CodeID: spec.codeID, - Creator: codeInfo.Creator, - DataHash: codeInfo.CodeHash, - InstantiatePermission: spec.accessConfig, - }, - Data: wasmCode, - } - require.NotNil(t, got.CodeInfoResponse) - require.EqualValues(t, expectedResponse, got) - }) - } -} - -func TestQueryCodeInfoList(t *testing.T) { - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - keeper := keepers.WasmKeeper - - anyAddress, err := sdk.AccAddressFromBech32("cosmos100dejzacpanrldpjjwksjm62shqhyss44jf5xz") - require.NoError(t, err) - codeInfoWithConfig := func(accessConfig types.AccessConfig) types.CodeInfo { - codeInfo := types.CodeInfoFixture(types.WithSHA256CodeHash(wasmCode)) - codeInfo.InstantiateConfig = accessConfig - return codeInfo - } - - codes := []struct { - name string - codeID uint64 - codeInfo types.CodeInfo - }{ - { - name: "everybody", - codeID: 1, - codeInfo: codeInfoWithConfig(types.AllowEverybody), - }, - { - codeID: 10, - name: "nobody", - codeInfo: codeInfoWithConfig(types.AllowNobody), - }, - { - name: "with_address", - codeID: 20, - codeInfo: codeInfoWithConfig(types.AccessTypeOnlyAddress.With(anyAddress)), - }, - } - - allCodesResponse := make([]types.CodeInfoResponse, 0) - for _, code := range codes { - t.Run(fmt.Sprintf("import_%s", code.name), func(t *testing.T) { - require.NoError(t, keeper.importCode(ctx, code.codeID, - code.codeInfo, - wasmCode), - ) - }) - - allCodesResponse = append(allCodesResponse, types.CodeInfoResponse{ - CodeID: code.codeID, - Creator: code.codeInfo.Creator, - DataHash: code.codeInfo.CodeHash, - InstantiatePermission: code.codeInfo.InstantiateConfig, - }) - } - q := Querier(keeper) - got, err := q.Codes(sdk.WrapSDKContext(ctx), &types.QueryCodesRequest{ - Pagination: &query.PageRequest{ - Limit: 3, - }, - }) - require.NoError(t, err) - require.Len(t, got.CodeInfos, 3) - require.EqualValues(t, allCodesResponse, got.CodeInfos) -} - -func TestQueryContractsByCreatorList(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities) - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000000)) - topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 500)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - anyAddr := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...) - - wasmCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - - codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, wasmCode, nil) - require.NoError(t, err) - - _, bob := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: anyAddr, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - - // manage some realistic block settings - var h int64 = 10 - setBlock := func(ctx sdk.Context, height int64) sdk.Context { - ctx = ctx.WithBlockHeight(height) - meter := sdk.NewGasMeter(1000000) - ctx = ctx.WithGasMeter(meter) - ctx = ctx.WithBlockGasMeter(meter) - return ctx - } - - var allExpecedContracts []string - // create 10 contracts with real block/gas setup - for i := 0; i < 10; i++ { - ctx = setBlock(ctx, h) - h++ - contract, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, fmt.Sprintf("contract %d", i), topUp) - allExpecedContracts = append(allExpecedContracts, contract.String()) - require.NoError(t, err) - } - - specs := map[string]struct { - srcQuery *types.QueryContractsByCreatorRequest - expContractAddr []string - expErr error - }{ - "query all": { - srcQuery: &types.QueryContractsByCreatorRequest{ - CreatorAddress: creator.String(), - }, - expContractAddr: allExpecedContracts, - expErr: nil, - }, - "with pagination offset": { - srcQuery: &types.QueryContractsByCreatorRequest{ - CreatorAddress: creator.String(), - Pagination: &query.PageRequest{ - Offset: 1, - }, - }, - expContractAddr: allExpecedContracts[1:], - expErr: nil, - }, - "with pagination limit": { - srcQuery: &types.QueryContractsByCreatorRequest{ - CreatorAddress: creator.String(), - Pagination: &query.PageRequest{ - Limit: 1, - }, - }, - expContractAddr: allExpecedContracts[0:1], - expErr: nil, - }, - "nil creator": { - srcQuery: &types.QueryContractsByCreatorRequest{ - Pagination: &query.PageRequest{}, - }, - expContractAddr: allExpecedContracts, - expErr: errors.New("empty address string is not allowed"), - }, - "nil req": { - srcQuery: nil, - expContractAddr: allExpecedContracts, - expErr: status.Error(codes.InvalidArgument, "empty request"), - }, - } - - q := Querier(keepers.WasmKeeper) - for msg, spec := range specs { - t.Run(msg, func(t *testing.T) { - got, err := q.ContractsByCreator(sdk.WrapSDKContext(ctx), spec.srcQuery) - - if spec.expErr != nil { - require.Equal(t, spec.expErr, err) - return - } - require.NoError(t, err) - require.NotNil(t, got) - assert.Equal(t, spec.expContractAddr, got.ContractAddresses) - }) - } -} - -func fromBase64(s string) []byte { - r, err := base64.StdEncoding.DecodeString(s) - if err != nil { - panic(err) - } - return r -} diff --git a/x/wasm/keeper/query_plugins.go b/x/wasm/keeper/query_plugins.go deleted file mode 100644 index b85c582..0000000 --- a/x/wasm/keeper/query_plugins.go +++ /dev/null @@ -1,614 +0,0 @@ -package keeper - -import ( - "encoding/json" - "errors" - "fmt" - - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -type QueryHandler struct { - Ctx sdk.Context - Plugins WasmVMQueryHandler - Caller sdk.AccAddress - gasRegister GasRegister -} - -func NewQueryHandler(ctx sdk.Context, vmQueryHandler WasmVMQueryHandler, caller sdk.AccAddress, gasRegister GasRegister) QueryHandler { - return QueryHandler{ - Ctx: ctx, - Plugins: vmQueryHandler, - Caller: caller, - gasRegister: gasRegister, - } -} - -type GRPCQueryRouter interface { - Route(path string) baseapp.GRPCQueryHandler -} - -// -- end baseapp interfaces -- - -var _ wasmvmtypes.Querier = QueryHandler{} - -func (q QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { - // set a limit for a subCtx - sdkGas := q.gasRegister.FromWasmVMGas(gasLimit) - // discard all changes/ events in subCtx by not committing the cached context - subCtx, _ := q.Ctx.WithGasMeter(sdk.NewGasMeter(sdkGas)).CacheContext() - - // make sure we charge the higher level context even on panic - defer func() { - q.Ctx.GasMeter().ConsumeGas(subCtx.GasMeter().GasConsumed(), "contract sub-query") - }() - - res, err := q.Plugins.HandleQuery(subCtx, q.Caller, request) - if err == nil { - // short-circuit, the rest is dealing with handling existing errors - return res, nil - } - - // special mappings to wasmvm system error (which are not redacted) - var wasmvmErr types.WasmVMErrorable - if ok := errors.As(err, &wasmvmErr); ok { - err = wasmvmErr.ToWasmVMError() - } - - // Issue #759 - we don't return error string for worries of non-determinism - return nil, redactError(err) -} - -func (q QueryHandler) GasConsumed() uint64 { - return q.Ctx.GasMeter().GasConsumed() -} - -type CustomQuerier func(ctx sdk.Context, request json.RawMessage) ([]byte, error) - -type QueryPlugins struct { - Bank func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) - Custom CustomQuerier - IBC func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error) - Staking func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) - Stargate func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) - Wasm func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) -} - -type contractMetaDataSource interface { - GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo -} - -type wasmQueryKeeper interface { - contractMetaDataSource - GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo - QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte - QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) - IsPinnedCode(ctx sdk.Context, codeID uint64) bool -} - -func DefaultQueryPlugins( - bank types.BankViewKeeper, - staking types.StakingKeeper, - distKeeper types.DistributionKeeper, - channelKeeper types.ChannelKeeper, - wasm wasmQueryKeeper, -) QueryPlugins { - return QueryPlugins{ - Bank: BankQuerier(bank), - Custom: NoCustomQuerier, - IBC: IBCQuerier(wasm, channelKeeper), - Staking: StakingQuerier(staking, distKeeper), - Stargate: RejectStargateQuerier(), - Wasm: WasmQuerier(wasm), - } -} - -func (e QueryPlugins) Merge(o *QueryPlugins) QueryPlugins { - // only update if this is non-nil and then only set values - if o == nil { - return e - } - if o.Bank != nil { - e.Bank = o.Bank - } - if o.Custom != nil { - e.Custom = o.Custom - } - if o.IBC != nil { - e.IBC = o.IBC - } - if o.Staking != nil { - e.Staking = o.Staking - } - if o.Stargate != nil { - e.Stargate = o.Stargate - } - if o.Wasm != nil { - e.Wasm = o.Wasm - } - return e -} - -// HandleQuery executes the requested query -func (e QueryPlugins) HandleQuery(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { - // do the query - if request.Bank != nil { - return e.Bank(ctx, request.Bank) - } - if request.Custom != nil { - return e.Custom(ctx, request.Custom) - } - if request.IBC != nil { - return e.IBC(ctx, caller, request.IBC) - } - if request.Staking != nil { - return e.Staking(ctx, request.Staking) - } - if request.Stargate != nil { - return e.Stargate(ctx, request.Stargate) - } - if request.Wasm != nil { - return e.Wasm(ctx, request.Wasm) - } - return nil, wasmvmtypes.Unknown{} -} - -func BankQuerier(bankKeeper types.BankViewKeeper) func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) { - return func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) { - if request.AllBalances != nil { - addr, err := sdk.AccAddressFromBech32(request.AllBalances.Address) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, request.AllBalances.Address) - } - coins := bankKeeper.GetAllBalances(ctx, addr) - res := wasmvmtypes.AllBalancesResponse{ - Amount: ConvertSdkCoinsToWasmCoins(coins), - } - return json.Marshal(res) - } - if request.Balance != nil { - addr, err := sdk.AccAddressFromBech32(request.Balance.Address) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, request.Balance.Address) - } - coin := bankKeeper.GetBalance(ctx, addr, request.Balance.Denom) - res := wasmvmtypes.BalanceResponse{ - Amount: wasmvmtypes.Coin{ - Denom: coin.Denom, - Amount: coin.Amount.String(), - }, - } - return json.Marshal(res) - } - if request.Supply != nil { - coin := bankKeeper.GetSupply(ctx, request.Supply.Denom) - res := wasmvmtypes.SupplyResponse{ - Amount: wasmvmtypes.Coin{ - Denom: coin.Denom, - Amount: coin.Amount.String(), - }, - } - return json.Marshal(res) - } - return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown BankQuery variant"} - } -} - -func NoCustomQuerier(sdk.Context, json.RawMessage) ([]byte, error) { - return nil, wasmvmtypes.UnsupportedRequest{Kind: "custom"} -} - -func IBCQuerier(wasm contractMetaDataSource, channelKeeper types.ChannelKeeper) func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error) { - return func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error) { - if request.PortID != nil { - contractInfo := wasm.GetContractInfo(ctx, caller) - res := wasmvmtypes.PortIDResponse{ - PortID: contractInfo.IBCPortID, - } - return json.Marshal(res) - } - if request.ListChannels != nil { - portID := request.ListChannels.PortID - channels := make(wasmvmtypes.IBCChannels, 0) - channelKeeper.IterateChannels(ctx, func(ch channeltypes.IdentifiedChannel) bool { - // it must match the port and be in open state - if (portID == "" || portID == ch.PortId) && ch.State == channeltypes.OPEN { - newChan := wasmvmtypes.IBCChannel{ - Endpoint: wasmvmtypes.IBCEndpoint{ - PortID: ch.PortId, - ChannelID: ch.ChannelId, - }, - CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{ - PortID: ch.Counterparty.PortId, - ChannelID: ch.Counterparty.ChannelId, - }, - Order: ch.Ordering.String(), - Version: ch.Version, - ConnectionID: ch.ConnectionHops[0], - } - channels = append(channels, newChan) - } - return false - }) - res := wasmvmtypes.ListChannelsResponse{ - Channels: channels, - } - return json.Marshal(res) - } - if request.Channel != nil { - channelID := request.Channel.ChannelID - portID := request.Channel.PortID - if portID == "" { - contractInfo := wasm.GetContractInfo(ctx, caller) - portID = contractInfo.IBCPortID - } - got, found := channelKeeper.GetChannel(ctx, portID, channelID) - var channel *wasmvmtypes.IBCChannel - // it must be in open state - if found && got.State == channeltypes.OPEN { - channel = &wasmvmtypes.IBCChannel{ - Endpoint: wasmvmtypes.IBCEndpoint{ - PortID: portID, - ChannelID: channelID, - }, - CounterpartyEndpoint: wasmvmtypes.IBCEndpoint{ - PortID: got.Counterparty.PortId, - ChannelID: got.Counterparty.ChannelId, - }, - Order: got.Ordering.String(), - Version: got.Version, - ConnectionID: got.ConnectionHops[0], - } - } - res := wasmvmtypes.ChannelResponse{ - Channel: channel, - } - return json.Marshal(res) - } - return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown IBCQuery variant"} - } -} - -// RejectStargateQuerier rejects all stargate queries -func RejectStargateQuerier() func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { - return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { - return nil, wasmvmtypes.UnsupportedRequest{Kind: "Stargate queries are disabled"} - } -} - -// AcceptedStargateQueries define accepted Stargate queries as a map with path as key and response type as value. -// For example: -// acceptList["/cosmos.auth.v1beta1.Query/Account"]= &authtypes.QueryAccountResponse{} -type AcceptedStargateQueries map[string]codec.ProtoMarshaler - -// AcceptListStargateQuerier supports a preconfigured set of stargate queries only. -// All arguments must be non nil. -// -// Warning: Chains need to test and maintain their accept list carefully. -// There were critical consensus breaking issues in the past with non-deterministic behaviour in the SDK. -// -// This queries can be set via WithQueryPlugins option in the wasm keeper constructor: -// WithQueryPlugins(&QueryPlugins{Stargate: AcceptListStargateQuerier(acceptList, queryRouter, codec)}) -func AcceptListStargateQuerier(acceptList AcceptedStargateQueries, queryRouter GRPCQueryRouter, codec codec.Codec) func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { - return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { - protoResponse, accepted := acceptList[request.Path] - if !accepted { - return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", request.Path)} - } - - route := queryRouter.Route(request.Path) - if route == nil { - return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("No route to query '%s'", request.Path)} - } - - res, err := route(ctx, abci.RequestQuery{ - Data: request.Data, - Path: request.Path, - }) - if err != nil { - return nil, err - } - - return ConvertProtoToJSONMarshal(codec, protoResponse, res.Value) - } -} - -func StakingQuerier(keeper types.StakingKeeper, distKeeper types.DistributionKeeper) func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) { - return func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) { - if request.BondedDenom != nil { - denom := keeper.BondDenom(ctx) - res := wasmvmtypes.BondedDenomResponse{ - Denom: denom, - } - return json.Marshal(res) - } - if request.AllValidators != nil { - validators := keeper.GetBondedValidatorsByPower(ctx) - // validators := keeper.GetAllValidators(ctx) - wasmVals := make([]wasmvmtypes.Validator, len(validators)) - for i, v := range validators { - wasmVals[i] = wasmvmtypes.Validator{ - Address: v.OperatorAddress, - Commission: v.Commission.Rate.String(), - MaxCommission: v.Commission.MaxRate.String(), - MaxChangeRate: v.Commission.MaxChangeRate.String(), - } - } - res := wasmvmtypes.AllValidatorsResponse{ - Validators: wasmVals, - } - return json.Marshal(res) - } - if request.Validator != nil { - valAddr, err := sdk.ValAddressFromBech32(request.Validator.Address) - if err != nil { - return nil, err - } - v, found := keeper.GetValidator(ctx, valAddr) - res := wasmvmtypes.ValidatorResponse{} - if found { - res.Validator = &wasmvmtypes.Validator{ - Address: v.OperatorAddress, - Commission: v.Commission.Rate.String(), - MaxCommission: v.Commission.MaxRate.String(), - MaxChangeRate: v.Commission.MaxChangeRate.String(), - } - } - return json.Marshal(res) - } - if request.AllDelegations != nil { - delegator, err := sdk.AccAddressFromBech32(request.AllDelegations.Delegator) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, request.AllDelegations.Delegator) - } - sdkDels := keeper.GetAllDelegatorDelegations(ctx, delegator) - delegations, err := sdkToDelegations(ctx, keeper, sdkDels) - if err != nil { - return nil, err - } - res := wasmvmtypes.AllDelegationsResponse{ - Delegations: delegations, - } - return json.Marshal(res) - } - if request.Delegation != nil { - delegator, err := sdk.AccAddressFromBech32(request.Delegation.Delegator) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, request.Delegation.Delegator) - } - validator, err := sdk.ValAddressFromBech32(request.Delegation.Validator) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, request.Delegation.Validator) - } - - var res wasmvmtypes.DelegationResponse - d, found := keeper.GetDelegation(ctx, delegator, validator) - if found { - res.Delegation, err = sdkToFullDelegation(ctx, keeper, distKeeper, d) - if err != nil { - return nil, err - } - } - return json.Marshal(res) - } - return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown Staking variant"} - } -} - -func sdkToDelegations(ctx sdk.Context, keeper types.StakingKeeper, delegations []stakingtypes.Delegation) (wasmvmtypes.Delegations, error) { - result := make([]wasmvmtypes.Delegation, len(delegations)) - bondDenom := keeper.BondDenom(ctx) - - for i, d := range delegations { - delAddr, err := sdk.AccAddressFromBech32(d.DelegatorAddress) - if err != nil { - return nil, errorsmod.Wrap(err, "delegator address") - } - valAddr, err := sdk.ValAddressFromBech32(d.ValidatorAddress) - if err != nil { - return nil, errorsmod.Wrap(err, "validator address") - } - - // shares to amount logic comes from here: - // https://github.com/cosmos/cosmos-sdk/blob/v0.38.3/x/staking/keeper/querier.go#L404 - val, found := keeper.GetValidator(ctx, valAddr) - if !found { - return nil, errorsmod.Wrap(stakingtypes.ErrNoValidatorFound, "can't load validator for delegation") - } - amount := sdk.NewCoin(bondDenom, val.TokensFromShares(d.Shares).TruncateInt()) - - result[i] = wasmvmtypes.Delegation{ - Delegator: delAddr.String(), - Validator: valAddr.String(), - Amount: ConvertSdkCoinToWasmCoin(amount), - } - } - return result, nil -} - -func sdkToFullDelegation(ctx sdk.Context, keeper types.StakingKeeper, distKeeper types.DistributionKeeper, delegation stakingtypes.Delegation) (*wasmvmtypes.FullDelegation, error) { - delAddr, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) - if err != nil { - return nil, errorsmod.Wrap(err, "delegator address") - } - valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) - if err != nil { - return nil, errorsmod.Wrap(err, "validator address") - } - val, found := keeper.GetValidator(ctx, valAddr) - if !found { - return nil, errorsmod.Wrap(stakingtypes.ErrNoValidatorFound, "can't load validator for delegation") - } - bondDenom := keeper.BondDenom(ctx) - amount := sdk.NewCoin(bondDenom, val.TokensFromShares(delegation.Shares).TruncateInt()) - - delegationCoins := ConvertSdkCoinToWasmCoin(amount) - - // FIXME: this is very rough but better than nothing... - // https://github.com/CosmWasm/wasmd/issues/282 - // if this (val, delegate) pair is receiving a redelegation, it cannot redelegate more - // otherwise, it can redelegate the full amount - // (there are cases of partial funds redelegated, but this is a start) - redelegateCoins := wasmvmtypes.NewCoin(0, bondDenom) - if !keeper.HasReceivingRedelegation(ctx, delAddr, valAddr) { - redelegateCoins = delegationCoins - } - - // FIXME: make a cleaner way to do this (modify the sdk) - // we need the info from `distKeeper.calculateDelegationRewards()`, but it is not public - // neither is `queryDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper)` - // so we go through the front door of the querier.... - accRewards, err := getAccumulatedRewards(ctx, distKeeper, delegation) - if err != nil { - return nil, err - } - - return &wasmvmtypes.FullDelegation{ - Delegator: delAddr.String(), - Validator: valAddr.String(), - Amount: delegationCoins, - AccumulatedRewards: accRewards, - CanRedelegate: redelegateCoins, - }, nil -} - -// FIXME: simplify this enormously when -// https://github.com/cosmos/cosmos-sdk/issues/7466 is merged -func getAccumulatedRewards(ctx sdk.Context, distKeeper types.DistributionKeeper, delegation stakingtypes.Delegation) ([]wasmvmtypes.Coin, error) { - // Try to get *delegator* reward info! - params := distributiontypes.QueryDelegationRewardsRequest{ - DelegatorAddress: delegation.DelegatorAddress, - ValidatorAddress: delegation.ValidatorAddress, - } - cache, _ := ctx.CacheContext() - qres, err := distKeeper.DelegationRewards(sdk.WrapSDKContext(cache), ¶ms) - if err != nil { - return nil, err - } - - // now we have it, convert it into wasmvm types - rewards := make([]wasmvmtypes.Coin, len(qres.Rewards)) - for i, r := range qres.Rewards { - rewards[i] = wasmvmtypes.Coin{ - Denom: r.Denom, - Amount: r.Amount.TruncateInt().String(), - } - } - return rewards, nil -} - -func WasmQuerier(k wasmQueryKeeper) func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) { - return func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) { - switch { - case request.Smart != nil: - addr, err := sdk.AccAddressFromBech32(request.Smart.ContractAddr) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, request.Smart.ContractAddr) - } - msg := types.RawContractMessage(request.Smart.Msg) - if err := msg.ValidateBasic(); err != nil { - return nil, errorsmod.Wrap(err, "json msg") - } - return k.QuerySmart(ctx, addr, msg) - case request.Raw != nil: - addr, err := sdk.AccAddressFromBech32(request.Raw.ContractAddr) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, request.Raw.ContractAddr) - } - return k.QueryRaw(ctx, addr, request.Raw.Key), nil - case request.ContractInfo != nil: - contractAddr := request.ContractInfo.ContractAddr - addr, err := sdk.AccAddressFromBech32(contractAddr) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, contractAddr) - } - info := k.GetContractInfo(ctx, addr) - if info == nil { - return nil, types.ErrNoSuchContractFn(contractAddr). - Wrapf("address %s", contractAddr) - } - res := wasmvmtypes.ContractInfoResponse{ - CodeID: info.CodeID, - Creator: info.Creator, - Admin: info.Admin, - Pinned: k.IsPinnedCode(ctx, info.CodeID), - IBCPort: info.IBCPortID, - } - return json.Marshal(res) - case request.CodeInfo != nil: - if request.CodeInfo.CodeID == 0 { - return nil, types.ErrEmpty.Wrap("code id") - } - info := k.GetCodeInfo(ctx, request.CodeInfo.CodeID) - if info == nil { - return nil, types.ErrNoSuchCodeFn(request.CodeInfo.CodeID). - Wrapf("code id %d", request.CodeInfo.CodeID) - } - - res := wasmvmtypes.CodeInfoResponse{ - CodeID: request.CodeInfo.CodeID, - Creator: info.Creator, - Checksum: info.CodeHash, - } - return json.Marshal(res) - } - return nil, wasmvmtypes.UnsupportedRequest{Kind: "unknown WasmQuery variant"} - } -} - -// ConvertSdkCoinsToWasmCoins covert sdk type to wasmvm coins type -func ConvertSdkCoinsToWasmCoins(coins []sdk.Coin) wasmvmtypes.Coins { - converted := make(wasmvmtypes.Coins, len(coins)) - for i, c := range coins { - converted[i] = ConvertSdkCoinToWasmCoin(c) - } - return converted -} - -// ConvertSdkCoinToWasmCoin covert sdk type to wasmvm coin type -func ConvertSdkCoinToWasmCoin(coin sdk.Coin) wasmvmtypes.Coin { - return wasmvmtypes.Coin{ - Denom: coin.Denom, - Amount: coin.Amount.String(), - } -} - -// ConvertProtoToJSONMarshal unmarshals the given bytes into a proto message and then marshals it to json. -// This is done so that clients calling stargate queries do not need to define their own proto unmarshalers, -// being able to use response directly by json marshalling, which is supported in cosmwasm. -func ConvertProtoToJSONMarshal(cdc codec.Codec, protoResponse codec.ProtoMarshaler, bz []byte) ([]byte, error) { - // unmarshal binary into stargate response data structure - err := cdc.Unmarshal(bz, protoResponse) - if err != nil { - return nil, errorsmod.Wrap(err, "to proto") - } - - bz, err = cdc.MarshalJSON(protoResponse) - if err != nil { - return nil, errorsmod.Wrap(err, "to json") - } - - protoResponse.Reset() - return bz, nil -} - -var _ WasmVMQueryHandler = WasmVMQueryHandlerFn(nil) - -// WasmVMQueryHandlerFn is a helper to construct a function based query handler. -type WasmVMQueryHandlerFn func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) - -// HandleQuery delegates call into wrapped WasmVMQueryHandlerFn -func (w WasmVMQueryHandlerFn) HandleQuery(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { - return w(ctx, caller, request) -} diff --git a/x/wasm/keeper/query_plugins_test.go b/x/wasm/keeper/query_plugins_test.go deleted file mode 100644 index f90b926..0000000 --- a/x/wasm/keeper/query_plugins_test.go +++ /dev/null @@ -1,851 +0,0 @@ -package keeper_test - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "testing" - "time" - - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - dbm "github.com/cometbft/cometbft-db" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/query" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/gogoproto/proto" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/app" - "github.com/terpnetwork/terp-core/x/wasm/keeper" - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestIBCQuerier(t *testing.T) { - myExampleChannels := []channeltypes.IdentifiedChannel{ - // this is returned - { - State: channeltypes.OPEN, - Ordering: channeltypes.ORDERED, - Counterparty: channeltypes.Counterparty{ - PortId: "counterPartyPortID", - ChannelId: "counterPartyChannelID", - }, - ConnectionHops: []string{"one"}, - Version: "v1", - PortId: "myPortID", - ChannelId: "myChannelID", - }, - // this is filtered out - { - State: channeltypes.INIT, - Ordering: channeltypes.UNORDERED, - Counterparty: channeltypes.Counterparty{ - PortId: "foobar", - }, - ConnectionHops: []string{"one"}, - Version: "initversion", - PortId: "initPortID", - ChannelId: "initChannelID", - }, - // this is returned - { - State: channeltypes.OPEN, - Ordering: channeltypes.UNORDERED, - Counterparty: channeltypes.Counterparty{ - PortId: "otherCounterPartyPortID", - ChannelId: "otherCounterPartyChannelID", - }, - ConnectionHops: []string{"other", "second"}, - Version: "otherVersion", - PortId: "otherPortID", - ChannelId: "otherChannelID", - }, - // this is filtered out - { - State: channeltypes.CLOSED, - Ordering: channeltypes.ORDERED, - Counterparty: channeltypes.Counterparty{ - PortId: "super", - ChannelId: "duper", - }, - ConnectionHops: []string{"no-more"}, - Version: "closedVersion", - PortId: "closedPortID", - ChannelId: "closedChannelID", - }, - } - specs := map[string]struct { - srcQuery *wasmvmtypes.IBCQuery - wasmKeeper *mockWasmQueryKeeper - channelKeeper *wasmtesting.MockChannelKeeper - expJSONResult string - expErr *errorsmod.Error - }{ - "query port id": { - srcQuery: &wasmvmtypes.IBCQuery{ - PortID: &wasmvmtypes.PortIDQuery{}, - }, - wasmKeeper: &mockWasmQueryKeeper{ - GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { - return &types.ContractInfo{IBCPortID: "myIBCPortID"} - }, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{}, - expJSONResult: `{"port_id":"myIBCPortID"}`, - }, - "query list channels - all": { - srcQuery: &wasmvmtypes.IBCQuery{ - ListChannels: &wasmvmtypes.ListChannelsQuery{}, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{ - IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels), - }, - expJSONResult: `{ - "channels": [ - { - "endpoint": { - "port_id": "myPortID", - "channel_id": "myChannelID" - }, - "counterparty_endpoint": { - "port_id": "counterPartyPortID", - "channel_id": "counterPartyChannelID" - }, - "order": "ORDER_ORDERED", - "version": "v1", - "connection_id": "one" - }, - { - "endpoint": { - "port_id": "otherPortID", - "channel_id": "otherChannelID" - }, - "counterparty_endpoint": { - "port_id": "otherCounterPartyPortID", - "channel_id": "otherCounterPartyChannelID" - }, - "order": "ORDER_UNORDERED", - "version": "otherVersion", - "connection_id": "other" - } - ] -}`, - }, - "query list channels - filtered": { - srcQuery: &wasmvmtypes.IBCQuery{ - ListChannels: &wasmvmtypes.ListChannelsQuery{ - PortID: "otherPortID", - }, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{ - IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels), - }, - expJSONResult: `{ - "channels": [ - { - "endpoint": { - "port_id": "otherPortID", - "channel_id": "otherChannelID" - }, - "counterparty_endpoint": { - "port_id": "otherCounterPartyPortID", - "channel_id": "otherCounterPartyChannelID" - }, - "order": "ORDER_UNORDERED", - "version": "otherVersion", - "connection_id": "other" - } - ] -}`, - }, - "query list channels - filtered empty": { - srcQuery: &wasmvmtypes.IBCQuery{ - ListChannels: &wasmvmtypes.ListChannelsQuery{ - PortID: "none-existing", - }, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{ - IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels), - }, - expJSONResult: `{"channels": []}`, - }, - "query channel": { - srcQuery: &wasmvmtypes.IBCQuery{ - Channel: &wasmvmtypes.ChannelQuery{ - PortID: "myQueryPortID", - ChannelID: "myQueryChannelID", - }, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{ - GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { - return channeltypes.Channel{ - State: channeltypes.OPEN, - Ordering: channeltypes.UNORDERED, - Counterparty: channeltypes.Counterparty{ - PortId: "counterPartyPortID", - ChannelId: "otherCounterPartyChannelID", - }, - ConnectionHops: []string{"one"}, - Version: "version", - }, true - }, - }, - expJSONResult: `{ - "channel": { - "endpoint": { - "port_id": "myQueryPortID", - "channel_id": "myQueryChannelID" - }, - "counterparty_endpoint": { - "port_id": "counterPartyPortID", - "channel_id": "otherCounterPartyChannelID" - }, - "order": "ORDER_UNORDERED", - "version": "version", - "connection_id": "one" - } -}`, - }, - "query channel - without port set": { - srcQuery: &wasmvmtypes.IBCQuery{ - Channel: &wasmvmtypes.ChannelQuery{ - ChannelID: "myQueryChannelID", - }, - }, - wasmKeeper: &mockWasmQueryKeeper{ - GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { - return &types.ContractInfo{IBCPortID: "myLoadedPortID"} - }, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{ - GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { - return channeltypes.Channel{ - State: channeltypes.OPEN, - Ordering: channeltypes.UNORDERED, - Counterparty: channeltypes.Counterparty{ - PortId: "counterPartyPortID", - ChannelId: "otherCounterPartyChannelID", - }, - ConnectionHops: []string{"one"}, - Version: "version", - }, true - }, - }, - expJSONResult: `{ - "channel": { - "endpoint": { - "port_id": "myLoadedPortID", - "channel_id": "myQueryChannelID" - }, - "counterparty_endpoint": { - "port_id": "counterPartyPortID", - "channel_id": "otherCounterPartyChannelID" - }, - "order": "ORDER_UNORDERED", - "version": "version", - "connection_id": "one" - } -}`, - }, - "query channel in init state": { - srcQuery: &wasmvmtypes.IBCQuery{ - Channel: &wasmvmtypes.ChannelQuery{ - PortID: "myQueryPortID", - ChannelID: "myQueryChannelID", - }, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{ - GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { - return channeltypes.Channel{ - State: channeltypes.INIT, - Ordering: channeltypes.UNORDERED, - Counterparty: channeltypes.Counterparty{ - PortId: "foobar", - }, - ConnectionHops: []string{"one"}, - Version: "initversion", - }, true - }, - }, - expJSONResult: "{}", - }, - "query channel in closed state": { - srcQuery: &wasmvmtypes.IBCQuery{ - Channel: &wasmvmtypes.ChannelQuery{ - PortID: "myQueryPortID", - ChannelID: "myQueryChannelID", - }, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{ - GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { - return channeltypes.Channel{ - State: channeltypes.CLOSED, - Ordering: channeltypes.ORDERED, - Counterparty: channeltypes.Counterparty{ - PortId: "super", - ChannelId: "duper", - }, - ConnectionHops: []string{"no-more"}, - Version: "closedVersion", - }, true - }, - }, - expJSONResult: "{}", - }, - "query channel - empty result": { - srcQuery: &wasmvmtypes.IBCQuery{ - Channel: &wasmvmtypes.ChannelQuery{ - PortID: "myQueryPortID", - ChannelID: "myQueryChannelID", - }, - }, - channelKeeper: &wasmtesting.MockChannelKeeper{ - GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { - return channeltypes.Channel{}, false - }, - }, - expJSONResult: "{}", - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - h := keeper.IBCQuerier(spec.wasmKeeper, spec.channelKeeper) - gotResult, gotErr := h(sdk.Context{}, keeper.RandomAccountAddress(t), spec.srcQuery) - require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr) - if spec.expErr != nil { - return - } - assert.JSONEq(t, spec.expJSONResult, string(gotResult), string(gotResult)) - }) - } -} - -func TestBankQuerierBalance(t *testing.T) { - mock := bankKeeperMock{GetBalanceFn: func(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { - return sdk.NewCoin(denom, sdk.NewInt(1)) - }} - - ctx := sdk.Context{} - q := keeper.BankQuerier(mock) - gotBz, gotErr := q(ctx, &wasmvmtypes.BankQuery{ - Balance: &wasmvmtypes.BalanceQuery{ - Address: keeper.RandomBech32AccountAddress(t), - Denom: "ALX", - }, - }) - require.NoError(t, gotErr) - var got wasmvmtypes.BalanceResponse - require.NoError(t, json.Unmarshal(gotBz, &got)) - exp := wasmvmtypes.BalanceResponse{ - Amount: wasmvmtypes.Coin{ - Denom: "ALX", - Amount: "1", - }, - } - assert.Equal(t, exp, got) -} - -func TestContractInfoWasmQuerier(t *testing.T) { - myValidContractAddr := keeper.RandomBech32AccountAddress(t) - myCreatorAddr := keeper.RandomBech32AccountAddress(t) - myAdminAddr := keeper.RandomBech32AccountAddress(t) - var ctx sdk.Context - - specs := map[string]struct { - req *wasmvmtypes.WasmQuery - mock mockWasmQueryKeeper - expRes wasmvmtypes.ContractInfoResponse - expErr bool - }{ - "all good": { - req: &wasmvmtypes.WasmQuery{ - ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: myValidContractAddr}, - }, - mock: mockWasmQueryKeeper{ - GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { - val := types.ContractInfoFixture(func(i *types.ContractInfo) { - i.Admin, i.Creator, i.IBCPortID = myAdminAddr, myCreatorAddr, "myIBCPort" - }) - return &val - }, - IsPinnedCodeFn: func(ctx sdk.Context, codeID uint64) bool { return true }, - }, - expRes: wasmvmtypes.ContractInfoResponse{ - CodeID: 1, - Creator: myCreatorAddr, - Admin: myAdminAddr, - Pinned: true, - IBCPort: "myIBCPort", - }, - }, - "invalid addr": { - req: &wasmvmtypes.WasmQuery{ - ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: "not a valid addr"}, - }, - expErr: true, - }, - "unknown addr": { - req: &wasmvmtypes.WasmQuery{ - ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: myValidContractAddr}, - }, - mock: mockWasmQueryKeeper{GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { - return nil - }}, - expErr: true, - }, - "not pinned": { - req: &wasmvmtypes.WasmQuery{ - ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: myValidContractAddr}, - }, - mock: mockWasmQueryKeeper{ - GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { - val := types.ContractInfoFixture(func(i *types.ContractInfo) { - i.Admin, i.Creator = myAdminAddr, myCreatorAddr - }) - return &val - }, - IsPinnedCodeFn: func(ctx sdk.Context, codeID uint64) bool { return false }, - }, - expRes: wasmvmtypes.ContractInfoResponse{ - CodeID: 1, - Creator: myCreatorAddr, - Admin: myAdminAddr, - Pinned: false, - }, - }, - "without admin": { - req: &wasmvmtypes.WasmQuery{ - ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: myValidContractAddr}, - }, - mock: mockWasmQueryKeeper{ - GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { - val := types.ContractInfoFixture(func(i *types.ContractInfo) { - i.Creator = myCreatorAddr - }) - return &val - }, - IsPinnedCodeFn: func(ctx sdk.Context, codeID uint64) bool { return true }, - }, - expRes: wasmvmtypes.ContractInfoResponse{ - CodeID: 1, - Creator: myCreatorAddr, - Pinned: true, - }, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - q := keeper.WasmQuerier(spec.mock) - gotBz, gotErr := q(ctx, spec.req) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - var gotRes wasmvmtypes.ContractInfoResponse - require.NoError(t, json.Unmarshal(gotBz, &gotRes)) - assert.Equal(t, spec.expRes, gotRes) - }) - } -} - -func TestCodeInfoWasmQuerier(t *testing.T) { - myCreatorAddr := keeper.RandomBech32AccountAddress(t) - var ctx sdk.Context - - myRawChecksum := []byte("myHash78901234567890123456789012") - specs := map[string]struct { - req *wasmvmtypes.WasmQuery - mock mockWasmQueryKeeper - expRes wasmvmtypes.CodeInfoResponse - expErr bool - }{ - "all good": { - req: &wasmvmtypes.WasmQuery{ - CodeInfo: &wasmvmtypes.CodeInfoQuery{CodeID: 1}, - }, - mock: mockWasmQueryKeeper{ - GetCodeInfoFn: func(ctx sdk.Context, codeID uint64) *types.CodeInfo { - return &types.CodeInfo{ - CodeHash: myRawChecksum, - Creator: myCreatorAddr, - InstantiateConfig: types.AccessConfig{ - Permission: types.AccessTypeNobody, - Addresses: []string{myCreatorAddr}, - }, - } - }, - }, - expRes: wasmvmtypes.CodeInfoResponse{ - CodeID: 1, - Creator: myCreatorAddr, - Checksum: myRawChecksum, - }, - }, - "empty code id": { - req: &wasmvmtypes.WasmQuery{ - CodeInfo: &wasmvmtypes.CodeInfoQuery{}, - }, - expErr: true, - }, - "unknown code id": { - req: &wasmvmtypes.WasmQuery{ - CodeInfo: &wasmvmtypes.CodeInfoQuery{CodeID: 1}, - }, - mock: mockWasmQueryKeeper{ - GetCodeInfoFn: func(ctx sdk.Context, codeID uint64) *types.CodeInfo { - return nil - }, - }, - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - q := keeper.WasmQuerier(spec.mock) - gotBz, gotErr := q(ctx, spec.req) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - var gotRes wasmvmtypes.CodeInfoResponse - require.NoError(t, json.Unmarshal(gotBz, &gotRes), string(gotBz)) - assert.Equal(t, spec.expRes, gotRes) - }) - } -} - -func TestQueryErrors(t *testing.T) { - specs := map[string]struct { - src error - expErr error - }{ - "no error": {}, - "no such contract": { - src: types.ErrNoSuchContractFn("contract-addr"), - expErr: wasmvmtypes.NoSuchContract{Addr: "contract-addr"}, - }, - "no such contract - wrapped": { - src: errorsmod.Wrap(types.ErrNoSuchContractFn("contract-addr"), "my additional data"), - expErr: wasmvmtypes.NoSuchContract{Addr: "contract-addr"}, - }, - "no such code": { - src: types.ErrNoSuchCodeFn(123), - expErr: wasmvmtypes.NoSuchCode{CodeID: 123}, - }, - "no such code - wrapped": { - src: errorsmod.Wrap(types.ErrNoSuchCodeFn(123), "my additional data"), - expErr: wasmvmtypes.NoSuchCode{CodeID: 123}, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - mock := keeper.WasmVMQueryHandlerFn(func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { - return nil, spec.src - }) - ctx := sdk.Context{}.WithGasMeter(sdk.NewInfiniteGasMeter()).WithMultiStore(store.NewCommitMultiStore(dbm.NewMemDB())) - q := keeper.NewQueryHandler(ctx, mock, sdk.AccAddress{}, keeper.NewDefaultWasmGasRegister()) - _, gotErr := q.Query(wasmvmtypes.QueryRequest{}, 1) - assert.Equal(t, spec.expErr, gotErr) - }) - } -} - -func TestAcceptListStargateQuerier(t *testing.T) { - wasmApp := app.SetupWithEmptyStore(t) - ctx := wasmApp.NewUncachedContext(false, tmproto.Header{ChainID: "foo", Height: 1, Time: time.Now()}) - err := wasmApp.StakingKeeper.SetParams(ctx, stakingtypes.DefaultParams()) - require.NoError(t, err) - - addrs := app.AddTestAddrsIncremental(wasmApp, ctx, 2, sdk.NewInt(1_000_000)) - accepted := keeper.AcceptedStargateQueries{ - "/cosmos.auth.v1beta1.Query/Account": &authtypes.QueryAccountResponse{}, - "/no/route/to/this": &authtypes.QueryAccountResponse{}, - } - - marshal := func(pb proto.Message) []byte { - b, err := proto.Marshal(pb) - require.NoError(t, err) - return b - } - - specs := map[string]struct { - req *wasmvmtypes.StargateQuery - expErr bool - expResp string - }{ - "in accept list - success result": { - req: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.auth.v1beta1.Query/Account", - Data: marshal(&authtypes.QueryAccountRequest{Address: addrs[0].String()}), - }, - expResp: fmt.Sprintf(`{"account":{"@type":"/cosmos.auth.v1beta1.BaseAccount","address":%q,"pub_key":null,"account_number":"1","sequence":"0"}}`, addrs[0].String()), - }, - "in accept list - error result": { - req: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.auth.v1beta1.Query/Account", - Data: marshal(&authtypes.QueryAccountRequest{Address: sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).String()}), - }, - expErr: true, - }, - "not in accept list": { - req: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.bank.v1beta1.Query/AllBalances", - Data: marshal(&banktypes.QueryAllBalancesRequest{Address: addrs[0].String()}), - }, - expErr: true, - }, - "unknown route": { - req: &wasmvmtypes.StargateQuery{ - Path: "/no/route/to/this", - Data: marshal(&banktypes.QueryAllBalancesRequest{Address: addrs[0].String()}), - }, - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - q := keeper.AcceptListStargateQuerier(accepted, wasmApp.GRPCQueryRouter(), wasmApp.AppCodec()) - gotBz, gotErr := q(ctx, spec.req) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.JSONEq(t, spec.expResp, string(gotBz), string(gotBz)) - }) - } -} - -type mockWasmQueryKeeper struct { - GetContractInfoFn func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo - QueryRawFn func(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte - QuerySmartFn func(ctx sdk.Context, contractAddr sdk.AccAddress, req types.RawContractMessage) ([]byte, error) - IsPinnedCodeFn func(ctx sdk.Context, codeID uint64) bool - GetCodeInfoFn func(ctx sdk.Context, codeID uint64) *types.CodeInfo -} - -func (m mockWasmQueryKeeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { - if m.GetContractInfoFn == nil { - panic("not expected to be called") - } - return m.GetContractInfoFn(ctx, contractAddress) -} - -func (m mockWasmQueryKeeper) QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte { - if m.QueryRawFn == nil { - panic("not expected to be called") - } - return m.QueryRawFn(ctx, contractAddress, key) -} - -func (m mockWasmQueryKeeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) { - if m.QuerySmartFn == nil { - panic("not expected to be called") - } - return m.QuerySmartFn(ctx, contractAddr, req) -} - -func (m mockWasmQueryKeeper) IsPinnedCode(ctx sdk.Context, codeID uint64) bool { - if m.IsPinnedCodeFn == nil { - panic("not expected to be called") - } - return m.IsPinnedCodeFn(ctx, codeID) -} - -func (m mockWasmQueryKeeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo { - if m.GetCodeInfoFn == nil { - panic("not expected to be called") - } - return m.GetCodeInfoFn(ctx, codeID) -} - -type bankKeeperMock struct { - GetSupplyFn func(ctx sdk.Context, denom string) sdk.Coin - GetBalanceFn func(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin - GetAllBalancesFn func(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins -} - -func (m bankKeeperMock) GetSupply(ctx sdk.Context, denom string) sdk.Coin { - if m.GetSupplyFn == nil { - panic("not expected to be called") - } - return m.GetSupplyFn(ctx, denom) -} - -func (m bankKeeperMock) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { - if m.GetBalanceFn == nil { - panic("not expected to be called") - } - return m.GetBalanceFn(ctx, addr, denom) -} - -func (m bankKeeperMock) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { - if m.GetAllBalancesFn == nil { - panic("not expected to be called") - } - return m.GetAllBalancesFn(ctx, addr) -} - -func TestConvertProtoToJSONMarshal(t *testing.T) { - testCases := []struct { - name string - queryPath string - protoResponseStruct codec.ProtoMarshaler - originalResponse string - expectedProtoResponse codec.ProtoMarshaler - expectedError bool - }{ - { - name: "successful conversion from proto response to json marshalled response", - queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", - originalResponse: "0a090a036261721202333012050a03666f6f", - protoResponseStruct: &banktypes.QueryAllBalancesResponse{}, - expectedProtoResponse: &banktypes.QueryAllBalancesResponse{ - Balances: sdk.NewCoins(sdk.NewCoin("bar", sdk.NewInt(30))), - Pagination: &query.PageResponse{ - NextKey: []byte("foo"), - }, - }, - }, - { - name: "invalid proto response struct", - queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", - originalResponse: "0a090a036261721202333012050a03666f6f", - protoResponseStruct: &authtypes.QueryAccountResponse{}, - expectedError: true, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) { - originalVersionBz, err := hex.DecodeString(tc.originalResponse) - require.NoError(t, err) - appCodec := app.MakeEncodingConfig().Marshaler - - jsonMarshalledResponse, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.protoResponseStruct, originalVersionBz) - if tc.expectedError { - require.Error(t, err) - return - } - require.NoError(t, err) - - // check response by json marshalling proto response into json response manually - jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProtoResponse) - require.NoError(t, err) - require.JSONEq(t, string(jsonMarshalledResponse), string(jsonMarshalExpectedResponse)) - }) - } -} - -func TestResetProtoMarshalerAfterJsonMarshal(t *testing.T) { - appCodec := app.MakeEncodingConfig().Marshaler - - protoMarshaler := &banktypes.QueryAllBalancesResponse{} - expected := appCodec.MustMarshalJSON(&banktypes.QueryAllBalancesResponse{ - Balances: sdk.NewCoins(sdk.NewCoin("bar", sdk.NewInt(30))), - Pagination: &query.PageResponse{ - NextKey: []byte("foo"), - }, - }) - - bz, err := hex.DecodeString("0a090a036261721202333012050a03666f6f") - require.NoError(t, err) - - // first marshal - response, err := keeper.ConvertProtoToJSONMarshal(appCodec, protoMarshaler, bz) - require.NoError(t, err) - require.Equal(t, expected, response) - - // second marshal - response, err = keeper.ConvertProtoToJSONMarshal(appCodec, protoMarshaler, bz) - require.NoError(t, err) - require.Equal(t, expected, response) -} - -// TestDeterministicJsonMarshal tests that we get deterministic JSON marshalled response upon -// proto struct update in the state machine. -func TestDeterministicJsonMarshal(t *testing.T) { - testCases := []struct { - name string - originalResponse string - updatedResponse string - queryPath string - responseProtoStruct codec.ProtoMarshaler - expectedProto func() codec.ProtoMarshaler - }{ - /** - * - * Origin Response - * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331346c3268686a6e676c3939367772703935673867646a6871653038326375367a7732706c686b - * - * Updated Response - * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271122d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271 - // Origin proto - message QueryAccountResponse { - // account defines the account of the corresponding address. - google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; - } - // Updated proto - message QueryAccountResponse { - // account defines the account of the corresponding address. - google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; - // address is the address to query for. - string address = 2; - } - */ - { - "Query Account", - "0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679", - "0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679122d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679", - "/cosmos.auth.v1beta1.Query/Account", - &authtypes.QueryAccountResponse{}, - func() codec.ProtoMarshaler { - account := authtypes.BaseAccount{ - Address: "cosmos1f8uxultn8sqzhznrsz3q77xwaquhgrsg6jyvfy", - } - accountResponse, err := codectypes.NewAnyWithValue(&account) - require.NoError(t, err) - return &authtypes.QueryAccountResponse{ - Account: accountResponse, - } - }, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) { - appCodec := app.MakeEncodingConfig().Marshaler - - originVersionBz, err := hex.DecodeString(tc.originalResponse) - require.NoError(t, err) - jsonMarshalledOriginalBz, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.responseProtoStruct, originVersionBz) - require.NoError(t, err) - - newVersionBz, err := hex.DecodeString(tc.updatedResponse) - require.NoError(t, err) - jsonMarshalledUpdatedBz, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.responseProtoStruct, newVersionBz) - require.NoError(t, err) - - // json marshalled bytes should be the same since we use the same proto struct for unmarshalling - require.Equal(t, jsonMarshalledOriginalBz, jsonMarshalledUpdatedBz) - - // raw build also make same result - jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProto()) - require.NoError(t, err) - require.Equal(t, jsonMarshalledUpdatedBz, jsonMarshalExpectedResponse) - }) - } -} diff --git a/x/wasm/keeper/recurse_test.go b/x/wasm/keeper/recurse_test.go deleted file mode 100644 index d520294..0000000 --- a/x/wasm/keeper/recurse_test.go +++ /dev/null @@ -1,306 +0,0 @@ -package keeper - -import ( - "encoding/json" - "testing" - - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "github.com/terpnetwork/terp-core/x/wasm/types" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type Recurse struct { - Depth uint32 `json:"depth"` - Work uint32 `json:"work"` -} - -type recurseWrapper struct { - Recurse Recurse `json:"recurse"` -} - -func buildRecurseQuery(t *testing.T, msg Recurse) []byte { - wrapper := recurseWrapper{Recurse: msg} - bz, err := json.Marshal(wrapper) - require.NoError(t, err) - return bz -} - -type recurseResponse struct { - Hashed []byte `json:"hashed"` -} - -// number os wasm queries called from a contract -var totalWasmQueryCounter int - -func initRecurseContract(t *testing.T) (contract sdk.AccAddress, ctx sdk.Context, keeper *Keeper) { - countingQuerierDec := func(realWasmQuerier WasmVMQueryHandler) WasmVMQueryHandler { - return WasmVMQueryHandlerFn(func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { - totalWasmQueryCounter++ - return realWasmQuerier.HandleQuery(ctx, caller, request) - }) - } - ctx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithQueryHandlerDecorator(countingQuerierDec)) - keeper = keepers.WasmKeeper - exampleContract := InstantiateHackatomExampleContract(t, ctx, keepers) - return exampleContract.Contract, ctx, keeper -} - -func TestGasCostOnQuery(t *testing.T) { - const ( - GasNoWork uint64 = 63_950 - // Note: about 100 SDK gas (10k wasmer gas) for each round of sha256 - GasWork50 uint64 = 64_218 // this is a little shy of 50k gas - to keep an eye on the limit - - GasReturnUnhashed uint64 = 32 - GasReturnHashed uint64 = 27 - ) - - cases := map[string]struct { - gasLimit uint64 - msg Recurse - expectedGas uint64 - }{ - "no recursion, no work": { - gasLimit: 400_000, - msg: Recurse{}, - expectedGas: GasNoWork, - }, - "no recursion, some work": { - gasLimit: 400_000, - msg: Recurse{ - Work: 50, // 50 rounds of sha256 inside the contract - }, - expectedGas: GasWork50, - }, - "recursion 1, no work": { - gasLimit: 400_000, - msg: Recurse{ - Depth: 1, - }, - expectedGas: 2*GasNoWork + GasReturnUnhashed, - }, - "recursion 1, some work": { - gasLimit: 400_000, - msg: Recurse{ - Depth: 1, - Work: 50, - }, - expectedGas: 2*GasWork50 + GasReturnHashed, - }, - "recursion 4, some work": { - gasLimit: 400_000, - msg: Recurse{ - Depth: 4, - Work: 50, - }, - expectedGas: 5*GasWork50 + 4*GasReturnHashed, - }, - } - - contractAddr, ctx, keeper := initRecurseContract(t) - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - // external limit has no effect (we get a panic if this is enforced) - keeper.queryGasLimit = 1000 - - // make sure we set a limit before calling - ctx = ctx.WithGasMeter(sdk.NewGasMeter(tc.gasLimit)) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - - // do the query - recurse := tc.msg - msg := buildRecurseQuery(t, recurse) - data, err := keeper.QuerySmart(ctx, contractAddr, msg) - require.NoError(t, err) - - // check the gas is what we expected - if types.EnableGasVerification { - assert.Equal(t, tc.expectedGas, ctx.GasMeter().GasConsumed()) - } - // assert result is 32 byte sha256 hash (if hashed), or contractAddr if not - var resp recurseResponse - err = json.Unmarshal(data, &resp) - require.NoError(t, err) - if recurse.Work == 0 { - assert.Equal(t, len(contractAddr.String()), len(resp.Hashed)) - } else { - assert.Equal(t, 32, len(resp.Hashed)) - } - }) - } -} - -func TestGasOnExternalQuery(t *testing.T) { - const ( - GasWork50 uint64 = DefaultInstanceCost + 8_464 - ) - - cases := map[string]struct { - gasLimit uint64 - msg Recurse - expOutOfGas bool - }{ - "no recursion, plenty gas": { - gasLimit: 400_000, - msg: Recurse{ - Work: 50, // 50 rounds of sha256 inside the contract - }, - }, - "recursion 4, plenty gas": { - // this uses 244708 gas - gasLimit: 400_000, - msg: Recurse{ - Depth: 4, - Work: 50, - }, - }, - "no recursion, external gas limit": { - gasLimit: 5000, // this is not enough - msg: Recurse{ - Work: 50, - }, - expOutOfGas: true, - }, - "recursion 4, external gas limit": { - // this uses 244708 gas but give less - gasLimit: 4 * GasWork50, - msg: Recurse{ - Depth: 4, - Work: 50, - }, - expOutOfGas: true, - }, - } - - contractAddr, ctx, keeper := initRecurseContract(t) - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - recurse := tc.msg - msg := buildRecurseQuery(t, recurse) - - querier := NewGrpcQuerier(keeper.cdc, keeper.storeKey, keeper, tc.gasLimit) - req := &types.QuerySmartContractStateRequest{Address: contractAddr.String(), QueryData: msg} - _, gotErr := querier.SmartContractState(sdk.WrapSDKContext(ctx), req) - if tc.expOutOfGas { - require.Error(t, gotErr, sdkerrors.ErrOutOfGas) - return - } - require.NoError(t, gotErr) - }) - } -} - -func TestLimitRecursiveQueryGas(t *testing.T) { - // The point of this test from https://github.com/CosmWasm/cosmwasm/issues/456 - // Basically, if I burn 90% of gas in CPU loop, then query out (to my self) - // the sub-query will have all the original gas (minus the 40k instance charge) - // and can burn 90% and call a sub-contract again... - // This attack would allow us to use far more than the provided gas before - // eventually hitting an OutOfGas panic. - - const ( - // Note: about 100 SDK gas (10k wasmer gas) for each round of sha256 - GasWork2k uint64 = 77_206 // = NewContractInstanceCosts + x // we have 6x gas used in cpu than in the instance - // This is overhead for calling into a sub-contract - GasReturnHashed uint64 = 27 - ) - - cases := map[string]struct { - gasLimit uint64 - msg Recurse - expectQueriesFromContract int - expectedGas uint64 - expectOutOfGas bool - expectError string - }{ - "no recursion, lots of work": { - gasLimit: 4_000_000, - msg: Recurse{ - Depth: 0, - Work: 2000, - }, - expectQueriesFromContract: 0, - expectedGas: GasWork2k, - }, - "recursion 5, lots of work": { - gasLimit: 4_000_000, - msg: Recurse{ - Depth: 5, - Work: 2000, - }, - expectQueriesFromContract: 5, - // FIXME: why -1 ... confused a bit by calculations, seems like rounding issues - expectedGas: GasWork2k + 5*(GasWork2k+GasReturnHashed), - }, - // this is where we expect an error... - // it has enough gas to run 5 times and die on the 6th (5th time dispatching to sub-contract) - // however, if we don't charge the cpu gas before sub-dispatching, we can recurse over 20 times - "deep recursion, should die on 5th level": { - gasLimit: 400_000, - msg: Recurse{ - Depth: 50, - Work: 2000, - }, - expectQueriesFromContract: 5, - expectOutOfGas: true, - }, - "very deep recursion, hits recursion limit": { - gasLimit: 10_000_000, - msg: Recurse{ - Depth: 100, - Work: 2000, - }, - expectQueriesFromContract: 10, - expectOutOfGas: false, - expectError: "query wasm contract failed", // Error we get from the contract instance doing the failing query, not wasmd - expectedGas: 10*(GasWork2k+GasReturnHashed) - 247, - }, - } - - contractAddr, ctx, keeper := initRecurseContract(t) - - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - // reset the counter before test - totalWasmQueryCounter = 0 - - // make sure we set a limit before calling - ctx = ctx.WithGasMeter(sdk.NewGasMeter(tc.gasLimit)) - require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed()) - - // prepare the query - recurse := tc.msg - msg := buildRecurseQuery(t, recurse) - - // if we expect out of gas, make sure this panics - if tc.expectOutOfGas { - require.Panics(t, func() { - _, err := keeper.QuerySmart(ctx, contractAddr, msg) - t.Logf("Got error not panic: %#v", err) - }) - assert.Equal(t, tc.expectQueriesFromContract, totalWasmQueryCounter) - return - } - - // otherwise, we expect a successful call - _, err := keeper.QuerySmart(ctx, contractAddr, msg) - if tc.expectError != "" { - require.ErrorContains(t, err, tc.expectError) - } else { - require.NoError(t, err) - } - if types.EnableGasVerification { - assert.Equal(t, tc.expectedGas, ctx.GasMeter().GasConsumed()) - } - assert.Equal(t, tc.expectQueriesFromContract, totalWasmQueryCounter) - }) - } -} diff --git a/x/wasm/keeper/reflect_test.go b/x/wasm/keeper/reflect_test.go deleted file mode 100644 index 608d705..0000000 --- a/x/wasm/keeper/reflect_test.go +++ /dev/null @@ -1,683 +0,0 @@ -package keeper - -import ( - "encoding/json" - "os" - "strings" - "testing" - - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/cosmos/gogoproto/proto" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/testdata" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// ReflectInitMsg is {} - -func buildReflectQuery(t *testing.T, query *testdata.ReflectQueryMsg) []byte { - bz, err := json.Marshal(query) - require.NoError(t, err) - return bz -} - -func mustParse(t *testing.T, data []byte, res interface{}) { - err := json.Unmarshal(data, res) - require.NoError(t, err) -} - -const ReflectFeatures = "staking,mask,stargate,cosmwasm_1_1" - -func TestReflectContractSend(t *testing.T) { - cdc := MakeEncodingConfig(t).Marshaler - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc))) - accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - _, bob := keyPubAddr() - - // upload reflect code - reflectID, _, err := keeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), reflectID) - - // upload hackatom escrow code - escrowCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - escrowID, _, err := keeper.Create(ctx, creator, escrowCode, nil) - require.NoError(t, err) - require.Equal(t, uint64(2), escrowID) - - // creator instantiates a contract and gives it tokens - reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - reflectAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) - require.NoError(t, err) - require.NotEmpty(t, reflectAddr) - - // now we set contract as verifier of an escrow - initMsg := HackatomExampleInitMsg{ - Verifier: reflectAddr, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - escrowStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 25000)) - escrowAddr, _, err := keeper.Instantiate(ctx, escrowID, creator, nil, initMsgBz, "escrow contract 2", escrowStart) - require.NoError(t, err) - require.NotEmpty(t, escrowAddr) - - // let's make sure all balances make sense - checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // 100k - 40k - 25k - checkAccount(t, ctx, accKeeper, bankKeeper, reflectAddr, reflectStart) - checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, escrowStart) - checkAccount(t, ctx, accKeeper, bankKeeper, bob, nil) - - // now for the trick.... we reflect a message through the reflect to call the escrow - // we also send an additional 14k tokens there. - // this should reduce the reflect balance by 14k (to 26k) - // this 14k is added to the escrow, then the entire balance is sent to bob (total: 39k) - approveMsg := []byte(`{"release":{}}`) - msgs := []wasmvmtypes.CosmosMsg{{ - Wasm: &wasmvmtypes.WasmMsg{ - Execute: &wasmvmtypes.ExecuteMsg{ - ContractAddr: escrowAddr.String(), - Msg: approveMsg, - Funds: []wasmvmtypes.Coin{{ - Denom: "denom", - Amount: "14000", - }}, - }, - }, - }} - reflectSend := testdata.ReflectHandleMsg{ - Reflect: &testdata.ReflectPayload{ - Msgs: msgs, - }, - } - reflectSendBz, err := json.Marshal(reflectSend) - require.NoError(t, err) - _, err = keeper.Execute(ctx, reflectAddr, creator, reflectSendBz, nil) - require.NoError(t, err) - - // did this work??? - checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // same as before - checkAccount(t, ctx, accKeeper, bankKeeper, reflectAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 26000))) // 40k - 14k (from send) - checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, sdk.Coins{}) // emptied reserved - checkAccount(t, ctx, accKeeper, bankKeeper, bob, sdk.NewCoins(sdk.NewInt64Coin("denom", 39000))) // all escrow of 25k + 14k -} - -func TestReflectCustomMsg(t *testing.T) { - cdc := MakeEncodingConfig(t).Marshaler - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) - accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - bob := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - _, fred := keyPubAddr() - - // upload code - codeID, _, err := keeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), codeID) - - // creator instantiates a contract and gives it tokens - contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) - require.NoError(t, err) - require.NotEmpty(t, contractAddr) - - // set owner to bob - transfer := testdata.ReflectHandleMsg{ - ChangeOwner: &testdata.OwnerPayload{ - Owner: bob, - }, - } - transferBz, err := json.Marshal(transfer) - require.NoError(t, err) - _, err = keeper.Execute(ctx, contractAddr, creator, transferBz, nil) - require.NoError(t, err) - - // check some account values - checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, contractStart) - checkAccount(t, ctx, accKeeper, bankKeeper, bob, deposit) - checkAccount(t, ctx, accKeeper, bankKeeper, fred, nil) - - // bob can send contract's tokens to fred (using SendMsg) - msgs := []wasmvmtypes.CosmosMsg{{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: fred.String(), - Amount: []wasmvmtypes.Coin{{ - Denom: "denom", - Amount: "15000", - }}, - }, - }, - }} - reflectSend := testdata.ReflectHandleMsg{ - Reflect: &testdata.ReflectPayload{ - Msgs: msgs, - }, - } - reflectSendBz, err := json.Marshal(reflectSend) - require.NoError(t, err) - _, err = keeper.Execute(ctx, contractAddr, bob, reflectSendBz, nil) - require.NoError(t, err) - - // fred got coins - checkAccount(t, ctx, accKeeper, bankKeeper, fred, sdk.NewCoins(sdk.NewInt64Coin("denom", 15000))) - // contract lost them - checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 25000))) - checkAccount(t, ctx, accKeeper, bankKeeper, bob, deposit) - - // construct an opaque message - var sdkSendMsg sdk.Msg = &banktypes.MsgSend{ - FromAddress: contractAddr.String(), - ToAddress: fred.String(), - Amount: sdk.NewCoins(sdk.NewInt64Coin("denom", 23000)), - } - opaque, err := toReflectRawMsg(cdc, sdkSendMsg) - require.NoError(t, err) - reflectOpaque := testdata.ReflectHandleMsg{ - Reflect: &testdata.ReflectPayload{ - Msgs: []wasmvmtypes.CosmosMsg{opaque}, - }, - } - reflectOpaqueBz, err := json.Marshal(reflectOpaque) - require.NoError(t, err) - - _, err = keeper.Execute(ctx, contractAddr, bob, reflectOpaqueBz, nil) - require.NoError(t, err) - - // fred got more coins - checkAccount(t, ctx, accKeeper, bankKeeper, fred, sdk.NewCoins(sdk.NewInt64Coin("denom", 38000))) - // contract lost them - checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 2000))) - checkAccount(t, ctx, accKeeper, bankKeeper, bob, deposit) -} - -func TestMaskReflectCustomQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Marshaler - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) - keeper := keepers.WasmKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - // upload code - codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), codeID) - - // creator instantiates a contract and gives it tokens - contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) - require.NoError(t, err) - require.NotEmpty(t, contractAddr) - - // let's perform a normal query of state - ownerQuery := testdata.ReflectQueryMsg{ - Owner: &struct{}{}, - } - ownerQueryBz, err := json.Marshal(ownerQuery) - require.NoError(t, err) - ownerRes, err := keeper.QuerySmart(ctx, contractAddr, ownerQueryBz) - require.NoError(t, err) - var res testdata.OwnerResponse - err = json.Unmarshal(ownerRes, &res) - require.NoError(t, err) - assert.Equal(t, res.Owner, creator.String()) - - // and now making use of the custom querier callbacks - customQuery := testdata.ReflectQueryMsg{ - Capitalized: &testdata.Text{ - Text: "all Caps noW", - }, - } - customQueryBz, err := json.Marshal(customQuery) - require.NoError(t, err) - custom, err := keeper.QuerySmart(ctx, contractAddr, customQueryBz) - require.NoError(t, err) - var resp capitalizedResponse - err = json.Unmarshal(custom, &resp) - require.NoError(t, err) - assert.Equal(t, resp.Text, "ALL CAPS NOW") -} - -func TestReflectStargateQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Marshaler - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) - keeper := keepers.WasmKeeper - - funds := sdk.NewCoins(sdk.NewInt64Coin("denom", 320000)) - contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - expectedBalance := funds.Sub(contractStart...) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, funds...) - - // upload code - codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), codeID) - - // creator instantiates a contract and gives it tokens - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) - require.NoError(t, err) - require.NotEmpty(t, contractAddr) - - // first, normal query for the bank balance (to make sure our query is proper) - bankQuery := wasmvmtypes.QueryRequest{ - Bank: &wasmvmtypes.BankQuery{ - AllBalances: &wasmvmtypes.AllBalancesQuery{ - Address: creator.String(), - }, - }, - } - simpleQueryBz, err := json.Marshal(testdata.ReflectQueryMsg{ - Chain: &testdata.ChainQuery{Request: &bankQuery}, - }) - require.NoError(t, err) - simpleRes, err := keeper.QuerySmart(ctx, contractAddr, simpleQueryBz) - require.NoError(t, err) - var simpleChain testdata.ChainResponse - mustParse(t, simpleRes, &simpleChain) - var simpleBalance wasmvmtypes.AllBalancesResponse - mustParse(t, simpleChain.Data, &simpleBalance) - require.Equal(t, len(expectedBalance), len(simpleBalance.Amount)) - assert.Equal(t, simpleBalance.Amount[0].Amount, expectedBalance[0].Amount.String()) - assert.Equal(t, simpleBalance.Amount[0].Denom, expectedBalance[0].Denom) -} - -func TestReflectTotalSupplyQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Marshaler - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) - keeper := keepers.WasmKeeper - // upload code - codeID := StoreReflectContract(t, ctx, keepers).CodeID - // creator instantiates a contract and gives it tokens - creator := RandomAccountAddress(t) - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "testing", nil) - require.NoError(t, err) - - currentStakeSupply := keepers.BankKeeper.GetSupply(ctx, "stake") - require.NotEmpty(t, currentStakeSupply.Amount) // ensure we have real data - specs := map[string]struct { - denom string - expAmount wasmvmtypes.Coin - }{ - "known denom": { - denom: "stake", - expAmount: ConvertSdkCoinToWasmCoin(currentStakeSupply), - }, - "unknown denom": { - denom: "unknown", - expAmount: wasmvmtypes.Coin{Denom: "unknown", Amount: "0"}, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // when - queryBz := mustMarshal(t, testdata.ReflectQueryMsg{ - Chain: &testdata.ChainQuery{ - Request: &wasmvmtypes.QueryRequest{ - Bank: &wasmvmtypes.BankQuery{ - Supply: &wasmvmtypes.SupplyQuery{Denom: spec.denom}, - }, - }, - }, - }) - simpleRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz) - - // then - require.NoError(t, err) - var rsp testdata.ChainResponse - mustParse(t, simpleRes, &rsp) - var supplyRsp wasmvmtypes.SupplyResponse - mustParse(t, rsp.Data, &supplyRsp) - assert.Equal(t, spec.expAmount, supplyRsp.Amount, spec.expAmount) - }) - } -} - -func TestReflectInvalidStargateQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Marshaler - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) - keeper := keepers.WasmKeeper - - funds := sdk.NewCoins(sdk.NewInt64Coin("denom", 320000)) - contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, funds...) - - // upload code - codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), codeID) - - // creator instantiates a contract and gives it tokens - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) - require.NoError(t, err) - require.NotEmpty(t, contractAddr) - - // now, try to build a protobuf query - protoQuery := banktypes.QueryAllBalancesRequest{ - Address: creator.String(), - } - protoQueryBin, err := proto.Marshal(&protoQuery) - require.NoError(t, err) - - protoRequest := wasmvmtypes.QueryRequest{ - Stargate: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.bank.v1beta1.Query/AllBalances", - Data: protoQueryBin, - }, - } - protoQueryBz, err := json.Marshal(testdata.ReflectQueryMsg{ - Chain: &testdata.ChainQuery{Request: &protoRequest}, - }) - require.NoError(t, err) - - // make a query on the chain, should not be whitelisted - _, err = keeper.QuerySmart(ctx, contractAddr, protoQueryBz) - require.Error(t, err) - require.Contains(t, err.Error(), "Unsupported query") - - // now, try to build a protobuf query - protoRequest = wasmvmtypes.QueryRequest{ - Stargate: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.tx.v1beta1.Service/GetTx", - Data: []byte{}, - }, - } - protoQueryBz, err = json.Marshal(testdata.ReflectQueryMsg{ - Chain: &testdata.ChainQuery{Request: &protoRequest}, - }) - require.NoError(t, err) - - // make a query on the chain, should be blacklisted - _, err = keeper.QuerySmart(ctx, contractAddr, protoQueryBz) - require.Error(t, err) - require.Contains(t, err.Error(), "Unsupported query") - - // and another one - protoRequest = wasmvmtypes.QueryRequest{ - Stargate: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.base.tendermint.v1beta1.Service/GetNodeInfo", - Data: []byte{}, - }, - } - protoQueryBz, err = json.Marshal(testdata.ReflectQueryMsg{ - Chain: &testdata.ChainQuery{Request: &protoRequest}, - }) - require.NoError(t, err) - - // make a query on the chain, should be blacklisted - _, err = keeper.QuerySmart(ctx, contractAddr, protoQueryBz) - require.Error(t, err) - require.Contains(t, err.Error(), "Unsupported query") -} - -type reflectState struct { - Owner string `json:"owner"` -} - -func TestMaskReflectWasmQueries(t *testing.T) { - cdc := MakeEncodingConfig(t).Marshaler - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) - keeper := keepers.WasmKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - // upload reflect code - reflectID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), reflectID) - - // creator instantiates a contract and gives it tokens - reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - reflectAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) - require.NoError(t, err) - require.NotEmpty(t, reflectAddr) - - // for control, let's make some queries directly on the reflect - ownerQuery := buildReflectQuery(t, &testdata.ReflectQueryMsg{Owner: &struct{}{}}) - res, err := keeper.QuerySmart(ctx, reflectAddr, ownerQuery) - require.NoError(t, err) - var ownerRes testdata.OwnerResponse - mustParse(t, res, &ownerRes) - require.Equal(t, ownerRes.Owner, creator.String()) - - // and a raw query: cosmwasm_storage::Singleton uses 2 byte big-endian length-prefixed to store data - configKey := append([]byte{0, 6}, []byte("config")...) - raw := keeper.QueryRaw(ctx, reflectAddr, configKey) - var stateRes reflectState - mustParse(t, raw, &stateRes) - require.Equal(t, stateRes.Owner, creator.String()) - - // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result - reflectOwnerQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ - Smart: &wasmvmtypes.SmartQuery{ - ContractAddr: reflectAddr.String(), - Msg: ownerQuery, - }, - }}}} - reflectOwnerBin := buildReflectQuery(t, &reflectOwnerQuery) - res, err = keeper.QuerySmart(ctx, reflectAddr, reflectOwnerBin) - require.NoError(t, err) - // first we pull out the data from chain response, before parsing the original response - var reflectRes testdata.ChainResponse - mustParse(t, res, &reflectRes) - var reflectOwnerRes testdata.OwnerResponse - mustParse(t, reflectRes.Data, &reflectOwnerRes) - require.Equal(t, reflectOwnerRes.Owner, creator.String()) - - // and with queryRaw - reflectStateQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ - Raw: &wasmvmtypes.RawQuery{ - ContractAddr: reflectAddr.String(), - Key: configKey, - }, - }}}} - reflectStateBin := buildReflectQuery(t, &reflectStateQuery) - res, err = keeper.QuerySmart(ctx, reflectAddr, reflectStateBin) - require.NoError(t, err) - // first we pull out the data from chain response, before parsing the original response - var reflectRawRes testdata.ChainResponse - mustParse(t, res, &reflectRawRes) - // now, with the raw data, we can parse it into state - var reflectStateRes reflectState - mustParse(t, reflectRawRes.Data, &reflectStateRes) - require.Equal(t, reflectStateRes.Owner, creator.String()) -} - -func TestWasmRawQueryWithNil(t *testing.T) { - cdc := MakeEncodingConfig(t).Marshaler - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) - keeper := keepers.WasmKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - - // upload reflect code - reflectID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), reflectID) - - // creator instantiates a contract and gives it tokens - reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - reflectAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) - require.NoError(t, err) - require.NotEmpty(t, reflectAddr) - - // control: query directly - missingKey := []byte{0, 1, 2, 3, 4} - raw := keeper.QueryRaw(ctx, reflectAddr, missingKey) - require.Nil(t, raw) - - // and with queryRaw - reflectQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ - Raw: &wasmvmtypes.RawQuery{ - ContractAddr: reflectAddr.String(), - Key: missingKey, - }, - }}}} - reflectStateBin := buildReflectQuery(t, &reflectQuery) - res, err := keeper.QuerySmart(ctx, reflectAddr, reflectStateBin) - require.NoError(t, err) - - // first we pull out the data from chain response, before parsing the original response - var reflectRawRes testdata.ChainResponse - mustParse(t, res, &reflectRawRes) - // and make sure there is no data - require.Empty(t, reflectRawRes.Data) - // we get an empty byte slice not nil (if anyone care in go-land) - require.Equal(t, []byte{}, reflectRawRes.Data) -} - -func TestRustPanicIsHandled(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, ReflectFeatures) - keeper := keepers.ContractKeeper - - creator := keepers.Faucet.NewFundedRandomAccount(ctx, sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))...) - - // upload code - codeID, _, err := keeper.Create(ctx, creator, testdata.CyberpunkContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), codeID) - - contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "cyberpunk contract", nil) - require.NoError(t, err) - require.NotEmpty(t, contractAddr) - - // when panic is triggered - msg := []byte(`{"panic":{}}`) - gotData, err := keeper.Execute(ctx, contractAddr, creator, msg, nil) - require.ErrorIs(t, err, types.ErrExecuteFailed) - assert.Contains(t, err.Error(), "panicked at 'This page intentionally faulted'") - assert.Nil(t, gotData) -} - -func checkAccount(t *testing.T, ctx sdk.Context, accKeeper authkeeper.AccountKeeper, bankKeeper bankkeeper.Keeper, addr sdk.AccAddress, expected sdk.Coins) { - acct := accKeeper.GetAccount(ctx, addr) - if expected == nil { - assert.Nil(t, acct) - } else { - assert.NotNil(t, acct) - if expected.Empty() { - // there is confusion between nil and empty slice... let's just treat them the same - assert.True(t, bankKeeper.GetAllBalances(ctx, acct.GetAddress()).Empty()) - } else { - assert.Equal(t, bankKeeper.GetAllBalances(ctx, acct.GetAddress()), expected) - } - } -} - -/**** Code to support custom messages *****/ - -type reflectCustomMsg struct { - Debug string `json:"debug,omitempty"` - Raw []byte `json:"raw,omitempty"` -} - -// toReflectRawMsg encodes an sdk msg using any type with json encoding. -// Then wraps it as an opaque message -func toReflectRawMsg(cdc codec.Codec, msg sdk.Msg) (wasmvmtypes.CosmosMsg, error) { - codecAny, err := codectypes.NewAnyWithValue(msg) - if err != nil { - return wasmvmtypes.CosmosMsg{}, err - } - rawBz, err := cdc.MarshalJSON(codecAny) - if err != nil { - return wasmvmtypes.CosmosMsg{}, errorsmod.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) - } - customMsg, _ := json.Marshal(reflectCustomMsg{ - Raw: rawBz, - }) - res := wasmvmtypes.CosmosMsg{ - Custom: customMsg, - } - return res, err -} - -// reflectEncoders needs to be registered in test setup to handle custom message callbacks -func reflectEncoders(cdc codec.Codec) *MessageEncoders { - return &MessageEncoders{ - Custom: fromReflectRawMsg(cdc), - } -} - -// fromReflectRawMsg decodes msg.Data to an sdk.Msg using proto Any and json encoding. -// this needs to be registered on the Encoders -func fromReflectRawMsg(cdc codec.Codec) CustomEncoder { - return func(_sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - var custom reflectCustomMsg - err := json.Unmarshal(msg, &custom) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) - } - if custom.Raw != nil { - var codecAny codectypes.Any - if err := cdc.UnmarshalJSON(custom.Raw, &codecAny); err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) - } - var msg sdk.Msg - if err := cdc.UnpackAny(&codecAny, &msg); err != nil { - return nil, err - } - return []sdk.Msg{msg}, nil - } - if custom.Debug != "" { - return nil, errorsmod.Wrapf(types.ErrInvalidMsg, "Custom Debug: %s", custom.Debug) - } - return nil, errorsmod.Wrap(types.ErrInvalidMsg, "Unknown Custom message variant") - } -} - -type reflectCustomQuery struct { - Ping *struct{} `json:"ping,omitempty"` - Capitalized *testdata.Text `json:"capitalized,omitempty"` -} - -// this is from the go code back to the contract (capitalized or ping) -type customQueryResponse struct { - Msg string `json:"msg"` -} - -// this is from the contract to the go code (capitalized or ping) -type capitalizedResponse struct { - Text string `json:"text"` -} - -// reflectPlugins needs to be registered in test setup to handle custom query callbacks -func reflectPlugins() *QueryPlugins { - return &QueryPlugins{ - Custom: performCustomQuery, - } -} - -func performCustomQuery(_ sdk.Context, request json.RawMessage) ([]byte, error) { - var custom reflectCustomQuery - err := json.Unmarshal(request, &custom) - if err != nil { - return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) - } - if custom.Capitalized != nil { - msg := strings.ToUpper(custom.Capitalized.Text) - return json.Marshal(customQueryResponse{Msg: msg}) - } - if custom.Ping != nil { - return json.Marshal(customQueryResponse{Msg: "pong"}) - } - return nil, errorsmod.Wrap(types.ErrInvalidMsg, "Unknown Custom query variant") -} diff --git a/x/wasm/keeper/relay.go b/x/wasm/keeper/relay.go deleted file mode 100644 index a265179..0000000 --- a/x/wasm/keeper/relay.go +++ /dev/null @@ -1,203 +0,0 @@ -package keeper - -import ( - "time" - - errorsmod "cosmossdk.io/errors" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/cosmos/cosmos-sdk/telemetry" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var _ types.IBCContractKeeper = (*Keeper)(nil) - -// OnOpenChannel calls the contract to participate in the IBC channel handshake step. -// In the IBC protocol this is either the `Channel Open Init` event on the initiating chain or -// `Channel Open Try` on the counterparty chain. -// Protocol version and channel ordering should be verified for example. -// See https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#channel-lifecycle-management -func (k Keeper) OnOpenChannel( - ctx sdk.Context, - contractAddr sdk.AccAddress, - msg wasmvmtypes.IBCChannelOpenMsg, -) (string, error) { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-open-channel") - _, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) - if err != nil { - return "", err - } - - env := types.NewEnv(ctx, contractAddr) - querier := k.newQueryHandler(ctx, contractAddr) - - gas := k.runtimeGasForContract(ctx) - res, gasUsed, execErr := k.wasmVM.IBCChannelOpen(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - return "", errorsmod.Wrap(types.ErrExecuteFailed, execErr.Error()) - } - if res != nil { - return res.Version, nil - } - return "", nil -} - -// OnConnectChannel calls the contract to let it know the IBC channel was established. -// In the IBC protocol this is either the `Channel Open Ack` event on the initiating chain or -// `Channel Open Confirm` on the counterparty chain. -// -// There is an open issue with the [cosmos-sdk](https://github.com/cosmos/cosmos-sdk/issues/8334) -// that the counterparty channelID is empty on the initiating chain -// See https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#channel-lifecycle-management -func (k Keeper) OnConnectChannel( - ctx sdk.Context, - contractAddr sdk.AccAddress, - msg wasmvmtypes.IBCChannelConnectMsg, -) error { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-connect-channel") - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) - if err != nil { - return err - } - - env := types.NewEnv(ctx, contractAddr) - querier := k.newQueryHandler(ctx, contractAddr) - - gas := k.runtimeGasForContract(ctx) - res, gasUsed, execErr := k.wasmVM.IBCChannelConnect(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - return errorsmod.Wrap(types.ErrExecuteFailed, execErr.Error()) - } - - return k.handleIBCBasicContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res) -} - -// OnCloseChannel calls the contract to let it know the IBC channel is closed. -// Calling modules MAY atomically execute appropriate application logic in conjunction with calling chanCloseConfirm. -// -// Once closed, channels cannot be reopened and identifiers cannot be reused. Identifier reuse is prevented because -// we want to prevent potential replay of previously sent packets -// See https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#channel-lifecycle-management -func (k Keeper) OnCloseChannel( - ctx sdk.Context, - contractAddr sdk.AccAddress, - msg wasmvmtypes.IBCChannelCloseMsg, -) error { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-close-channel") - - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) - if err != nil { - return err - } - - params := types.NewEnv(ctx, contractAddr) - querier := k.newQueryHandler(ctx, contractAddr) - - gas := k.runtimeGasForContract(ctx) - res, gasUsed, execErr := k.wasmVM.IBCChannelClose(codeInfo.CodeHash, params, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - return errorsmod.Wrap(types.ErrExecuteFailed, execErr.Error()) - } - - return k.handleIBCBasicContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res) -} - -// OnRecvPacket calls the contract to process the incoming IBC packet. The contract fully owns the data processing and -// returns the acknowledgement data for the chain level. This allows custom applications and protocols on top -// of IBC. Although it is recommended to use the standard acknowledgement envelope defined in -// https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#acknowledgement-envelope -// -// For more information see: https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#packet-flow--handling -func (k Keeper) OnRecvPacket( - ctx sdk.Context, - contractAddr sdk.AccAddress, - msg wasmvmtypes.IBCPacketReceiveMsg, -) ([]byte, error) { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-recv-packet") - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) - if err != nil { - return nil, err - } - - env := types.NewEnv(ctx, contractAddr) - querier := k.newQueryHandler(ctx, contractAddr) - - gas := k.runtimeGasForContract(ctx) - res, gasUsed, execErr := k.wasmVM.IBCPacketReceive(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - panic(execErr) - } - if res.Err != "" { // handle error case as before https://github.com/CosmWasm/wasmvm/commit/c300106fe5c9426a495f8e10821e00a9330c56c6 - return nil, errorsmod.Wrap(types.ErrExecuteFailed, res.Err) - } - // note submessage reply results can overwrite the `Acknowledgement` data - return k.handleContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res.Ok.Messages, res.Ok.Attributes, res.Ok.Acknowledgement, res.Ok.Events) -} - -// OnAckPacket calls the contract to handle the "acknowledgement" data which can contain success or failure of a packet -// acknowledgement written on the receiving chain for example. This is application level data and fully owned by the -// contract. The use of the standard acknowledgement envelope is recommended: https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#acknowledgement-envelope -// -// On application errors the contract can revert an operation like returning tokens as in ibc-transfer. -// -// For more information see: https://github.com/cosmos/ics/tree/master/spec/ics-004-channel-and-packet-semantics#packet-flow--handling -func (k Keeper) OnAckPacket( - ctx sdk.Context, - contractAddr sdk.AccAddress, - msg wasmvmtypes.IBCPacketAckMsg, -) error { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-ack-packet") - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) - if err != nil { - return err - } - - env := types.NewEnv(ctx, contractAddr) - querier := k.newQueryHandler(ctx, contractAddr) - - gas := k.runtimeGasForContract(ctx) - res, gasUsed, execErr := k.wasmVM.IBCPacketAck(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - return errorsmod.Wrap(types.ErrExecuteFailed, execErr.Error()) - } - return k.handleIBCBasicContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res) -} - -// OnTimeoutPacket calls the contract to let it know the packet was never received on the destination chain within -// the timeout boundaries. -// The contract should handle this on the application level and undo the original operation -func (k Keeper) OnTimeoutPacket( - ctx sdk.Context, - contractAddr sdk.AccAddress, - msg wasmvmtypes.IBCPacketTimeoutMsg, -) error { - defer telemetry.MeasureSince(time.Now(), "wasm", "contract", "ibc-timeout-packet") - - contractInfo, codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) - if err != nil { - return err - } - - env := types.NewEnv(ctx, contractAddr) - querier := k.newQueryHandler(ctx, contractAddr) - - gas := k.runtimeGasForContract(ctx) - res, gasUsed, execErr := k.wasmVM.IBCPacketTimeout(codeInfo.CodeHash, env, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas, costJSONDeserialization) - k.consumeRuntimeGas(ctx, gasUsed) - if execErr != nil { - return errorsmod.Wrap(types.ErrExecuteFailed, execErr.Error()) - } - - return k.handleIBCBasicContractResponse(ctx, contractAddr, contractInfo.IBCPortID, res) -} - -func (k Keeper) handleIBCBasicContractResponse(ctx sdk.Context, addr sdk.AccAddress, id string, res *wasmvmtypes.IBCBasicResponse) error { - _, err := k.handleContractResponse(ctx, addr, id, res.Messages, res.Attributes, nil, res.Events) - return err -} diff --git a/x/wasm/keeper/relay_test.go b/x/wasm/keeper/relay_test.go deleted file mode 100644 index c9c72ad..0000000 --- a/x/wasm/keeper/relay_test.go +++ /dev/null @@ -1,711 +0,0 @@ -package keeper - -import ( - "encoding/json" - "errors" - "math" - "testing" - - wasmvm "github.com/CosmWasm/wasmvm" - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -func TestOnOpenChannel(t *testing.T) { - var m wasmtesting.MockWasmer - wasmtesting.MakeIBCInstantiable(&m) - messenger := &wasmtesting.MockMessageHandler{} - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithMessageHandler(messenger)) - example := SeedNewContractInstance(t, parentCtx, keepers, &m) - const myContractGas = 40 - - specs := map[string]struct { - contractAddr sdk.AccAddress - contractGas sdk.Gas - contractErr error - expGas uint64 - expErr bool - }{ - "consume contract gas": { - contractAddr: example.Contract, - contractGas: myContractGas, - expGas: myContractGas, - }, - "consume max gas": { - contractAddr: example.Contract, - contractGas: math.MaxUint64 / DefaultGasMultiplier, - expGas: math.MaxUint64 / DefaultGasMultiplier, - }, - "consume gas on error": { - contractAddr: example.Contract, - contractGas: myContractGas, - contractErr: errors.New("test, ignore"), - expErr: true, - }, - "unknown contract address": { - contractAddr: RandomAccountAddress(t), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - myChannel := wasmvmtypes.IBCChannel{Version: "my test channel"} - myMsg := wasmvmtypes.IBCChannelOpenMsg{OpenTry: &wasmvmtypes.IBCOpenTry{Channel: myChannel, CounterpartyVersion: "foo"}} - m.IBCChannelOpenFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) { - assert.Equal(t, myMsg, msg) - return &wasmvmtypes.IBC3ChannelOpenResponse{}, spec.contractGas * DefaultGasMultiplier, spec.contractErr - } - - ctx, _ := parentCtx.CacheContext() - before := ctx.GasMeter().GasConsumed() - - // when - msg := wasmvmtypes.IBCChannelOpenMsg{ - OpenTry: &wasmvmtypes.IBCOpenTry{ - Channel: myChannel, - CounterpartyVersion: "foo", - }, - } - _, err := keepers.WasmKeeper.OnOpenChannel(ctx, spec.contractAddr, msg) - - // then - if spec.expErr { - require.Error(t, err) - return - } - require.NoError(t, err) - // verify gas consumed - const storageCosts = sdk.Gas(2903) - assert.Equal(t, spec.expGas, ctx.GasMeter().GasConsumed()-before-storageCosts) - }) - } -} - -func TestOnConnectChannel(t *testing.T) { - var m wasmtesting.MockWasmer - wasmtesting.MakeIBCInstantiable(&m) - messenger := &wasmtesting.MockMessageHandler{} - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithMessageHandler(messenger)) - example := SeedNewContractInstance(t, parentCtx, keepers, &m) - const myContractGas = 40 - - specs := map[string]struct { - contractAddr sdk.AccAddress - contractResp *wasmvmtypes.IBCBasicResponse - contractErr error - overwriteMessenger *wasmtesting.MockMessageHandler - expContractGas sdk.Gas - expErr bool - expEventTypes []string - }{ - "consume contract gas": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{}, - }, - "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - contractErr: errors.New("test, ignore"), - expErr: true, - }, - "dispatch contract messages on success": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - }, - }, - "emit contract events on success": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "messenger errors returned, events stored": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - overwriteMessenger: wasmtesting.NewErroringMessageHandler(), - expErr: true, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "unknown contract address": { - contractAddr: RandomAccountAddress(t), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - myChannel := wasmvmtypes.IBCChannel{Version: "my test channel"} - myMsg := wasmvmtypes.IBCChannelConnectMsg{OpenConfirm: &wasmvmtypes.IBCOpenConfirm{Channel: myChannel}} - m.IBCChannelConnectFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelConnectMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) { - assert.Equal(t, msg, myMsg) - return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr - } - - ctx, _ := parentCtx.CacheContext() - ctx = ctx.WithEventManager(sdk.NewEventManager()) - - before := ctx.GasMeter().GasConsumed() - msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() - *messenger = *msger - if spec.overwriteMessenger != nil { - *messenger = *spec.overwriteMessenger - } - - // when - msg := wasmvmtypes.IBCChannelConnectMsg{ - OpenConfirm: &wasmvmtypes.IBCOpenConfirm{ - Channel: myChannel, - }, - } - - err := keepers.WasmKeeper.OnConnectChannel(ctx, spec.contractAddr, msg) - - // then - if spec.expErr { - require.Error(t, err) - assert.Empty(t, capturedMsgs) // no messages captured on error - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - return - } - require.NoError(t, err) - // verify gas consumed - const storageCosts = sdk.Gas(2903) - assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) - // verify msgs dispatched - require.Len(t, *capturedMsgs, len(spec.contractResp.Messages)) - for i, m := range spec.contractResp.Messages { - assert.Equal(t, (*capturedMsgs)[i], m.Msg) - } - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - }) - } -} - -func TestOnCloseChannel(t *testing.T) { - var m wasmtesting.MockWasmer - wasmtesting.MakeIBCInstantiable(&m) - messenger := &wasmtesting.MockMessageHandler{} - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithMessageHandler(messenger)) - example := SeedNewContractInstance(t, parentCtx, keepers, &m) - const myContractGas = 40 - - specs := map[string]struct { - contractAddr sdk.AccAddress - contractResp *wasmvmtypes.IBCBasicResponse - contractErr error - overwriteMessenger *wasmtesting.MockMessageHandler - expContractGas sdk.Gas - expErr bool - expEventTypes []string - }{ - "consume contract gas": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{}, - }, - "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - contractErr: errors.New("test, ignore"), - expErr: true, - }, - "dispatch contract messages on success": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - }, - }, - "emit contract events on success": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "messenger errors returned, events stored": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - overwriteMessenger: wasmtesting.NewErroringMessageHandler(), - expErr: true, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "unknown contract address": { - contractAddr: RandomAccountAddress(t), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - myChannel := wasmvmtypes.IBCChannel{Version: "my test channel"} - myMsg := wasmvmtypes.IBCChannelCloseMsg{CloseInit: &wasmvmtypes.IBCCloseInit{Channel: myChannel}} - m.IBCChannelCloseFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelCloseMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) { - assert.Equal(t, msg, myMsg) - return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr - } - - ctx, _ := parentCtx.CacheContext() - before := ctx.GasMeter().GasConsumed() - msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() - *messenger = *msger - - if spec.overwriteMessenger != nil { - *messenger = *spec.overwriteMessenger - } - - // when - msg := wasmvmtypes.IBCChannelCloseMsg{ - CloseInit: &wasmvmtypes.IBCCloseInit{ - Channel: myChannel, - }, - } - err := keepers.WasmKeeper.OnCloseChannel(ctx, spec.contractAddr, msg) - - // then - if spec.expErr { - require.Error(t, err) - assert.Empty(t, capturedMsgs) // no messages captured on error - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - return - } - require.NoError(t, err) - // verify gas consumed - const storageCosts = sdk.Gas(2903) - assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) - // verify msgs dispatched - require.Len(t, *capturedMsgs, len(spec.contractResp.Messages)) - for i, m := range spec.contractResp.Messages { - assert.Equal(t, (*capturedMsgs)[i], m.Msg) - } - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - }) - } -} - -func TestOnRecvPacket(t *testing.T) { - var m wasmtesting.MockWasmer - wasmtesting.MakeIBCInstantiable(&m) - messenger := &wasmtesting.MockMessageHandler{} - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithMessageHandler(messenger)) - example := SeedNewContractInstance(t, parentCtx, keepers, &m) - const myContractGas = 40 - const storageCosts = sdk.Gas(2903) - - specs := map[string]struct { - contractAddr sdk.AccAddress - contractResp *wasmvmtypes.IBCReceiveResponse - contractErr error - overwriteMessenger *wasmtesting.MockMessageHandler - mockReplyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) - expContractGas sdk.Gas - expAck []byte - expErr bool - expPanic bool - expEventTypes []string - }{ - "consume contract gas": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCReceiveResponse{ - Acknowledgement: []byte("myAck"), - }, - expAck: []byte("myAck"), - }, - "can return empty ack": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCReceiveResponse{}, - }, - "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCReceiveResponse{ - Acknowledgement: []byte("myAck"), - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - contractErr: errors.New("test, ignore"), - expPanic: true, - }, - "dispatch contract messages on success": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCReceiveResponse{ - Acknowledgement: []byte("myAck"), - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - }, - expAck: []byte("myAck"), - }, - "emit contract attributes on success": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCReceiveResponse{ - Acknowledgement: []byte("myAck"), - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - expEventTypes: []string{types.WasmModuleEventType}, - expAck: []byte("myAck"), - }, - "emit contract events on success": { - contractAddr: example.Contract, - expContractGas: myContractGas + 46, // charge or custom event as well - contractResp: &wasmvmtypes.IBCReceiveResponse{ - Acknowledgement: []byte("myAck"), - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - Events: []wasmvmtypes.Event{{ - Type: "custom", - Attributes: []wasmvmtypes.EventAttribute{{ - Key: "message", - Value: "to rudi", - }}, - }}, - }, - expEventTypes: []string{types.WasmModuleEventType, "wasm-custom"}, - expAck: []byte("myAck"), - }, - "messenger errors returned, events stored": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCReceiveResponse{ - Acknowledgement: []byte("myAck"), - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - overwriteMessenger: wasmtesting.NewErroringMessageHandler(), - expErr: true, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "submessage reply can overwrite ack data": { - contractAddr: example.Contract, - expContractGas: myContractGas + storageCosts, - contractResp: &wasmvmtypes.IBCReceiveResponse{ - Acknowledgement: []byte("myAck"), - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyAlways, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}}, - }, - mockReplyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) { - return &wasmvmtypes.Response{Data: []byte("myBetterAck")}, 0, nil - }, - expAck: []byte("myBetterAck"), - expEventTypes: []string{types.EventTypeReply}, - }, - "unknown contract address": { - contractAddr: RandomAccountAddress(t), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - myPacket := wasmvmtypes.IBCPacket{Data: []byte("my data")} - - m.IBCPacketReceiveFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketReceiveMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCReceiveResult, uint64, error) { - assert.Equal(t, myPacket, msg.Packet) - return &wasmvmtypes.IBCReceiveResult{Ok: spec.contractResp}, myContractGas * DefaultGasMultiplier, spec.contractErr - } - if spec.mockReplyFn != nil { - m.ReplyFn = spec.mockReplyFn - h, ok := keepers.WasmKeeper.wasmVMResponseHandler.(*DefaultWasmVMContractResponseHandler) - require.True(t, ok) - h.md = NewMessageDispatcher(messenger, keepers.WasmKeeper) - } - - ctx, _ := parentCtx.CacheContext() - before := ctx.GasMeter().GasConsumed() - - msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() - *messenger = *msger - - if spec.overwriteMessenger != nil { - *messenger = *spec.overwriteMessenger - } - - // when - msg := wasmvmtypes.IBCPacketReceiveMsg{Packet: myPacket} - if spec.expPanic { - assert.Panics(t, func() { - keepers.WasmKeeper.OnRecvPacket(ctx, spec.contractAddr, msg) //nolint:errcheck // we are checking for a panic here - }) - return - } - gotAck, err := keepers.WasmKeeper.OnRecvPacket(ctx, spec.contractAddr, msg) - - // then - if spec.expErr { - require.Error(t, err) - assert.Empty(t, capturedMsgs) // no messages captured on error - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - return - } - require.NoError(t, err) - require.Equal(t, spec.expAck, gotAck) - - // verify gas consumed - const storageCosts = sdk.Gas(2903) - assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) - // verify msgs dispatched - require.Len(t, *capturedMsgs, len(spec.contractResp.Messages)) - for i, m := range spec.contractResp.Messages { - assert.Equal(t, (*capturedMsgs)[i], m.Msg) - } - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - }) - } -} - -func TestOnAckPacket(t *testing.T) { - var m wasmtesting.MockWasmer - wasmtesting.MakeIBCInstantiable(&m) - messenger := &wasmtesting.MockMessageHandler{} - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithMessageHandler(messenger)) - example := SeedNewContractInstance(t, parentCtx, keepers, &m) - const myContractGas = 40 - - specs := map[string]struct { - contractAddr sdk.AccAddress - contractResp *wasmvmtypes.IBCBasicResponse - contractErr error - overwriteMessenger *wasmtesting.MockMessageHandler - expContractGas sdk.Gas - expErr bool - expEventTypes []string - }{ - "consume contract gas": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{}, - }, - "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - contractErr: errors.New("test, ignore"), - expErr: true, - }, - "dispatch contract messages on success": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - }, - }, - "emit contract events on success": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "messenger errors returned, events stored": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - overwriteMessenger: wasmtesting.NewErroringMessageHandler(), - expErr: true, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "unknown contract address": { - contractAddr: RandomAccountAddress(t), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - myAck := wasmvmtypes.IBCPacketAckMsg{Acknowledgement: wasmvmtypes.IBCAcknowledgement{Data: []byte("myAck")}} - m.IBCPacketAckFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketAckMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) { - assert.Equal(t, myAck, msg) - return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr - } - - ctx, _ := parentCtx.CacheContext() - before := ctx.GasMeter().GasConsumed() - msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() - *messenger = *msger - - if spec.overwriteMessenger != nil { - *messenger = *spec.overwriteMessenger - } - - // when - err := keepers.WasmKeeper.OnAckPacket(ctx, spec.contractAddr, myAck) - - // then - - if spec.expErr { - require.Error(t, err) - assert.Empty(t, capturedMsgs) // no messages captured on error - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - return - } - require.NoError(t, err) - // verify gas consumed - const storageCosts = sdk.Gas(2903) - assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) - // verify msgs dispatched - require.Len(t, *capturedMsgs, len(spec.contractResp.Messages)) - for i, m := range spec.contractResp.Messages { - assert.Equal(t, (*capturedMsgs)[i], m.Msg) - } - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - }) - } -} - -func TestOnTimeoutPacket(t *testing.T) { - var m wasmtesting.MockWasmer - wasmtesting.MakeIBCInstantiable(&m) - messenger := &wasmtesting.MockMessageHandler{} - parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithMessageHandler(messenger)) - example := SeedNewContractInstance(t, parentCtx, keepers, &m) - const myContractGas = 40 - - specs := map[string]struct { - contractAddr sdk.AccAddress - contractResp *wasmvmtypes.IBCBasicResponse - contractErr error - overwriteMessenger *wasmtesting.MockMessageHandler - expContractGas sdk.Gas - expErr bool - expEventTypes []string - }{ - "consume contract gas": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{}, - }, - "consume gas on error, ignore events + messages": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - contractErr: errors.New("test, ignore"), - expErr: true, - }, - "dispatch contract messages on success": { - contractAddr: example.Contract, - expContractGas: myContractGas, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - }, - }, - "emit contract attributes on success": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "emit contract events on success": { - contractAddr: example.Contract, - expContractGas: myContractGas + 46, // cost for custom events - contractResp: &wasmvmtypes.IBCBasicResponse{ - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - Events: []wasmvmtypes.Event{{ - Type: "custom", - Attributes: []wasmvmtypes.EventAttribute{{ - Key: "message", - Value: "to rudi", - }}, - }}, - }, - expEventTypes: []string{types.WasmModuleEventType, "wasm-custom"}, - }, - "messenger errors returned, events stored before": { - contractAddr: example.Contract, - expContractGas: myContractGas + 10, - contractResp: &wasmvmtypes.IBCBasicResponse{ - Messages: []wasmvmtypes.SubMsg{{ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Bank: &wasmvmtypes.BankMsg{}}}, {ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{Custom: json.RawMessage(`{"foo":"bar"}`)}}}, - Attributes: []wasmvmtypes.EventAttribute{{Key: "Foo", Value: "Bar"}}, - }, - overwriteMessenger: wasmtesting.NewErroringMessageHandler(), - expErr: true, - expEventTypes: []string{types.WasmModuleEventType}, - }, - "unknown contract address": { - contractAddr: RandomAccountAddress(t), - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - myPacket := wasmvmtypes.IBCPacket{Data: []byte("my test packet")} - m.IBCPacketTimeoutFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketTimeoutMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) { - assert.Equal(t, myPacket, msg.Packet) - return spec.contractResp, myContractGas * DefaultGasMultiplier, spec.contractErr - } - - ctx, _ := parentCtx.CacheContext() - before := ctx.GasMeter().GasConsumed() - msger, capturedMsgs := wasmtesting.NewCapturingMessageHandler() - *messenger = *msger - - if spec.overwriteMessenger != nil { - *messenger = *spec.overwriteMessenger - } - - // when - msg := wasmvmtypes.IBCPacketTimeoutMsg{Packet: myPacket} - err := keepers.WasmKeeper.OnTimeoutPacket(ctx, spec.contractAddr, msg) - - // then - if spec.expErr { - require.Error(t, err) - assert.Empty(t, capturedMsgs) // no messages captured on error - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - return - } - require.NoError(t, err) - // verify gas consumed - const storageCosts = sdk.Gas(2903) - assert.Equal(t, spec.expContractGas, ctx.GasMeter().GasConsumed()-before-storageCosts) - // verify msgs dispatched - require.Len(t, *capturedMsgs, len(spec.contractResp.Messages)) - for i, m := range spec.contractResp.Messages { - assert.Equal(t, (*capturedMsgs)[i], m.Msg) - } - assert.Equal(t, spec.expEventTypes, stripTypes(ctx.EventManager().Events())) - }) - } -} - -func stripTypes(events sdk.Events) []string { - var r []string - for _, e := range events { - r = append(r, e.Type) - } - return r -} diff --git a/x/wasm/keeper/snapshotter.go b/x/wasm/keeper/snapshotter.go deleted file mode 100644 index 3642c51..0000000 --- a/x/wasm/keeper/snapshotter.go +++ /dev/null @@ -1,141 +0,0 @@ -package keeper - -import ( - "encoding/hex" - "io" - - errorsmod "cosmossdk.io/errors" - "github.com/cometbft/cometbft/libs/log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - snapshot "github.com/cosmos/cosmos-sdk/snapshots/types" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/terpnetwork/terp-core/x/wasm/ioutils" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var _ snapshot.ExtensionSnapshotter = &WasmSnapshotter{} - -// SnapshotFormat format 1 is just gzipped wasm byte code for each item payload. No protobuf envelope, no metadata. -const SnapshotFormat = 1 - -type WasmSnapshotter struct { - wasm *Keeper - cms sdk.MultiStore -} - -func NewWasmSnapshotter(cms sdk.MultiStore, wasm *Keeper) *WasmSnapshotter { - return &WasmSnapshotter{ - wasm: wasm, - cms: cms, - } -} - -func (ws *WasmSnapshotter) SnapshotName() string { - return types.ModuleName -} - -func (ws *WasmSnapshotter) SnapshotFormat() uint32 { - return SnapshotFormat -} - -func (ws *WasmSnapshotter) SupportedFormats() []uint32 { - // If we support older formats, add them here and handle them in Restore - return []uint32{SnapshotFormat} -} - -func (ws *WasmSnapshotter) SnapshotExtension(height uint64, payloadWriter snapshot.ExtensionPayloadWriter) error { - cacheMS, err := ws.cms.CacheMultiStoreWithVersion(int64(height)) - if err != nil { - return err - } - - ctx := sdk.NewContext(cacheMS, tmproto.Header{}, false, log.NewNopLogger()) - seenBefore := make(map[string]bool) - var rerr error - - ws.wasm.IterateCodeInfos(ctx, func(id uint64, info types.CodeInfo) bool { - // Many code ids may point to the same code hash... only sync it once - hexHash := hex.EncodeToString(info.CodeHash) - // if seenBefore, just skip this one and move to the next - if seenBefore[hexHash] { - return false - } - seenBefore[hexHash] = true - - // load code and abort on error - wasmBytes, err := ws.wasm.GetByteCode(ctx, id) - if err != nil { - rerr = err - return true - } - - compressedWasm, err := ioutils.GzipIt(wasmBytes) - if err != nil { - rerr = err - return true - } - - err = payloadWriter(compressedWasm) - if err != nil { - rerr = err - return true - } - - return false - }) - - return rerr -} - -func (ws *WasmSnapshotter) RestoreExtension(height uint64, format uint32, payloadReader snapshot.ExtensionPayloadReader) error { - if format == SnapshotFormat { - return ws.processAllItems(height, payloadReader, restoreV1, finalizeV1) - } - return snapshot.ErrUnknownFormat -} - -func restoreV1(_ sdk.Context, k *Keeper, compressedCode []byte) error { - if !ioutils.IsGzip(compressedCode) { - return types.ErrInvalid.Wrap("not a gzip") - } - wasmCode, err := ioutils.Uncompress(compressedCode, uint64(types.MaxWasmSize)) - if err != nil { - return errorsmod.Wrap(types.ErrCreateFailed, err.Error()) - } - - // FIXME: check which codeIDs the checksum matches?? - _, err = k.wasmVM.Create(wasmCode) - if err != nil { - return errorsmod.Wrap(types.ErrCreateFailed, err.Error()) - } - return nil -} - -func finalizeV1(ctx sdk.Context, k *Keeper) error { - // FIXME: ensure all codes have been uploaded? - return k.InitializePinnedCodes(ctx) -} - -func (ws *WasmSnapshotter) processAllItems( - height uint64, - payloadReader snapshot.ExtensionPayloadReader, - cb func(sdk.Context, *Keeper, []byte) error, - finalize func(sdk.Context, *Keeper) error, -) error { - ctx := sdk.NewContext(ws.cms, tmproto.Header{Height: int64(height)}, false, log.NewNopLogger()) - for { - payload, err := payloadReader() - if err == io.EOF { - break - } else if err != nil { - return err - } - - if err := cb(ctx, ws.wasm, payload); err != nil { - return errorsmod.Wrap(err, "processing snapshot item") - } - } - - return finalize(ctx, ws.wasm) -} diff --git a/x/wasm/keeper/snapshotter_integration_test.go b/x/wasm/keeper/snapshotter_integration_test.go deleted file mode 100644 index 2925f09..0000000 --- a/x/wasm/keeper/snapshotter_integration_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package keeper_test - -import ( - "crypto/sha256" - "os" - "testing" - "time" - - "github.com/terpnetwork/terp-core/x/wasm/types" - - "github.com/stretchr/testify/assert" - - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - tmtypes "github.com/cometbft/cometbft/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/app" - "github.com/terpnetwork/terp-core/x/wasm/keeper" -) - -func TestSnapshotter(t *testing.T) { - specs := map[string]struct { - wasmFiles []string - }{ - "single contract": { - wasmFiles: []string{"./testdata/reflect.wasm"}, - }, - "multiple contract": { - wasmFiles: []string{"./testdata/reflect.wasm", "./testdata/burner.wasm", "./testdata/reflect.wasm"}, - }, - "duplicate contracts": { - wasmFiles: []string{"./testdata/reflect.wasm", "./testdata/reflect.wasm"}, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - // setup source app - srcTerpApp, genesisAddr := newWasmExampleApp(t) - - // store wasm codes on chain - ctx := srcTerpApp.NewUncachedContext(false, tmproto.Header{ - ChainID: "foo", - Height: srcTerpApp.LastBlockHeight() + 1, - Time: time.Now(), - }) - wasmKeeper := srcTerpApp.WasmKeeper - contractKeeper := keeper.NewDefaultPermissionKeeper(&wasmKeeper) - - srcCodeIDToChecksum := make(map[uint64][]byte, len(spec.wasmFiles)) - for i, v := range spec.wasmFiles { - wasmCode, err := os.ReadFile(v) - require.NoError(t, err) - codeID, checksum, err := contractKeeper.Create(ctx, genesisAddr, wasmCode, nil) - require.NoError(t, err) - require.Equal(t, uint64(i+1), codeID) - srcCodeIDToChecksum[codeID] = checksum - } - // create snapshot - srcTerpApp.Commit() - snapshotHeight := uint64(srcTerpApp.LastBlockHeight()) - snapshot, err := srcTerpApp.SnapshotManager().Create(snapshotHeight) - require.NoError(t, err) - assert.NotNil(t, snapshot) - - // when snapshot imported into dest app instance - destTerpApp := app.SetupWithEmptyStore(t) - require.NoError(t, destTerpApp.SnapshotManager().Restore(*snapshot)) - for i := uint32(0); i < snapshot.Chunks; i++ { - chunkBz, err := srcTerpApp.SnapshotManager().LoadChunk(snapshot.Height, snapshot.Format, i) - require.NoError(t, err) - end, err := destTerpApp.SnapshotManager().RestoreChunk(chunkBz) - require.NoError(t, err) - if end { - break - } - } - - // then all wasm contracts are imported - wasmKeeper = destTerpApp.WasmKeeper - ctx = destTerpApp.NewUncachedContext(false, tmproto.Header{ - ChainID: "foo", - Height: destTerpApp.LastBlockHeight() + 1, - Time: time.Now(), - }) - - destCodeIDToChecksum := make(map[uint64][]byte, len(spec.wasmFiles)) - wasmKeeper.IterateCodeInfos(ctx, func(id uint64, info types.CodeInfo) bool { - bz, err := wasmKeeper.GetByteCode(ctx, id) - require.NoError(t, err) - hash := sha256.Sum256(bz) - destCodeIDToChecksum[id] = hash[:] - assert.Equal(t, hash[:], info.CodeHash) - return false - }) - assert.Equal(t, srcCodeIDToChecksum, destCodeIDToChecksum) - }) - } -} - -func newWasmExampleApp(t *testing.T) (*app.TerpApp, sdk.AccAddress) { - senderPrivKey := ed25519.GenPrivKey() - pubKey, err := cryptocodec.ToTmPubKeyInterface(senderPrivKey.PubKey()) - require.NoError(t, err) - - senderAddr := senderPrivKey.PubKey().Address().Bytes() - acc := authtypes.NewBaseAccount(senderAddr, senderPrivKey.PubKey(), 0, 0) - amount, ok := sdk.NewIntFromString("10000000000000000000") - require.True(t, ok) - - balance := banktypes.Balance{ - Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)), - } - validator := tmtypes.NewValidator(pubKey, 1) - valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) - wasmApp := app.SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, "testing", nil, balance) - - return wasmApp, senderAddr -} diff --git a/x/wasm/keeper/staking_test.go b/x/wasm/keeper/staking_test.go deleted file mode 100644 index eb26f9c..0000000 --- a/x/wasm/keeper/staking_test.go +++ /dev/null @@ -1,739 +0,0 @@ -package keeper - -import ( - "encoding/json" - "os" - "testing" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" - "github.com/cosmos/cosmos-sdk/x/staking" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/testdata" - wasmtypes "github.com/terpnetwork/terp-core/x/wasm/types" -) - -type StakingInitMsg struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - Decimals uint8 `json:"decimals"` - Validator sdk.ValAddress `json:"validator"` - ExitTax sdk.Dec `json:"exit_tax"` - // MinWithdrawal is uint128 encoded as a string (use math.Int?) - MinWithdrawl string `json:"min_withdrawal"` -} - -// StakingHandleMsg is used to encode handle messages -type StakingHandleMsg struct { - Transfer *transferPayload `json:"transfer,omitempty"` - Bond *struct{} `json:"bond,omitempty"` - Unbond *unbondPayload `json:"unbond,omitempty"` - Claim *struct{} `json:"claim,omitempty"` - Reinvest *struct{} `json:"reinvest,omitempty"` - Change *testdata.OwnerPayload `json:"change_owner,omitempty"` -} - -type transferPayload struct { - Recipient sdk.Address `json:"recipient"` - // uint128 encoded as string - Amount string `json:"amount"` -} - -type unbondPayload struct { - // uint128 encoded as string - Amount string `json:"amount"` -} - -// StakingQueryMsg is used to encode query messages -type StakingQueryMsg struct { - Balance *addressQuery `json:"balance,omitempty"` - Claims *addressQuery `json:"claims,omitempty"` - TokenInfo *struct{} `json:"token_info,omitempty"` - Investment *struct{} `json:"investment,omitempty"` -} - -type addressQuery struct { - Address sdk.AccAddress `json:"address"` -} - -type BalanceResponse struct { - Balance string `json:"balance,omitempty"` -} - -type ClaimsResponse struct { - Claims string `json:"claims,omitempty"` -} - -type TokenInfoResponse struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - Decimals uint8 `json:"decimals"` -} - -type InvestmentResponse struct { - TokenSupply string `json:"token_supply"` - StakedTokens sdk.Coin `json:"staked_tokens"` - NominalValue sdk.Dec `json:"nominal_value"` - Owner sdk.AccAddress `json:"owner"` - Validator sdk.ValAddress `json:"validator"` - ExitTax sdk.Dec `json:"exit_tax"` - // MinWithdrawl is uint128 encoded as a string (use math.Int?) - MinWithdrawl string `json:"min_withdrawal"` -} - -func TestInitializeStaking(t *testing.T) { - ctx, k := CreateTestInput(t, false, AvailableCapabilities) - accKeeper, stakingKeeper, keeper, bankKeeper := k.AccountKeeper, k.StakingKeeper, k.ContractKeeper, k.BankKeeper - - valAddr := addValidator(t, ctx, stakingKeeper, k.Faucet, sdk.NewInt64Coin("stake", 1234567)) - ctx = nextBlock(ctx, stakingKeeper) - v, found := stakingKeeper.GetValidator(ctx, valAddr) - assert.True(t, found) - assert.Equal(t, v.GetDelegatorShares(), sdk.NewDec(1234567)) - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000), sdk.NewInt64Coin("stake", 500000)) - creator := k.Faucet.NewFundedRandomAccount(ctx, deposit...) - - // upload staking derivates code - stakingCode, err := os.ReadFile("./testdata/staking.wasm") - require.NoError(t, err) - stakingID, _, err := keeper.Create(ctx, creator, stakingCode, nil) - require.NoError(t, err) - require.Equal(t, uint64(1), stakingID) - - // register to a valid address - initMsg := StakingInitMsg{ - Name: "Staking Derivatives", - Symbol: "DRV", - Decimals: 0, - Validator: valAddr, - ExitTax: sdk.MustNewDecFromStr("0.10"), - MinWithdrawl: "100", - } - initBz, err := json.Marshal(&initMsg) - require.NoError(t, err) - - stakingAddr, _, err := k.ContractKeeper.Instantiate(ctx, stakingID, creator, nil, initBz, "staking derivates - DRV", nil) - require.NoError(t, err) - require.NotEmpty(t, stakingAddr) - - // nothing spent here - checkAccount(t, ctx, accKeeper, bankKeeper, creator, deposit) - - // try to register with a validator not on the list and it fails - _, bob := keyPubAddr() - badInitMsg := StakingInitMsg{ - Name: "Missing Validator", - Symbol: "MISS", - Decimals: 0, - Validator: sdk.ValAddress(bob), - ExitTax: sdk.MustNewDecFromStr("0.10"), - MinWithdrawl: "100", - } - badBz, err := json.Marshal(&badInitMsg) - require.NoError(t, err) - - _, _, err = k.ContractKeeper.Instantiate(ctx, stakingID, creator, nil, badBz, "missing validator", nil) - require.Error(t, err) - - // no changes to bonding shares - val, _ := stakingKeeper.GetValidator(ctx, valAddr) - assert.Equal(t, val.GetDelegatorShares(), sdk.NewDec(1234567)) -} - -type initInfo struct { - valAddr sdk.ValAddress - creator sdk.AccAddress - contractAddr sdk.AccAddress - - ctx sdk.Context - accKeeper authkeeper.AccountKeeper - stakingKeeper *stakingkeeper.Keeper - distKeeper distributionkeeper.Keeper - wasmKeeper Keeper - contractKeeper wasmtypes.ContractOpsKeeper - bankKeeper bankkeeper.Keeper - faucet *TestFaucet -} - -func initializeStaking(t *testing.T) initInfo { - ctx, k := CreateTestInput(t, false, AvailableCapabilities) - accKeeper, stakingKeeper, keeper, bankKeeper := k.AccountKeeper, k.StakingKeeper, k.WasmKeeper, k.BankKeeper - - valAddr := addValidator(t, ctx, stakingKeeper, k.Faucet, sdk.NewInt64Coin("stake", 1000000)) - ctx = nextBlock(ctx, stakingKeeper) - - v, found := stakingKeeper.GetValidator(ctx, valAddr) - assert.True(t, found) - assert.Equal(t, v.GetDelegatorShares(), sdk.NewDec(1000000)) - assert.Equal(t, v.Status, stakingtypes.Bonded) - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000), sdk.NewInt64Coin("stake", 500000)) - creator := k.Faucet.NewFundedRandomAccount(ctx, deposit...) - - // upload staking derivates code - stakingCode, err := os.ReadFile("./testdata/staking.wasm") - require.NoError(t, err) - stakingID, _, err := k.ContractKeeper.Create(ctx, creator, stakingCode, nil) - require.NoError(t, err) - require.Equal(t, uint64(1), stakingID) - - // register to a valid address - initMsg := StakingInitMsg{ - Name: "Staking Derivatives", - Symbol: "DRV", - Decimals: 0, - Validator: valAddr, - ExitTax: sdk.MustNewDecFromStr("0.10"), - MinWithdrawl: "100", - } - initBz, err := json.Marshal(&initMsg) - require.NoError(t, err) - - stakingAddr, _, err := k.ContractKeeper.Instantiate(ctx, stakingID, creator, nil, initBz, "staking derivates - DRV", nil) - require.NoError(t, err) - require.NotEmpty(t, stakingAddr) - - return initInfo{ - valAddr: valAddr, - creator: creator, - contractAddr: stakingAddr, - ctx: ctx, - accKeeper: accKeeper, - stakingKeeper: stakingKeeper, - wasmKeeper: *keeper, - distKeeper: k.DistKeeper, - bankKeeper: bankKeeper, - contractKeeper: k.ContractKeeper, - faucet: k.Faucet, - } -} - -func TestBonding(t *testing.T) { - initInfo := initializeStaking(t) - ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr - keeper, stakingKeeper, accKeeper, bankKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper, initInfo.accKeeper, initInfo.bankKeeper - - // initial checks of bonding state - val, found := stakingKeeper.GetValidator(ctx, valAddr) - require.True(t, found) - initPower := val.GetDelegatorShares() - - // bob has 160k, putting 80k into the contract - full := sdk.NewCoins(sdk.NewInt64Coin("stake", 160000)) - funds := sdk.NewCoins(sdk.NewInt64Coin("stake", 80000)) - bob := initInfo.faucet.NewFundedRandomAccount(ctx, full...) - - // check contract state before - assertBalance(t, ctx, keeper, contractAddr, bob, "0") - assertClaims(t, ctx, keeper, contractAddr, bob, "0") - assertSupply(t, ctx, keeper, contractAddr, "0", sdk.NewInt64Coin("stake", 0)) - - bond := StakingHandleMsg{ - Bond: &struct{}{}, - } - bondBz, err := json.Marshal(bond) - require.NoError(t, err) - _, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, bondBz, funds) - require.NoError(t, err) - - // check some account values - the money is on neither account (cuz it is bonded) - checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.Coins{}) - checkAccount(t, ctx, accKeeper, bankKeeper, bob, funds) - - // make sure the proper number of tokens have been bonded - val, _ = stakingKeeper.GetValidator(ctx, valAddr) - finalPower := val.GetDelegatorShares() - assert.Equal(t, sdk.NewInt(80000), finalPower.Sub(initPower).TruncateInt()) - - // check the delegation itself - d, found := stakingKeeper.GetDelegation(ctx, contractAddr, valAddr) - require.True(t, found) - assert.Equal(t, d.Shares, sdk.MustNewDecFromStr("80000")) - - // check we have the desired balance - assertBalance(t, ctx, keeper, contractAddr, bob, "80000") - assertClaims(t, ctx, keeper, contractAddr, bob, "0") - assertSupply(t, ctx, keeper, contractAddr, "80000", sdk.NewInt64Coin("stake", 80000)) -} - -func TestUnbonding(t *testing.T) { - initInfo := initializeStaking(t) - ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr - keeper, stakingKeeper, accKeeper, bankKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper, initInfo.accKeeper, initInfo.bankKeeper - - // initial checks of bonding state - val, found := stakingKeeper.GetValidator(ctx, valAddr) - require.True(t, found) - initPower := val.GetDelegatorShares() - - // bob has 160k, putting 80k into the contract - full := sdk.NewCoins(sdk.NewInt64Coin("stake", 160000)) - funds := sdk.NewCoins(sdk.NewInt64Coin("stake", 80000)) - bob := initInfo.faucet.NewFundedRandomAccount(ctx, full...) - - bond := StakingHandleMsg{ - Bond: &struct{}{}, - } - bondBz, err := json.Marshal(bond) - require.NoError(t, err) - _, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, bondBz, funds) - require.NoError(t, err) - - // update height a bit - ctx = nextBlock(ctx, stakingKeeper) - - // now unbond 30k - note that 3k (10%) goes to the owner as a tax, 27k unbonded and available as claims - unbond := StakingHandleMsg{ - Unbond: &unbondPayload{ - Amount: "30000", - }, - } - unbondBz, err := json.Marshal(unbond) - require.NoError(t, err) - _, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, unbondBz, nil) - require.NoError(t, err) - - // check some account values - the money is on neither account (cuz it is bonded) - // Note: why is this immediate? just test setup? - checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.Coins{}) - checkAccount(t, ctx, accKeeper, bankKeeper, bob, funds) - - // make sure the proper number of tokens have been bonded (80k - 27k = 53k) - val, _ = stakingKeeper.GetValidator(ctx, valAddr) - finalPower := val.GetDelegatorShares() - assert.Equal(t, sdk.NewInt(53000), finalPower.Sub(initPower).TruncateInt(), finalPower.String()) - - // check the delegation itself - d, found := stakingKeeper.GetDelegation(ctx, contractAddr, valAddr) - require.True(t, found) - assert.Equal(t, d.Shares, sdk.MustNewDecFromStr("53000")) - - // check there is unbonding in progress - un, found := stakingKeeper.GetUnbondingDelegation(ctx, contractAddr, valAddr) - require.True(t, found) - require.Equal(t, 1, len(un.Entries)) - assert.Equal(t, "27000", un.Entries[0].Balance.String()) - - // check we have the desired balance - assertBalance(t, ctx, keeper, contractAddr, bob, "50000") - assertBalance(t, ctx, keeper, contractAddr, initInfo.creator, "3000") - assertClaims(t, ctx, keeper, contractAddr, bob, "27000") - assertSupply(t, ctx, keeper, contractAddr, "53000", sdk.NewInt64Coin("stake", 53000)) -} - -func TestReinvest(t *testing.T) { - initInfo := initializeStaking(t) - ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr - keeper, stakingKeeper, accKeeper, bankKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper, initInfo.accKeeper, initInfo.bankKeeper - distKeeper := initInfo.distKeeper - - // initial checks of bonding state - val, found := stakingKeeper.GetValidator(ctx, valAddr) - require.True(t, found) - initPower := val.GetDelegatorShares() - assert.Equal(t, val.Tokens, sdk.NewInt(1000000), "%s", val.Tokens) - - // full is 2x funds, 1x goes to the contract, other stays on his wallet - full := sdk.NewCoins(sdk.NewInt64Coin("stake", 400000)) - funds := sdk.NewCoins(sdk.NewInt64Coin("stake", 200000)) - bob := initInfo.faucet.NewFundedRandomAccount(ctx, full...) - - // we will stake 200k to a validator with 1M self-bond - // this means we should get 1/6 of the rewards - bond := StakingHandleMsg{ - Bond: &struct{}{}, - } - bondBz, err := json.Marshal(bond) - require.NoError(t, err) - _, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, bondBz, funds) - require.NoError(t, err) - - // update height a bit to solidify the delegation - ctx = nextBlock(ctx, stakingKeeper) - // we get 1/6, our share should be 40k minus 10% commission = 36k - setValidatorRewards(ctx, stakingKeeper, distKeeper, valAddr, "240000") - - // this should withdraw our outstanding 36k of rewards and reinvest them in the same delegation - reinvest := StakingHandleMsg{ - Reinvest: &struct{}{}, - } - reinvestBz, err := json.Marshal(reinvest) - require.NoError(t, err) - _, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, reinvestBz, nil) - require.NoError(t, err) - - // check some account values - the money is on neither account (cuz it is bonded) - // Note: why is this immediate? just test setup? - checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.Coins{}) - checkAccount(t, ctx, accKeeper, bankKeeper, bob, funds) - - // check the delegation itself - d, found := stakingKeeper.GetDelegation(ctx, contractAddr, valAddr) - require.True(t, found) - // we started with 200k and added 36k - assert.Equal(t, d.Shares, sdk.MustNewDecFromStr("236000")) - - // make sure the proper number of tokens have been bonded (80k + 40k = 120k) - val, _ = stakingKeeper.GetValidator(ctx, valAddr) - finalPower := val.GetDelegatorShares() - assert.Equal(t, sdk.NewInt(236000), finalPower.Sub(initPower).TruncateInt(), finalPower.String()) - - // check there is no unbonding in progress - un, found := stakingKeeper.GetUnbondingDelegation(ctx, contractAddr, valAddr) - assert.False(t, found, "%#v", un) - - // check we have the desired balance - assertBalance(t, ctx, keeper, contractAddr, bob, "200000") - assertBalance(t, ctx, keeper, contractAddr, initInfo.creator, "0") - assertClaims(t, ctx, keeper, contractAddr, bob, "0") - assertSupply(t, ctx, keeper, contractAddr, "200000", sdk.NewInt64Coin("stake", 236000)) -} - -func TestQueryStakingInfo(t *testing.T) { - // STEP 1: take a lot of setup from TestReinvest so we have non-zero info - initInfo := initializeStaking(t) - ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr - keeper, stakingKeeper := initInfo.wasmKeeper, initInfo.stakingKeeper - distKeeper := initInfo.distKeeper - - // initial checks of bonding state - val, found := stakingKeeper.GetValidator(ctx, valAddr) - require.True(t, found) - assert.Equal(t, sdk.NewInt(1000000), val.Tokens) - - // full is 2x funds, 1x goes to the contract, other stays on his wallet - full := sdk.NewCoins(sdk.NewInt64Coin("stake", 400000)) - funds := sdk.NewCoins(sdk.NewInt64Coin("stake", 200000)) - bob := initInfo.faucet.NewFundedRandomAccount(ctx, full...) - - // we will stake 200k to a validator with 1M self-bond - // this means we should get 1/6 of the rewards - bond := StakingHandleMsg{ - Bond: &struct{}{}, - } - bondBz, err := json.Marshal(bond) - require.NoError(t, err) - _, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, bondBz, funds) - require.NoError(t, err) - - // update height a bit to solidify the delegation - ctx = nextBlock(ctx, stakingKeeper) - // we get 1/6, our share should be 40k minus 10% commission = 36k - setValidatorRewards(ctx, stakingKeeper, distKeeper, valAddr, "240000") - - // see what the current rewards are - origReward := distKeeper.GetValidatorCurrentRewards(ctx, valAddr) - - // STEP 2: Prepare the mask contract - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - creator := initInfo.faucet.NewFundedRandomAccount(ctx, deposit...) - - // upload mask code - maskID, _, err := initInfo.contractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(2), maskID) - - // creator instantiates a contract and gives it tokens - maskAddr, _, err := initInfo.contractKeeper.Instantiate(ctx, maskID, creator, nil, []byte("{}"), "mask contract 2", nil) - require.NoError(t, err) - require.NotEmpty(t, maskAddr) - - // STEP 3: now, let's reflect some queries. - // let's get the bonded denom - reflectBondedQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ - BondedDenom: &struct{}{}, - }}}} - reflectBondedBin := buildReflectQuery(t, &reflectBondedQuery) - res, err := keeper.QuerySmart(ctx, maskAddr, reflectBondedBin) - require.NoError(t, err) - // first we pull out the data from chain response, before parsing the original response - var reflectRes testdata.ChainResponse - mustParse(t, res, &reflectRes) - var bondedRes wasmvmtypes.BondedDenomResponse - mustParse(t, reflectRes.Data, &bondedRes) - assert.Equal(t, "stake", bondedRes.Denom) - - // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result - reflectAllValidatorsQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ - AllValidators: &wasmvmtypes.AllValidatorsQuery{}, - }}}} - reflectAllValidatorsBin := buildReflectQuery(t, &reflectAllValidatorsQuery) - res, err = keeper.QuerySmart(ctx, maskAddr, reflectAllValidatorsBin) - require.NoError(t, err) - // first we pull out the data from chain response, before parsing the original response - mustParse(t, res, &reflectRes) - var allValidatorsRes wasmvmtypes.AllValidatorsResponse - mustParse(t, reflectRes.Data, &allValidatorsRes) - require.Len(t, allValidatorsRes.Validators, 1, string(res)) - valInfo := allValidatorsRes.Validators[0] - // Note: this ValAddress not AccAddress, may change with #264 - require.Equal(t, valAddr.String(), valInfo.Address) - require.Contains(t, valInfo.Commission, "0.100") - require.Contains(t, valInfo.MaxCommission, "0.200") - require.Contains(t, valInfo.MaxChangeRate, "0.010") - - // find a validator - reflectValidatorQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ - Validator: &wasmvmtypes.ValidatorQuery{ - Address: valAddr.String(), - }, - }}}} - reflectValidatorBin := buildReflectQuery(t, &reflectValidatorQuery) - res, err = keeper.QuerySmart(ctx, maskAddr, reflectValidatorBin) - require.NoError(t, err) - // first we pull out the data from chain response, before parsing the original response - mustParse(t, res, &reflectRes) - var validatorRes wasmvmtypes.ValidatorResponse - mustParse(t, reflectRes.Data, &validatorRes) - require.NotNil(t, validatorRes.Validator) - valInfo = *validatorRes.Validator - // Note: this ValAddress not AccAddress, may change with #264 - require.Equal(t, valAddr.String(), valInfo.Address) - require.Contains(t, valInfo.Commission, "0.100") - require.Contains(t, valInfo.MaxCommission, "0.200") - require.Contains(t, valInfo.MaxChangeRate, "0.010") - - // missing validator - noVal := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address()) - reflectNoValidatorQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ - Validator: &wasmvmtypes.ValidatorQuery{ - Address: noVal.String(), - }, - }}}} - reflectNoValidatorBin := buildReflectQuery(t, &reflectNoValidatorQuery) - res, err = keeper.QuerySmart(ctx, maskAddr, reflectNoValidatorBin) - require.NoError(t, err) - // first we pull out the data from chain response, before parsing the original response - mustParse(t, res, &reflectRes) - var noValidatorRes wasmvmtypes.ValidatorResponse - mustParse(t, reflectRes.Data, &noValidatorRes) - require.Nil(t, noValidatorRes.Validator) - - // test to get all my delegations - reflectAllDelegationsQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ - AllDelegations: &wasmvmtypes.AllDelegationsQuery{ - Delegator: contractAddr.String(), - }, - }}}} - reflectAllDelegationsBin := buildReflectQuery(t, &reflectAllDelegationsQuery) - res, err = keeper.QuerySmart(ctx, maskAddr, reflectAllDelegationsBin) - require.NoError(t, err) - // first we pull out the data from chain response, before parsing the original response - mustParse(t, res, &reflectRes) - var allDelegationsRes wasmvmtypes.AllDelegationsResponse - mustParse(t, reflectRes.Data, &allDelegationsRes) - require.Len(t, allDelegationsRes.Delegations, 1) - delInfo := allDelegationsRes.Delegations[0] - // Note: this ValAddress not AccAddress, may change with #264 - require.Equal(t, valAddr.String(), delInfo.Validator) - // note this is not bob (who staked to the contract), but the contract itself - require.Equal(t, contractAddr.String(), delInfo.Delegator) - // this is a different Coin type, with String not BigInt, compare field by field - require.Equal(t, funds[0].Denom, delInfo.Amount.Denom) - require.Equal(t, funds[0].Amount.String(), delInfo.Amount.Amount) - - // test to get one delegation - reflectDelegationQuery := testdata.ReflectQueryMsg{Chain: &testdata.ChainQuery{Request: &wasmvmtypes.QueryRequest{Staking: &wasmvmtypes.StakingQuery{ - Delegation: &wasmvmtypes.DelegationQuery{ - Validator: valAddr.String(), - Delegator: contractAddr.String(), - }, - }}}} - reflectDelegationBin := buildReflectQuery(t, &reflectDelegationQuery) - res, err = keeper.QuerySmart(ctx, maskAddr, reflectDelegationBin) - require.NoError(t, err) - // first we pull out the data from chain response, before parsing the original response - mustParse(t, res, &reflectRes) - - var delegationRes wasmvmtypes.DelegationResponse - mustParse(t, reflectRes.Data, &delegationRes) - assert.NotEmpty(t, delegationRes.Delegation) - delInfo2 := delegationRes.Delegation - // Note: this ValAddress not AccAddress, may change with #264 - require.Equal(t, valAddr.String(), delInfo2.Validator) - // note this is not bob (who staked to the contract), but the contract itself - require.Equal(t, contractAddr.String(), delInfo2.Delegator) - // this is a different Coin type, with String not BigInt, compare field by field - require.Equal(t, funds[0].Denom, delInfo2.Amount.Denom) - require.Equal(t, funds[0].Amount.String(), delInfo2.Amount.Amount) - - require.Equal(t, wasmvmtypes.NewCoin(200000, "stake"), delInfo2.CanRedelegate) - require.Len(t, delInfo2.AccumulatedRewards, 1) - // see bonding above to see how we calculate 36000 (240000 / 6 - 10% commission) - require.Equal(t, wasmvmtypes.NewCoin(36000, "stake"), delInfo2.AccumulatedRewards[0]) - - // ensure rewards did not change when querying (neither amount nor period) - finalReward := distKeeper.GetValidatorCurrentRewards(ctx, valAddr) - require.Equal(t, origReward, finalReward) -} - -func TestQueryStakingPlugin(t *testing.T) { - // STEP 1: take a lot of setup from TestReinvest so we have non-zero info - initInfo := initializeStaking(t) - ctx, valAddr, contractAddr := initInfo.ctx, initInfo.valAddr, initInfo.contractAddr - stakingKeeper := initInfo.stakingKeeper - distKeeper := initInfo.distKeeper - - // initial checks of bonding state - val, found := stakingKeeper.GetValidator(ctx, valAddr) - require.True(t, found) - assert.Equal(t, sdk.NewInt(1000000), val.Tokens) - - // full is 2x funds, 1x goes to the contract, other stays on his wallet - full := sdk.NewCoins(sdk.NewInt64Coin("stake", 400000)) - funds := sdk.NewCoins(sdk.NewInt64Coin("stake", 200000)) - bob := initInfo.faucet.NewFundedRandomAccount(ctx, full...) - - // we will stake 200k to a validator with 1M self-bond - // this means we should get 1/6 of the rewards - bond := StakingHandleMsg{ - Bond: &struct{}{}, - } - bondBz, err := json.Marshal(bond) - require.NoError(t, err) - _, err = initInfo.contractKeeper.Execute(ctx, contractAddr, bob, bondBz, funds) - require.NoError(t, err) - - // update height a bit to solidify the delegation - ctx = nextBlock(ctx, stakingKeeper) - // we get 1/6, our share should be 40k minus 10% commission = 36k - setValidatorRewards(ctx, stakingKeeper, distKeeper, valAddr, "240000") - - // see what the current rewards are - origReward := distKeeper.GetValidatorCurrentRewards(ctx, valAddr) - - // Step 2: Try out the query plugins - query := wasmvmtypes.StakingQuery{ - Delegation: &wasmvmtypes.DelegationQuery{ - Delegator: contractAddr.String(), - Validator: valAddr.String(), - }, - } - raw, err := StakingQuerier(stakingKeeper, distributionkeeper.NewQuerier(distKeeper))(ctx, &query) - require.NoError(t, err) - var res wasmvmtypes.DelegationResponse - mustParse(t, raw, &res) - assert.NotEmpty(t, res.Delegation) - delInfo := res.Delegation - // Note: this ValAddress not AccAddress, may change with #264 - require.Equal(t, valAddr.String(), delInfo.Validator) - // note this is not bob (who staked to the contract), but the contract itself - require.Equal(t, contractAddr.String(), delInfo.Delegator) - // this is a different Coin type, with String not BigInt, compare field by field - require.Equal(t, funds[0].Denom, delInfo.Amount.Denom) - require.Equal(t, funds[0].Amount.String(), delInfo.Amount.Amount) - - require.Equal(t, wasmvmtypes.NewCoin(200000, "stake"), delInfo.CanRedelegate) - require.Len(t, delInfo.AccumulatedRewards, 1) - // see bonding above to see how we calculate 36000 (240000 / 6 - 10% commission) - require.Equal(t, wasmvmtypes.NewCoin(36000, "stake"), delInfo.AccumulatedRewards[0]) - - // ensure rewards did not change when querying (neither amount nor period) - finalReward := distKeeper.GetValidatorCurrentRewards(ctx, valAddr) - require.Equal(t, origReward, finalReward) -} - -// adds a few validators and returns a list of validators that are registered -func addValidator(t *testing.T, ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, faucet *TestFaucet, value sdk.Coin) sdk.ValAddress { - owner := faucet.NewFundedRandomAccount(ctx, value) - - privKey := secp256k1.GenPrivKey() - pubKey := privKey.PubKey() - addr := sdk.ValAddress(pubKey.Address()) - - pkAny, err := codectypes.NewAnyWithValue(pubKey) - require.NoError(t, err) - msg := &stakingtypes.MsgCreateValidator{ - Description: stakingtypes.Description{ - Moniker: "Validator power", - }, - Commission: stakingtypes.CommissionRates{ - Rate: sdk.MustNewDecFromStr("0.1"), - MaxRate: sdk.MustNewDecFromStr("0.2"), - MaxChangeRate: sdk.MustNewDecFromStr("0.01"), - }, - MinSelfDelegation: sdk.OneInt(), - DelegatorAddress: owner.String(), - ValidatorAddress: addr.String(), - Pubkey: pkAny, - Value: value, - } - _, err = stakingkeeper.NewMsgServerImpl(stakingKeeper).CreateValidator(sdk.WrapSDKContext(ctx), msg) - require.NoError(t, err) - return addr -} - -// this will commit the current set, update the block height and set historic info -// basically, letting two blocks pass -func nextBlock(ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper) sdk.Context { - staking.EndBlocker(ctx, stakingKeeper) - ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) - staking.BeginBlocker(ctx, stakingKeeper) - return ctx -} - -func setValidatorRewards(ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, distKeeper distributionkeeper.Keeper, valAddr sdk.ValAddress, reward string) { - // allocate some rewards - vali := stakingKeeper.Validator(ctx, valAddr) - amount, err := sdk.NewDecFromStr(reward) - if err != nil { - panic(err) - } - payout := sdk.DecCoins{{Denom: "stake", Amount: amount}} - distKeeper.AllocateTokensToValidator(ctx, vali, payout) -} - -func assertBalance(t *testing.T, ctx sdk.Context, keeper Keeper, contract sdk.AccAddress, addr sdk.AccAddress, expected string) { - query := StakingQueryMsg{ - Balance: &addressQuery{ - Address: addr, - }, - } - queryBz, err := json.Marshal(query) - require.NoError(t, err) - res, err := keeper.QuerySmart(ctx, contract, queryBz) - require.NoError(t, err) - var balance BalanceResponse - err = json.Unmarshal(res, &balance) - require.NoError(t, err) - assert.Equal(t, expected, balance.Balance) -} - -func assertClaims(t *testing.T, ctx sdk.Context, keeper Keeper, contract sdk.AccAddress, addr sdk.AccAddress, expected string) { - query := StakingQueryMsg{ - Claims: &addressQuery{ - Address: addr, - }, - } - queryBz, err := json.Marshal(query) - require.NoError(t, err) - res, err := keeper.QuerySmart(ctx, contract, queryBz) - require.NoError(t, err) - var claims ClaimsResponse - err = json.Unmarshal(res, &claims) - require.NoError(t, err) - assert.Equal(t, expected, claims.Claims) -} - -func assertSupply(t *testing.T, ctx sdk.Context, keeper Keeper, contract sdk.AccAddress, expectedIssued string, expectedBonded sdk.Coin) { - query := StakingQueryMsg{Investment: &struct{}{}} - queryBz, err := json.Marshal(query) - require.NoError(t, err) - res, err := keeper.QuerySmart(ctx, contract, queryBz) - require.NoError(t, err) - var invest InvestmentResponse - err = json.Unmarshal(res, &invest) - require.NoError(t, err) - assert.Equal(t, expectedIssued, invest.TokenSupply) - assert.Equal(t, expectedBonded, invest.StakedTokens) -} diff --git a/x/wasm/keeper/submsg_test.go b/x/wasm/keeper/submsg_test.go deleted file mode 100644 index fc14d2c..0000000 --- a/x/wasm/keeper/submsg_test.go +++ /dev/null @@ -1,552 +0,0 @@ -package keeper - -import ( - "encoding/json" - "fmt" - "os" - "strconv" - "testing" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/testdata" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -// test handing of submessages, very closely related to the reflect_test - -// Try a simple send, no gas limit to for a sanity check before trying table tests -func TestDispatchSubMsgSuccessCase(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, ReflectFeatures) - accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - creatorBalance := deposit.Sub(contractStart...) - _, fred := keyPubAddr() - - // upload code - codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - require.Equal(t, uint64(1), codeID) - - // creator instantiates a contract and gives it tokens - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) - require.NoError(t, err) - require.NotEmpty(t, contractAddr) - - // check some account values - checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, contractStart) - checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance) - checkAccount(t, ctx, accKeeper, bankKeeper, fred, nil) - - // creator can send contract's tokens to fred (using SendMsg) - msg := wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: fred.String(), - Amount: []wasmvmtypes.Coin{{ - Denom: "denom", - Amount: "15000", - }}, - }, - }, - } - reflectSend := testdata.ReflectHandleMsg{ - ReflectSubMsg: &testdata.ReflectSubPayload{ - Msgs: []wasmvmtypes.SubMsg{{ - ID: 7, - Msg: msg, - ReplyOn: wasmvmtypes.ReplyAlways, - }}, - }, - } - reflectSendBz, err := json.Marshal(reflectSend) - require.NoError(t, err) - _, err = keepers.ContractKeeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil) - require.NoError(t, err) - - // fred got coins - checkAccount(t, ctx, accKeeper, bankKeeper, fred, sdk.NewCoins(sdk.NewInt64Coin("denom", 15000))) - // contract lost them - checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 25000))) - checkAccount(t, ctx, accKeeper, bankKeeper, creator, creatorBalance) - - // query the reflect state to ensure the result was stored - query := testdata.ReflectQueryMsg{ - SubMsgResult: &testdata.SubCall{ID: 7}, - } - queryBz, err := json.Marshal(query) - require.NoError(t, err) - queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz) - require.NoError(t, err) - - var res wasmvmtypes.Reply - err = json.Unmarshal(queryRes, &res) - require.NoError(t, err) - assert.Equal(t, uint64(7), res.ID) - assert.Empty(t, res.Result.Err) - require.NotNil(t, res.Result.Ok) - sub := res.Result.Ok - assert.Empty(t, sub.Data) - // as of v0.28.0 we strip out all events that don't come from wasm contracts. can't trust the sdk. - require.Len(t, sub.Events, 0) -} - -func TestDispatchSubMsgErrorHandling(t *testing.T) { - fundedDenom := "funds" - fundedAmount := 1_000_000 - ctxGasLimit := uint64(1_000_000) - subGasLimit := uint64(300_000) - - // prep - create one chain and upload the code - ctx, keepers := CreateTestInput(t, false, ReflectFeatures) - ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) - keeper := keepers.WasmKeeper - contractStart := sdk.NewCoins(sdk.NewInt64Coin(fundedDenom, int64(fundedAmount))) - uploader := keepers.Faucet.NewFundedRandomAccount(ctx, contractStart.Add(contractStart...)...) - - // upload code - reflectID, _, err := keepers.ContractKeeper.Create(ctx, uploader, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - - // create hackatom contract for testing (for infinite loop) - hackatomCode, err := os.ReadFile("./testdata/hackatom.wasm") - require.NoError(t, err) - hackatomID, _, err := keepers.ContractKeeper.Create(ctx, uploader, hackatomCode, nil) - require.NoError(t, err) - _, bob := keyPubAddr() - _, fred := keyPubAddr() - initMsg := HackatomExampleInitMsg{ - Verifier: fred, - Beneficiary: bob, - } - initMsgBz, err := json.Marshal(initMsg) - require.NoError(t, err) - hackatomAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, hackatomID, uploader, nil, initMsgBz, "hackatom demo", contractStart) - require.NoError(t, err) - - validBankSend := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg { - return wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: emptyAccount, - Amount: []wasmvmtypes.Coin{{ - Denom: fundedDenom, - Amount: strconv.Itoa(fundedAmount / 2), - }}, - }, - }, - } - } - - invalidBankSend := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg { - return wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: emptyAccount, - Amount: []wasmvmtypes.Coin{{ - Denom: fundedDenom, - Amount: strconv.Itoa(fundedAmount * 2), - }}, - }, - }, - } - } - - infiniteLoop := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg { - return wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - Execute: &wasmvmtypes.ExecuteMsg{ - ContractAddr: hackatomAddr.String(), - Msg: []byte(`{"cpu_loop":{}}`), - }, - }, - } - } - - instantiateContract := func(contract, emptyAccount string) wasmvmtypes.CosmosMsg { - return wasmvmtypes.CosmosMsg{ - Wasm: &wasmvmtypes.WasmMsg{ - Instantiate: &wasmvmtypes.InstantiateMsg{ - CodeID: reflectID, - Msg: []byte("{}"), - Label: "subcall reflect", - }, - }, - } - } - - type assertion func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) - - assertReturnedEvents := func(expectedEvents int) assertion { - return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) { - require.Len(t, response.Ok.Events, expectedEvents) - } - } - - assertGasUsed := func(minGas, maxGas uint64) assertion { - return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) { - gasUsed := ctx.GasMeter().GasConsumed() - assert.True(t, gasUsed >= minGas, "Used %d gas (less than expected %d)", gasUsed, minGas) - assert.True(t, gasUsed <= maxGas, "Used %d gas (more than expected %d)", gasUsed, maxGas) - } - } - - assertErrorString := func(shouldContain string) assertion { - return func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) { - assert.Contains(t, response.Err, shouldContain) - } - } - - assertGotContractAddr := func(t *testing.T, ctx sdk.Context, contract, emptyAccount string, response wasmvmtypes.SubMsgResult) { - // should get the events emitted on new contract - event := response.Ok.Events[0] - require.Equal(t, event.Type, "instantiate") - assert.Equal(t, event.Attributes[0].Key, "_contract_address") - eventAddr := event.Attributes[0].Value - assert.NotEqual(t, contract, eventAddr) - - var res types.MsgInstantiateContractResponse - keepers.EncodingConfig.Marshaler.MustUnmarshal(response.Ok.Data, &res) - assert.Equal(t, eventAddr, res.Address) - } - - cases := map[string]struct { - submsgID uint64 - // we will generate message from the - msg func(contract, emptyAccount string) wasmvmtypes.CosmosMsg - gasLimit *uint64 - - // true if we expect this to throw out of gas panic - isOutOfGasPanic bool - // true if we expect this execute to return an error (can be false when submessage errors) - executeError bool - // true if we expect submessage to return an error (but execute to return success) - subMsgError bool - // make assertions after dispatch - resultAssertions []assertion - }{ - "send tokens": { - submsgID: 5, - msg: validBankSend, - resultAssertions: []assertion{assertReturnedEvents(0), assertGasUsed(102000, 103000)}, - }, - "not enough tokens": { - submsgID: 6, - msg: invalidBankSend, - subMsgError: true, - // uses less gas than the send tokens (cost of bank transfer) - resultAssertions: []assertion{assertGasUsed(76000, 79000), assertErrorString("codespace: sdk, code: 5")}, - }, - "out of gas panic with no gas limit": { - submsgID: 7, - msg: infiniteLoop, - isOutOfGasPanic: true, - }, - - "send tokens with limit": { - submsgID: 15, - msg: validBankSend, - gasLimit: &subGasLimit, - // uses same gas as call without limit (note we do not charge the 40k on reply) - resultAssertions: []assertion{assertReturnedEvents(0), assertGasUsed(102000, 103000)}, - }, - "not enough tokens with limit": { - submsgID: 16, - msg: invalidBankSend, - subMsgError: true, - gasLimit: &subGasLimit, - // uses same gas as call without limit (note we do not charge the 40k on reply) - resultAssertions: []assertion{assertGasUsed(77700, 77800), assertErrorString("codespace: sdk, code: 5")}, - }, - "out of gas caught with gas limit": { - submsgID: 17, - msg: infiniteLoop, - subMsgError: true, - gasLimit: &subGasLimit, - // uses all the subGasLimit, plus the 52k or so for the main contract - resultAssertions: []assertion{assertGasUsed(subGasLimit+73000, subGasLimit+74000), assertErrorString("codespace: sdk, code: 11")}, - }, - "instantiate contract gets address in data and events": { - submsgID: 21, - msg: instantiateContract, - resultAssertions: []assertion{assertReturnedEvents(1), assertGotContractAddr}, - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - creator := keepers.Faucet.NewFundedRandomAccount(ctx, contractStart...) - _, empty := keyPubAddr() - - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), fmt.Sprintf("contract %s", name), contractStart) - require.NoError(t, err) - - msg := tc.msg(contractAddr.String(), empty.String()) - reflectSend := testdata.ReflectHandleMsg{ - ReflectSubMsg: &testdata.ReflectSubPayload{ - Msgs: []wasmvmtypes.SubMsg{{ - ID: tc.submsgID, - Msg: msg, - GasLimit: tc.gasLimit, - ReplyOn: wasmvmtypes.ReplyAlways, - }}, - }, - } - reflectSendBz, err := json.Marshal(reflectSend) - require.NoError(t, err) - - execCtx := ctx.WithGasMeter(sdk.NewGasMeter(ctxGasLimit)) - defer func() { - if tc.isOutOfGasPanic { - r := recover() - require.NotNil(t, r, "expected panic") - if _, ok := r.(sdk.ErrorOutOfGas); !ok { - t.Fatalf("Expected OutOfGas panic, got: %#v\n", r) - } - } - }() - _, err = keepers.ContractKeeper.Execute(execCtx, contractAddr, creator, reflectSendBz, nil) - - if tc.executeError { - require.Error(t, err) - } else { - require.NoError(t, err) - - // query the reply - query := testdata.ReflectQueryMsg{ - SubMsgResult: &testdata.SubCall{ID: tc.submsgID}, - } - queryBz, err := json.Marshal(query) - require.NoError(t, err) - queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz) - require.NoError(t, err) - var res wasmvmtypes.Reply - err = json.Unmarshal(queryRes, &res) - require.NoError(t, err) - assert.Equal(t, tc.submsgID, res.ID) - - if tc.subMsgError { - require.NotEmpty(t, res.Result.Err) - require.Nil(t, res.Result.Ok) - } else { - require.Empty(t, res.Result.Err) - require.NotNil(t, res.Result.Ok) - } - - for _, assertion := range tc.resultAssertions { - assertion(t, execCtx, contractAddr.String(), empty.String(), res.Result) - } - - } - }) - } -} - -// Test an error case, where the Encoded doesn't return any sdk.Msg and we trigger(ed) a null pointer exception. -// This occurs with the IBC encoder. Test this. -func TestDispatchSubMsgEncodeToNoSdkMsg(t *testing.T) { - // fake out the bank handle to return success with no data - nilEncoder := func(sender sdk.AccAddress, msg *wasmvmtypes.BankMsg) ([]sdk.Msg, error) { - return nil, nil - } - customEncoders := &MessageEncoders{ - Bank: nilEncoder, - } - - ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageHandler(NewSDKMessageHandler(nil, customEncoders))) - keeper := keepers.WasmKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - _, fred := keyPubAddr() - - // upload code - codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - - // creator instantiates a contract and gives it tokens - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) - require.NoError(t, err) - require.NotEmpty(t, contractAddr) - - // creator can send contract's tokens to fred (using SendMsg) - msg := wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: fred.String(), - Amount: []wasmvmtypes.Coin{{ - Denom: "denom", - Amount: "15000", - }}, - }, - }, - } - reflectSend := testdata.ReflectHandleMsg{ - ReflectSubMsg: &testdata.ReflectSubPayload{ - Msgs: []wasmvmtypes.SubMsg{{ - ID: 7, - Msg: msg, - ReplyOn: wasmvmtypes.ReplyAlways, - }}, - }, - } - reflectSendBz, err := json.Marshal(reflectSend) - require.NoError(t, err) - _, err = keepers.ContractKeeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil) - require.NoError(t, err) - - // query the reflect state to ensure the result was stored - query := testdata.ReflectQueryMsg{ - SubMsgResult: &testdata.SubCall{ID: 7}, - } - queryBz, err := json.Marshal(query) - require.NoError(t, err) - queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz) - require.NoError(t, err) - - var res wasmvmtypes.Reply - err = json.Unmarshal(queryRes, &res) - require.NoError(t, err) - assert.Equal(t, uint64(7), res.ID) - assert.Empty(t, res.Result.Err) - require.NotNil(t, res.Result.Ok) - sub := res.Result.Ok - assert.Empty(t, sub.Data) - require.Len(t, sub.Events, 0) -} - -// Try a simple send, no gas limit to for a sanity check before trying table tests -func TestDispatchSubMsgConditionalReplyOn(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, ReflectFeatures) - keeper := keepers.WasmKeeper - - deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) - contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) - - creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...) - _, fred := keyPubAddr() - - // upload code - codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) - require.NoError(t, err) - - // creator instantiates a contract and gives it tokens - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) - require.NoError(t, err) - - goodSend := wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: fred.String(), - Amount: []wasmvmtypes.Coin{{ - Denom: "denom", - Amount: "1000", - }}, - }, - }, - } - failSend := wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: fred.String(), - Amount: []wasmvmtypes.Coin{{ - Denom: "no-such-token", - Amount: "777777", - }}, - }, - }, - } - - cases := map[string]struct { - // true for wasmvmtypes.ReplySuccess, false for wasmvmtypes.ReplyError - replyOnSuccess bool - msg wasmvmtypes.CosmosMsg - // true if the call should return an error (it wasn't handled) - expectError bool - // true if the reflect contract wrote the response (success or error) - it was captured - writeResult bool - }{ - "all good, reply success": { - replyOnSuccess: true, - msg: goodSend, - expectError: false, - writeResult: true, - }, - "all good, reply error": { - replyOnSuccess: false, - msg: goodSend, - expectError: false, - writeResult: false, - }, - "bad msg, reply success": { - replyOnSuccess: true, - msg: failSend, - expectError: true, - writeResult: false, - }, - "bad msg, reply error": { - replyOnSuccess: false, - msg: failSend, - expectError: false, - writeResult: true, - }, - } - - var id uint64 - for name, tc := range cases { - id++ - t.Run(name, func(t *testing.T) { - subMsg := wasmvmtypes.SubMsg{ - ID: id, - Msg: tc.msg, - ReplyOn: wasmvmtypes.ReplySuccess, - } - if !tc.replyOnSuccess { - subMsg.ReplyOn = wasmvmtypes.ReplyError - } - - reflectSend := testdata.ReflectHandleMsg{ - ReflectSubMsg: &testdata.ReflectSubPayload{ - Msgs: []wasmvmtypes.SubMsg{subMsg}, - }, - } - reflectSendBz, err := json.Marshal(reflectSend) - require.NoError(t, err) - _, err = keepers.ContractKeeper.Execute(ctx, contractAddr, creator, reflectSendBz, nil) - - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - - // query the reflect state to check if the result was stored - query := testdata.ReflectQueryMsg{ - SubMsgResult: &testdata.SubCall{ID: id}, - } - queryBz, err := json.Marshal(query) - require.NoError(t, err) - queryRes, err := keeper.QuerySmart(ctx, contractAddr, queryBz) - if tc.writeResult { - // we got some data for this call - require.NoError(t, err) - var res wasmvmtypes.Reply - err = json.Unmarshal(queryRes, &res) - require.NoError(t, err) - require.Equal(t, id, res.ID) - } else { - // nothing should be there -> error - require.Error(t, err) - } - }) - } -} diff --git a/x/wasm/keeper/test_common.go b/x/wasm/keeper/test_common.go deleted file mode 100644 index 7b7f9c7..0000000 --- a/x/wasm/keeper/test_common.go +++ /dev/null @@ -1,816 +0,0 @@ -package keeper - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "os" - "testing" - "time" - - "github.com/terpnetwork/terp-core/x/wasm/keeper/testdata" - - icacontrollertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types" - icahosttypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/host/types" - - errorsmod "cosmossdk.io/errors" - - dbm "github.com/cometbft/cometbft-db" - "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" - "github.com/cometbft/cometbft/libs/log" - "github.com/cometbft/cometbft/libs/rand" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/std" - "github.com/cosmos/cosmos-sdk/store" - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/address" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/auth" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/auth/vesting" - authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" - "github.com/cosmos/cosmos-sdk/x/bank" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/cosmos/cosmos-sdk/x/capability" - capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - "github.com/cosmos/cosmos-sdk/x/crisis" - crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" - "github.com/cosmos/cosmos-sdk/x/distribution" - distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" - distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" - "github.com/cosmos/cosmos-sdk/x/evidence" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - "github.com/cosmos/cosmos-sdk/x/feegrant" - "github.com/cosmos/cosmos-sdk/x/gov" - govclient "github.com/cosmos/cosmos-sdk/x/gov/client" - govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - "github.com/cosmos/cosmos-sdk/x/mint" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" - "github.com/cosmos/cosmos-sdk/x/params" - paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" - paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" - paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" - paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal" - "github.com/cosmos/cosmos-sdk/x/slashing" - slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" - "github.com/cosmos/cosmos-sdk/x/staking" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/cosmos/cosmos-sdk/x/upgrade" - upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client" - upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" - upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer" - ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - ibc "github.com/cosmos/ibc-go/v7/modules/core" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" - ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper" - "github.com/stretchr/testify/require" - - wasmappparams "github.com/terpnetwork/terp-core/app/params" - "github.com/terpnetwork/terp-core/x/wasm/keeper/wasmtesting" - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var moduleBasics = module.NewBasicManager( - auth.AppModuleBasic{}, - bank.AppModuleBasic{}, - capability.AppModuleBasic{}, - staking.AppModuleBasic{}, - mint.AppModuleBasic{}, - distribution.AppModuleBasic{}, - gov.NewAppModuleBasic([]govclient.ProposalHandler{ - paramsclient.ProposalHandler, - upgradeclient.LegacyProposalHandler, - upgradeclient.LegacyCancelProposalHandler, - }), - params.AppModuleBasic{}, - crisis.AppModuleBasic{}, - slashing.AppModuleBasic{}, - ibc.AppModuleBasic{}, - upgrade.AppModuleBasic{}, - evidence.AppModuleBasic{}, - transfer.AppModuleBasic{}, - vesting.AppModuleBasic{}, -) - -func MakeTestCodec(t testing.TB) codec.Codec { - return MakeEncodingConfig(t).Marshaler -} - -func MakeEncodingConfig(_ testing.TB) wasmappparams.EncodingConfig { - encodingConfig := wasmappparams.MakeEncodingConfig() - amino := encodingConfig.Amino - interfaceRegistry := encodingConfig.InterfaceRegistry - - std.RegisterInterfaces(interfaceRegistry) - std.RegisterLegacyAminoCodec(amino) - - moduleBasics.RegisterLegacyAminoCodec(amino) - moduleBasics.RegisterInterfaces(interfaceRegistry) - // add wasmd types - types.RegisterInterfaces(interfaceRegistry) - types.RegisterLegacyAminoCodec(amino) - - return encodingConfig -} - -var TestingStakeParams = stakingtypes.Params{ - UnbondingTime: 100, - MaxValidators: 10, - MaxEntries: 10, - HistoricalEntries: 10, - BondDenom: "stake", - MinCommissionRate: stakingtypes.DefaultMinCommissionRate, -} - -type TestFaucet struct { - t testing.TB - bankKeeper bankkeeper.Keeper - sender sdk.AccAddress - balance sdk.Coins - minterModuleName string -} - -func NewTestFaucet(t testing.TB, ctx sdk.Context, bankKeeper bankkeeper.Keeper, minterModuleName string, initialAmount ...sdk.Coin) *TestFaucet { - require.NotEmpty(t, initialAmount) - r := &TestFaucet{t: t, bankKeeper: bankKeeper, minterModuleName: minterModuleName} - _, addr := keyPubAddr() - r.sender = addr - r.Mint(ctx, addr, initialAmount...) - r.balance = initialAmount - return r -} - -func (f *TestFaucet) Mint(parentCtx sdk.Context, addr sdk.AccAddress, amounts ...sdk.Coin) { - require.NotEmpty(f.t, amounts) - ctx := parentCtx.WithEventManager(sdk.NewEventManager()) // discard all faucet related events - err := f.bankKeeper.MintCoins(ctx, f.minterModuleName, amounts) - require.NoError(f.t, err) - err = f.bankKeeper.SendCoinsFromModuleToAccount(ctx, f.minterModuleName, addr, amounts) - require.NoError(f.t, err) - f.balance = f.balance.Add(amounts...) -} - -func (f *TestFaucet) Fund(parentCtx sdk.Context, receiver sdk.AccAddress, amounts ...sdk.Coin) { - require.NotEmpty(f.t, amounts) - // ensure faucet is always filled - if !f.balance.IsAllGTE(amounts) { - f.Mint(parentCtx, f.sender, amounts...) - } - ctx := parentCtx.WithEventManager(sdk.NewEventManager()) // discard all faucet related events - err := f.bankKeeper.SendCoins(ctx, f.sender, receiver, amounts) - require.NoError(f.t, err) - f.balance = f.balance.Sub(amounts...) -} - -func (f *TestFaucet) NewFundedRandomAccount(ctx sdk.Context, amounts ...sdk.Coin) sdk.AccAddress { - _, addr := keyPubAddr() - f.Fund(ctx, addr, amounts...) - return addr -} - -type TestKeepers struct { - AccountKeeper authkeeper.AccountKeeper - StakingKeeper *stakingkeeper.Keeper - DistKeeper distributionkeeper.Keeper - BankKeeper bankkeeper.Keeper - GovKeeper *govkeeper.Keeper - ContractKeeper types.ContractOpsKeeper - WasmKeeper *Keeper - IBCKeeper *ibckeeper.Keeper - Router MessageRouter - EncodingConfig wasmappparams.EncodingConfig - Faucet *TestFaucet - MultiStore sdk.CommitMultiStore - ScopedWasmKeeper capabilitykeeper.ScopedKeeper - WasmStoreKey *storetypes.KVStoreKey -} - -// CreateDefaultTestInput common settings for CreateTestInput -func CreateDefaultTestInput(t testing.TB) (sdk.Context, TestKeepers) { - return CreateTestInput(t, false, "staking") -} - -// CreateTestInput encoders can be nil to accept the defaults, or set it to override some of the message handlers (like default) -func CreateTestInput(t testing.TB, isCheckTx bool, availableCapabilities string, opts ...Option) (sdk.Context, TestKeepers) { - // Load default wasm config - return createTestInput(t, isCheckTx, availableCapabilities, types.DefaultWasmConfig(), dbm.NewMemDB(), opts...) -} - -// encoders can be nil to accept the defaults, or set it to override some of the message handlers (like default) -func createTestInput( - t testing.TB, - isCheckTx bool, - availableCapabilities string, - wasmConfig types.WasmConfig, - db dbm.DB, - opts ...Option, -) (sdk.Context, TestKeepers) { - tempDir := t.TempDir() - - keys := sdk.NewKVStoreKeys( - authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, - minttypes.StoreKey, distributiontypes.StoreKey, slashingtypes.StoreKey, - govtypes.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, - evidencetypes.StoreKey, ibctransfertypes.StoreKey, - capabilitytypes.StoreKey, feegrant.StoreKey, authzkeeper.StoreKey, - types.StoreKey, - ) - ms := store.NewCommitMultiStore(db) - for _, v := range keys { - ms.MountStoreWithDB(v, storetypes.StoreTypeIAVL, db) - } - tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey) - for _, v := range tkeys { - ms.MountStoreWithDB(v, storetypes.StoreTypeTransient, db) - } - - memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) - for _, v := range memKeys { - ms.MountStoreWithDB(v, storetypes.StoreTypeMemory, db) - } - - require.NoError(t, ms.LoadLatestVersion()) - - ctx := sdk.NewContext(ms, tmproto.Header{ - Height: 1234567, - Time: time.Date(2020, time.April, 22, 12, 0, 0, 0, time.UTC), - }, isCheckTx, log.NewNopLogger()) - ctx = types.WithTXCounter(ctx, 0) - - encodingConfig := MakeEncodingConfig(t) - appCodec, legacyAmino := encodingConfig.Marshaler, encodingConfig.Amino - - paramsKeeper := paramskeeper.NewKeeper( - appCodec, - legacyAmino, - keys[paramstypes.StoreKey], - tkeys[paramstypes.TStoreKey], - ) - for _, m := range []string{ - authtypes.ModuleName, - banktypes.ModuleName, - stakingtypes.ModuleName, - minttypes.ModuleName, - distributiontypes.ModuleName, - slashingtypes.ModuleName, - crisistypes.ModuleName, - ibctransfertypes.ModuleName, - capabilitytypes.ModuleName, - ibcexported.ModuleName, - govtypes.ModuleName, - types.ModuleName, - } { - paramsKeeper.Subspace(m) - } - subspace := func(m string) paramstypes.Subspace { - r, ok := paramsKeeper.GetSubspace(m) - require.True(t, ok) - - var keyTable paramstypes.KeyTable - switch r.Name() { - case authtypes.ModuleName: - keyTable = authtypes.ParamKeyTable() //nolint:staticcheck - case banktypes.ModuleName: - keyTable = banktypes.ParamKeyTable() //nolint:staticcheck - case stakingtypes.ModuleName: - keyTable = stakingtypes.ParamKeyTable() - case minttypes.ModuleName: - keyTable = minttypes.ParamKeyTable() //nolint:staticcheck - case distributiontypes.ModuleName: - keyTable = distributiontypes.ParamKeyTable() //nolint:staticcheck - case slashingtypes.ModuleName: - keyTable = slashingtypes.ParamKeyTable() //nolint:staticcheck - case govtypes.ModuleName: - keyTable = govv1.ParamKeyTable() //nolint:staticcheck - case crisistypes.ModuleName: - keyTable = crisistypes.ParamKeyTable() //nolint:staticcheck - // ibc types - case ibctransfertypes.ModuleName: - keyTable = ibctransfertypes.ParamKeyTable() - case icahosttypes.SubModuleName: - keyTable = icahosttypes.ParamKeyTable() - case icacontrollertypes.SubModuleName: - keyTable = icacontrollertypes.ParamKeyTable() - // wasm - case types.ModuleName: - keyTable = types.ParamKeyTable() //nolint:staticcheck - default: - return r - } - - if !r.HasKeyTable() { - r = r.WithKeyTable(keyTable) - } - return r - } - maccPerms := map[string][]string{ // module account permissions - authtypes.FeeCollectorName: nil, - distributiontypes.ModuleName: nil, - minttypes.ModuleName: {authtypes.Minter}, - stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, - stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, - govtypes.ModuleName: {authtypes.Burner}, - ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - types.ModuleName: {authtypes.Burner}, - } - accountKeeper := authkeeper.NewAccountKeeper( - appCodec, - keys[authtypes.StoreKey], // target store - authtypes.ProtoBaseAccount, // prototype - maccPerms, - sdk.Bech32MainPrefix, - authtypes.NewModuleAddress(authtypes.ModuleName).String(), - ) - blockedAddrs := make(map[string]bool) - for acc := range maccPerms { - blockedAddrs[authtypes.NewModuleAddress(acc).String()] = true - } - - require.NoError(t, accountKeeper.SetParams(ctx, authtypes.DefaultParams())) - - bankKeeper := bankkeeper.NewBaseKeeper( - appCodec, - keys[banktypes.StoreKey], - accountKeeper, - blockedAddrs, - authtypes.NewModuleAddress(banktypes.ModuleName).String(), - ) - require.NoError(t, bankKeeper.SetParams(ctx, banktypes.DefaultParams())) - - stakingKeeper := stakingkeeper.NewKeeper( - appCodec, - keys[stakingtypes.StoreKey], - accountKeeper, - bankKeeper, - authtypes.NewModuleAddress(stakingtypes.ModuleName).String(), - ) - stakingtypes.DefaultParams() - require.NoError(t, stakingKeeper.SetParams(ctx, TestingStakeParams)) - - distKeeper := distributionkeeper.NewKeeper( - appCodec, - keys[distributiontypes.StoreKey], - accountKeeper, - bankKeeper, - stakingKeeper, - authtypes.FeeCollectorName, - authtypes.NewModuleAddress(distributiontypes.ModuleName).String(), - ) - require.NoError(t, distKeeper.SetParams(ctx, distributiontypes.DefaultParams())) - stakingKeeper.SetHooks(distKeeper.Hooks()) - - // set genesis items required for distribution - distKeeper.SetFeePool(ctx, distributiontypes.InitialFeePool()) - - upgradeKeeper := upgradekeeper.NewKeeper( - map[int64]bool{}, - keys[upgradetypes.StoreKey], - appCodec, - tempDir, - nil, - authtypes.NewModuleAddress(upgradetypes.ModuleName).String(), - ) - - faucet := NewTestFaucet(t, ctx, bankKeeper, minttypes.ModuleName, sdk.NewCoin("stake", sdk.NewInt(100_000_000_000))) - - // set some funds ot pay out validatores, based on code from: - // https://github.com/cosmos/cosmos-sdk/blob/fea231556aee4d549d7551a6190389c4328194eb/x/distribution/keeper/keeper_test.go#L50-L57 - distrAcc := distKeeper.GetDistributionAccount(ctx) - faucet.Fund(ctx, distrAcc.GetAddress(), sdk.NewCoin("stake", sdk.NewInt(2000000))) - accountKeeper.SetModuleAccount(ctx, distrAcc) - - capabilityKeeper := capabilitykeeper.NewKeeper( - appCodec, - keys[capabilitytypes.StoreKey], - memKeys[capabilitytypes.MemStoreKey], - ) - scopedIBCKeeper := capabilityKeeper.ScopeToModule(ibcexported.ModuleName) - scopedWasmKeeper := capabilityKeeper.ScopeToModule(types.ModuleName) - - ibcKeeper := ibckeeper.NewKeeper( - appCodec, - keys[ibcexported.StoreKey], - subspace(ibcexported.ModuleName), - stakingKeeper, - upgradeKeeper, - scopedIBCKeeper, - ) - - querier := baseapp.NewGRPCQueryRouter() - querier.SetInterfaceRegistry(encodingConfig.InterfaceRegistry) - msgRouter := baseapp.NewMsgServiceRouter() - msgRouter.SetInterfaceRegistry(encodingConfig.InterfaceRegistry) - - cfg := sdk.GetConfig() - cfg.SetAddressVerifier(types.VerifyAddressLen()) - - keeper := NewKeeper( - appCodec, - keys[types.StoreKey], - accountKeeper, - bankKeeper, - stakingKeeper, - distributionkeeper.NewQuerier(distKeeper), - ibcKeeper.ChannelKeeper, - &ibcKeeper.PortKeeper, - scopedWasmKeeper, - wasmtesting.MockIBCTransferKeeper{}, - msgRouter, - querier, - tempDir, - wasmConfig, - availableCapabilities, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - opts..., - ) - require.NoError(t, keeper.SetParams(ctx, types.DefaultParams())) - - // add wasm handler so we can loop-back (contracts calling contracts) - contractKeeper := NewDefaultPermissionKeeper(&keeper) - - govRouter := govv1beta1.NewRouter(). - AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler). - AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(paramsKeeper)). - AddRoute(types.RouterKey, NewWasmProposalHandler(&keeper, types.EnableAllProposals)) - - govKeeper := govkeeper.NewKeeper( - appCodec, - keys[govtypes.StoreKey], - accountKeeper, - bankKeeper, - stakingKeeper, - msgRouter, - govtypes.DefaultConfig(), - authtypes.NewModuleAddress(govtypes.ModuleName).String(), - ) - require.NoError(t, govKeeper.SetParams(ctx, govv1.DefaultParams())) - govKeeper.SetLegacyRouter(govRouter) - govKeeper.SetProposalID(ctx, 1) - - am := module.NewManager( // minimal module set that we use for message/ query tests - bank.NewAppModule(appCodec, bankKeeper, accountKeeper, subspace(banktypes.ModuleName)), - staking.NewAppModule(appCodec, stakingKeeper, accountKeeper, bankKeeper, subspace(stakingtypes.ModuleName)), - distribution.NewAppModule(appCodec, distKeeper, accountKeeper, bankKeeper, stakingKeeper, subspace(distributiontypes.ModuleName)), - gov.NewAppModule(appCodec, govKeeper, accountKeeper, bankKeeper, subspace(govtypes.ModuleName)), - ) - am.RegisterServices(module.NewConfigurator(appCodec, msgRouter, querier)) - types.RegisterMsgServer(msgRouter, NewMsgServerImpl(&keeper)) - types.RegisterQueryServer(querier, NewGrpcQuerier(appCodec, keys[types.ModuleName], keeper, keeper.queryGasLimit)) - - keepers := TestKeepers{ - AccountKeeper: accountKeeper, - StakingKeeper: stakingKeeper, - DistKeeper: distKeeper, - ContractKeeper: contractKeeper, - WasmKeeper: &keeper, - BankKeeper: bankKeeper, - GovKeeper: govKeeper, - IBCKeeper: ibcKeeper, - Router: msgRouter, - EncodingConfig: encodingConfig, - Faucet: faucet, - MultiStore: ms, - ScopedWasmKeeper: scopedWasmKeeper, - WasmStoreKey: keys[types.StoreKey], - } - return ctx, keepers -} - -// TestHandler returns a wasm handler for tests (to avoid circular imports) -func TestHandler(k types.ContractOpsKeeper) MessageRouter { - return MessageRouterFunc(func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { - ctx = ctx.WithEventManager(sdk.NewEventManager()) - switch msg := msg.(type) { - case *types.MsgStoreCode: - return handleStoreCode(ctx, k, msg) - case *types.MsgInstantiateContract: - return handleInstantiate(ctx, k, msg) - case *types.MsgExecuteContract: - return handleExecute(ctx, k, msg) - default: - errMsg := fmt.Sprintf("unrecognized wasm message type: %T", msg) - return nil, errorsmod.Wrap(sdkerrors.ErrUnknownRequest, errMsg) - } - }) -} - -var _ MessageRouter = MessageRouterFunc(nil) - -type MessageRouterFunc func(ctx sdk.Context, req sdk.Msg) (*sdk.Result, error) - -func (m MessageRouterFunc) Handler(msg sdk.Msg) baseapp.MsgServiceHandler { - return m -} - -func handleStoreCode(ctx sdk.Context, k types.ContractOpsKeeper, msg *types.MsgStoreCode) (*sdk.Result, error) { - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - codeID, _, err := k.Create(ctx, senderAddr, msg.WASMByteCode, msg.InstantiatePermission) - if err != nil { - return nil, err - } - - return &sdk.Result{ - Data: []byte(fmt.Sprintf("%d", codeID)), - Events: ctx.EventManager().ABCIEvents(), - }, nil -} - -func handleInstantiate(ctx sdk.Context, k types.ContractOpsKeeper, msg *types.MsgInstantiateContract) (*sdk.Result, error) { - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - var adminAddr sdk.AccAddress - if msg.Admin != "" { - if adminAddr, err = sdk.AccAddressFromBech32(msg.Admin); err != nil { - return nil, errorsmod.Wrap(err, "admin") - } - } - - contractAddr, _, err := k.Instantiate(ctx, msg.CodeID, senderAddr, adminAddr, msg.Msg, msg.Label, msg.Funds) - if err != nil { - return nil, err - } - - return &sdk.Result{ - Data: contractAddr, - Events: ctx.EventManager().Events().ToABCIEvents(), - }, nil -} - -func handleExecute(ctx sdk.Context, k types.ContractOpsKeeper, msg *types.MsgExecuteContract) (*sdk.Result, error) { - senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) - if err != nil { - return nil, errorsmod.Wrap(err, "sender") - } - contractAddr, err := sdk.AccAddressFromBech32(msg.Contract) - if err != nil { - return nil, errorsmod.Wrap(err, "admin") - } - data, err := k.Execute(ctx, contractAddr, senderAddr, msg.Msg, msg.Funds) - if err != nil { - return nil, err - } - - return &sdk.Result{ - Data: data, - Events: ctx.EventManager().Events().ToABCIEvents(), - }, nil -} - -func RandomAccountAddress(_ testing.TB) sdk.AccAddress { - _, addr := keyPubAddr() - return addr -} - -// DeterministicAccountAddress creates a test address with v repeated to valid address size -func DeterministicAccountAddress(_ testing.TB, v byte) sdk.AccAddress { - return bytes.Repeat([]byte{v}, address.Len) -} - -func RandomBech32AccountAddress(t testing.TB) string { - return RandomAccountAddress(t).String() -} - -type ExampleContract struct { - InitialAmount sdk.Coins - Creator crypto.PrivKey - CreatorAddr sdk.AccAddress - CodeID uint64 - Checksum []byte -} - -func StoreHackatomExampleContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) ExampleContract { - return StoreExampleContractWasm(t, ctx, keepers, testdata.HackatomContractWasm()) -} - -func StoreBurnerExampleContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) ExampleContract { - return StoreExampleContractWasm(t, ctx, keepers, testdata.BurnerContractWasm()) -} - -func StoreIBCReflectContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) ExampleContract { - return StoreExampleContractWasm(t, ctx, keepers, testdata.IBCReflectContractWasm()) -} - -func StoreReflectContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) ExampleContract { - return StoreExampleContractWasm(t, ctx, keepers, testdata.ReflectContractWasm()) -} - -func StoreExampleContract(t testing.TB, ctx sdk.Context, keepers TestKeepers, wasmFile string) ExampleContract { - wasmCode, err := os.ReadFile(wasmFile) - require.NoError(t, err) - return StoreExampleContractWasm(t, ctx, keepers, wasmCode) -} - -func StoreExampleContractWasm(t testing.TB, ctx sdk.Context, keepers TestKeepers, wasmCode []byte) ExampleContract { - anyAmount := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000)) - creator, creatorAddr := keyPubAddr() - fundAccounts(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, creatorAddr, anyAmount) - - codeID, _, err := keepers.ContractKeeper.Create(ctx, creatorAddr, wasmCode, nil) - require.NoError(t, err) - hash := keepers.WasmKeeper.GetCodeInfo(ctx, codeID).CodeHash - return ExampleContract{anyAmount, creator, creatorAddr, codeID, hash} -} - -var wasmIdent = []byte("\x00\x61\x73\x6D") - -type ExampleContractInstance struct { - ExampleContract - Contract sdk.AccAddress -} - -// SeedNewContractInstance sets the mock wasmerEngine in keeper and calls store + instantiate to init the contract's metadata -func SeedNewContractInstance(t testing.TB, ctx sdk.Context, keepers TestKeepers, mock types.WasmerEngine) ExampleContractInstance { - t.Helper() - exampleContract := StoreRandomContract(t, ctx, keepers, mock) - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, exampleContract.CodeID, exampleContract.CreatorAddr, exampleContract.CreatorAddr, []byte(`{}`), "", nil) - require.NoError(t, err) - return ExampleContractInstance{ - ExampleContract: exampleContract, - Contract: contractAddr, - } -} - -// StoreRandomContract sets the mock wasmerEngine in keeper and calls store -func StoreRandomContract(t testing.TB, ctx sdk.Context, keepers TestKeepers, mock types.WasmerEngine) ExampleContract { - return StoreRandomContractWithAccessConfig(t, ctx, keepers, mock, nil) -} - -func StoreRandomContractWithAccessConfig( - t testing.TB, ctx sdk.Context, - keepers TestKeepers, - mock types.WasmerEngine, - cfg *types.AccessConfig, -) ExampleContract { - t.Helper() - anyAmount := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000)) - creator, creatorAddr := keyPubAddr() - fundAccounts(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, creatorAddr, anyAmount) - keepers.WasmKeeper.wasmVM = mock - wasmCode := append(wasmIdent, rand.Bytes(10)...) //nolint:gocritic - codeID, checksum, err := keepers.ContractKeeper.Create(ctx, creatorAddr, wasmCode, cfg) - require.NoError(t, err) - exampleContract := ExampleContract{InitialAmount: anyAmount, Creator: creator, CreatorAddr: creatorAddr, CodeID: codeID, Checksum: checksum} - return exampleContract -} - -type HackatomExampleInstance struct { - ExampleContract - Contract sdk.AccAddress - Verifier crypto.PrivKey - VerifierAddr sdk.AccAddress - Beneficiary crypto.PrivKey - BeneficiaryAddr sdk.AccAddress - Label string - Deposit sdk.Coins -} - -// InstantiateHackatomExampleContract load and instantiate the "./testdata/hackatom.wasm" contract -func InstantiateHackatomExampleContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) HackatomExampleInstance { - contract := StoreHackatomExampleContract(t, ctx, keepers) - - verifier, verifierAddr := keyPubAddr() - fundAccounts(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, verifierAddr, contract.InitialAmount) - - beneficiary, beneficiaryAddr := keyPubAddr() - initMsgBz := HackatomExampleInitMsg{ - Verifier: verifierAddr, - Beneficiary: beneficiaryAddr, - }.GetBytes(t) - initialAmount := sdk.NewCoins(sdk.NewInt64Coin("denom", 100)) - - adminAddr := contract.CreatorAddr - label := "demo contract to query" - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, contract.CodeID, contract.CreatorAddr, adminAddr, initMsgBz, label, initialAmount) - require.NoError(t, err) - return HackatomExampleInstance{ - ExampleContract: contract, - Contract: contractAddr, - Verifier: verifier, - VerifierAddr: verifierAddr, - Beneficiary: beneficiary, - BeneficiaryAddr: beneficiaryAddr, - Label: label, - Deposit: initialAmount, - } -} - -type ExampleInstance struct { - ExampleContract - Contract sdk.AccAddress - Label string - Deposit sdk.Coins -} - -// InstantiateReflectExampleContract load and instantiate the "./testdata/reflect.wasm" contract -func InstantiateReflectExampleContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) ExampleInstance { - example := StoreReflectContract(t, ctx, keepers) - initialAmount := sdk.NewCoins(sdk.NewInt64Coin("denom", 100)) - label := "demo contract to query" - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, example.CreatorAddr, []byte("{}"), label, initialAmount) - - require.NoError(t, err) - return ExampleInstance{ - ExampleContract: example, - Contract: contractAddr, - Label: label, - Deposit: initialAmount, - } -} - -type HackatomExampleInitMsg struct { - Verifier sdk.AccAddress `json:"verifier"` - Beneficiary sdk.AccAddress `json:"beneficiary"` -} - -func (m HackatomExampleInitMsg) GetBytes(t testing.TB) []byte { - initMsgBz, err := json.Marshal(m) - require.NoError(t, err) - return initMsgBz -} - -type IBCReflectExampleInstance struct { - Contract sdk.AccAddress - Admin sdk.AccAddress - CodeID uint64 - ReflectCodeID uint64 -} - -// InstantiateIBCReflectContract load and instantiate the "./testdata/ibc_reflect.wasm" contract -func InstantiateIBCReflectContract(t testing.TB, ctx sdk.Context, keepers TestKeepers) IBCReflectExampleInstance { - reflectID := StoreReflectContract(t, ctx, keepers).CodeID - ibcReflectID := StoreIBCReflectContract(t, ctx, keepers).CodeID - - initMsgBz := IBCReflectInitMsg{ - ReflectCodeID: reflectID, - }.GetBytes(t) - adminAddr := RandomAccountAddress(t) - - contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, ibcReflectID, adminAddr, adminAddr, initMsgBz, "ibc-reflect-factory", nil) - require.NoError(t, err) - return IBCReflectExampleInstance{ - Admin: adminAddr, - Contract: contractAddr, - CodeID: ibcReflectID, - ReflectCodeID: reflectID, - } -} - -type IBCReflectInitMsg struct { - ReflectCodeID uint64 `json:"reflect_code_id"` -} - -func (m IBCReflectInitMsg) GetBytes(t testing.TB) []byte { - initMsgBz, err := json.Marshal(m) - require.NoError(t, err) - return initMsgBz -} - -type BurnerExampleInitMsg struct { - Payout sdk.AccAddress `json:"payout"` -} - -func (m BurnerExampleInitMsg) GetBytes(t testing.TB) []byte { - initMsgBz, err := json.Marshal(m) - require.NoError(t, err) - return initMsgBz -} - -func fundAccounts(t testing.TB, ctx sdk.Context, am authkeeper.AccountKeeper, bank bankkeeper.Keeper, addr sdk.AccAddress, coins sdk.Coins) { - acc := am.NewAccountWithAddress(ctx, addr) - am.SetAccount(ctx, acc) - NewTestFaucet(t, ctx, bank, minttypes.ModuleName, coins...).Fund(ctx, addr, coins...) -} - -var keyCounter uint64 - -// we need to make this deterministic (same every test run), as encoded address size and thus gas cost, -// depends on the actual bytes (due to ugly CanonicalAddress encoding) -func keyPubAddr() (crypto.PrivKey, sdk.AccAddress) { - keyCounter++ - seed := make([]byte, 8) - binary.BigEndian.PutUint64(seed, keyCounter) - - key := ed25519.GenPrivKeyFromSecret(seed) - pub := key.PubKey() - addr := sdk.AccAddress(pub.Address()) - return key, addr -} diff --git a/x/wasm/keeper/test_fuzz.go b/x/wasm/keeper/test_fuzz.go deleted file mode 100644 index d6acf7b..0000000 --- a/x/wasm/keeper/test_fuzz.go +++ /dev/null @@ -1,78 +0,0 @@ -package keeper - -import ( - "encoding/json" - - tmBytes "github.com/cometbft/cometbft/libs/bytes" - sdk "github.com/cosmos/cosmos-sdk/types" - fuzz "github.com/google/gofuzz" - - "github.com/terpnetwork/terp-core/x/wasm/types" -) - -var ModelFuzzers = []interface{}{FuzzAddr, FuzzAddrString, FuzzAbsoluteTxPosition, FuzzContractInfo, FuzzStateModel, FuzzAccessType, FuzzAccessConfig, FuzzContractCodeHistory} - -func FuzzAddr(m *sdk.AccAddress, c fuzz.Continue) { - *m = make([]byte, 20) - c.Read(*m) -} - -func FuzzAddrString(m *string, c fuzz.Continue) { - var x sdk.AccAddress - FuzzAddr(&x, c) - *m = x.String() -} - -func FuzzAbsoluteTxPosition(m *types.AbsoluteTxPosition, c fuzz.Continue) { - m.BlockHeight = c.RandUint64() - m.TxIndex = c.RandUint64() -} - -func FuzzContractInfo(m *types.ContractInfo, c fuzz.Continue) { - m.CodeID = c.RandUint64() - FuzzAddrString(&m.Creator, c) - FuzzAddrString(&m.Admin, c) - m.Label = c.RandString() - c.Fuzz(&m.Created) -} - -func FuzzContractCodeHistory(m *types.ContractCodeHistoryEntry, c fuzz.Continue) { - const maxMsgSize = 128 - m.CodeID = c.RandUint64() - msg := make([]byte, c.RandUint64()%maxMsgSize) - c.Read(msg) - var err error - if m.Msg, err = json.Marshal(msg); err != nil { - panic(err) - } - c.Fuzz(&m.Updated) - m.Operation = types.AllCodeHistoryTypes[c.Int()%len(types.AllCodeHistoryTypes)] -} - -func FuzzStateModel(m *types.Model, c fuzz.Continue) { - m.Key = tmBytes.HexBytes(c.RandString()) - if len(m.Key) != 0 { - c.Fuzz(&m.Value) - return - } - // try again, keys must not be empty - FuzzStateModel(m, c) -} - -func FuzzAccessType(m *types.AccessType, c fuzz.Continue) { - pos := c.Int() % len(types.AllAccessTypes) - for _, v := range types.AllAccessTypes { - if pos == 0 { - *m = v - return - } - pos-- - } -} - -func FuzzAccessConfig(m *types.AccessConfig, c fuzz.Continue) { - FuzzAccessType(&m.Permission, c) - var add sdk.AccAddress - FuzzAddr(&add, c) - *m = m.Permission.With(add) -} diff --git a/x/wasm/keeper/testdata/broken_crc.gzip b/x/wasm/keeper/testdata/broken_crc.gzip deleted file mode 100644 index 378713e2ff7a88e761305426258d73edfa5f9b4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 809232 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

3m6q@~RYn(?_267q?0SfbxL*hGDn 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

i*cJgVZwQDe=E!#?c?_e>8xFw*1 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 z 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%0A!$FOEGbys&vO3)MGc{&XTA!bJ1lr24G>yP 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

V9Ze!Ps!;R|8K5T$*3av>em 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%0Da zQEEqB3sn70JJm9LbVkW6t;WdTIwZL$k4S7@>$I;7b*fc z@2yU(FdTs#d+P=BrtZ<%ubP0E+E+0z1PaS_{sscSp7rY4ttNBHDQg=G3r?jAb)-!e zKg(|JuOK0v0a&qija9g+@KM5_#H`-_kh7GY)}n2dFp6Mq;m*L9+*_cZUMFeU=YS`>d_#tiQSxRp1hls|M|S@&rF{kV2A1&bRIh1X z5AW#|7RW1dU$mp-aEk?@Dh%F!zP1+2y_HB^H#V9*`*kqeP-ru)Y8K=zIhdc_PVFw$ zea%gN0MWF-KDSa3milT*rDMi}LBU?MOjjJ=p-q8`%Rc9WmzcyUUh_)>PD4g!`~eY( zg^OmnPPNq++afHC`35*9z$57MB|nar1OiLR9zk$@jXg2oM@}kkG9QpfqDtl(oEAtC z>dG{7-Le~MoYFk`C+l2Buw|IAvhvuo)g=?EafOu_X`rK`8d*KhAY#+fqs8gep4}^0 zT;eDPD=4CK^2e|F-sxzX8mqQVi%{q}20a4`6_4r0QH~|<;&!#n+F)bo*E6UBtM|S< zDpMp-8KN_0c>yuygXL!F`%RRRS6ts4ZW{g(@_JS1rjDJhW*-H&?}>I`IbxCi2eU?P z3LISTMFQ@0ci}%GVYyVeYb+X7;;_jy_I|Lukad+zlAzNktx?(QsW`Q?c)(!`?L^mxsSuRl| zjRw2Ac*i+8*!!5|&p@WcfuPzm`hZ7va!tiERiu}`vVSCkgu4qFBMvMw=SY0jbhM|{ zixwY9C? zv{Xwe>{YqI;~vG=&jPnPms19CF1JL5Yjwo{hcYc}<%>27Rq?y)r$>1*;;2Yaf*?cL zaET@Ctc2+$lzKQ?Pes*3)YzT&_gQ5cet|QZecwm}M6VUdR>+f=6=F6V z>N`o}abp$6SY>I?^bdk&JAWgnddS*LN~0`y(=f_NiMH69{qkfUb*sVpMl z8B`l@!xJS3oh9d%9p0e9S$&_h9VWCSJS(zj2F=Rv-yzjq%vY%LfAs>) zr>^oGXyr$1LRpx%fT$~QLc=@lyH^w@uQ`UCcY3K@4IYtEtknD$?(G{#BP5Tl{l4_Z zc0fw3C-`7*1R>9U>5cdcLB1gxnvA;{_Xi}xD3thrEKI24W6j}*B%Too>0nGB#+oUF zYz?5AWRkJj*I2G8?MnL+F8D)BzB1~fIO=myH$fBh1gZ_;ALF&W0-ItC*13+wL(1}y z68KUMHyoE0c}gDp=Y;i9;lYi{)NXO#1v*(6Q4cb=YU+#WSk!BxYg3ftWlRiu&;?U7 z9CuCR-ab${l7dC9xu-#fzjbA&b7je=D$>f)ZQIwLJPpZ*Fojd~sLI+iKp#N@cU20; z&A+EP_bySBhof#_dPqcjW(@$I<3zuE=f&?gelgtu!ey+y474!_QXt32Aax!eJQJxH zPYb6ZZY3lh*S@xa1;PveWed$aNK%H6r4uX(9Hv)rQhU6TglA32GXRK?a#VR;lBuE< z{(0zu?T@m1C-uN8BrY4wBQ-V$F^4SSzmAm0Bpdpp{I6VDA2XMj&FjJXU`!QHJOurY4ROApHMh3vM7lD!J`i?gAmm~U1JnwLE%h%S_S$>v}4YU-l|fM zIsoj`?LXN%5bG?}6)gyCKf?M64*dHa-6b&qv<8o6Zd#!v2p$;`rgupc$5ba|{9K zSD{Ai8QA3uY2gg0LGIa*F_bch>YM9>7@oFr5UzfN?TCrE{Uo0c*WsH1@j|$BTU#$Q z7%UR~sPI87RSi0!oo$NSyV&~h8CH%38Ju19 zSJLq3n&&;wi$K#$jN&>Nw^DrGcm^l-?TlLU1~T+$kF(RePyyJ&dW%%c zYYn~Ba|jWqhi|tB?4wJiDy>q{FiARh_T4103ssf|9ZmI4-9yV17y92!$3eeq)uRbG z4pU}$Znfp6Fx?SLB&C3sT|TV2*wLBYqrL~qbn#}S2GfK8SnwnVEENn3bx-fxPsOsnGs6C z??8{HpOFi02qVhLmwpU~_7G8xztLY)tyYojeH{wYd~to55sL9rH%}5i9k z+d)12!wvlGP>>_#`y6ANuExW|VXIu<05Ry}(;8QfTz!!D+L^20fa5{E$1tFJ6;ab? zr8(7)^F;oY=C2oTIp$8WDShLP%(=eOsPdD`lHeA&AW8=f97J3IDKH$1qX|31{$cVW zFcBdPiWt9ZWXPAs47~Og% zNB5X(?4u4Q>#+B;%FoaB^ZT_^T;mmlf?A>a*Et9~0x&1c| z@b)O*#)DZM=t@{O9Kg}B+ZGOho^o@JM2{g*i#K#Ls908dz{^m>e|Uo>x?DS=xOd*j zg=8n`q913xp^xJUH+dg3CEm*tsuLy%bDmv{>(Sv{4zkKqx0F`+zlE2OWFAI zW@259v@p}JU?wAQNDU;eQ|O~Soug2bLwLwp`8eb)sG19JyduI$gVRS*{UoLh7m-81 z22dO}w`%x@DbULVs`k6d$zqj8f59SgJkeiVS??+3%J^rCh#dNR5=;P8Pel?(~xIr@;5udlee@MO1xlHoSF{b&bJyXnpV;`)QF- zP3U?bOg6R9kTr7upKHpvR4&aY9ytnY4ef~WpjY2>t^9cQ4wu>`VzZpUsjLZ3^Ar-?(&bOu%;fJG|< z6n1%Z?;IO@++PB9_m8CvSWL^-N;Qx+2<~rKTZ}0PijpQRp3%GbJdbze)Dm5TBGdl#oFFToahRT5R9A?89A7hK2&(Ov(cRCQ3B^?)E6JwpO>PlU4=U8yG z7fv3i`csjt0cJcmh4TM#(zuqUi)ajrd97q@{0)H1HZ;iUyVmya93rUdreL``86SEV zPtI^R{Uo-9H*`1}4vW*LgqGr6y5dt6UGN;8Ii}4T4jV2dvjg9j>Ok^dJ~B{VZaB3Rcn0c5HbP_3Hq4w zM}r$UF6^4+5|3oa&u8|?NYQzJMdeqxyC*}y2IedW$RnX`b0gguq1TP3M&cN!reDhV zSllu&eB>(|CVFWE(xAsY#cO-uTfy)?-;@ae52D1Oghu`U5;Ah=ifB ze)T45%iut^Xkx3S5@RjqBF((j;;r0)c&eoBz1|%(OA&U?kV{)pXj8|0SU64~q_kn& zSt4gA%+<8x@C@$$t0TnIGs_mu1Uy?IpY#|1;~g!CxpdurX?rBXuB~F=#(`ApXFZi3 zzqy+r2OZZiYoe?(`GUbsrT=q8`=fJgBF{%^BjjrXKcA&$6t;kw|@?mGkCmeZqL4YEyk9g59(aSn|g#$j}kuuMCn*@htp>|2P= z&J(bDflM&nQP)BYroDRU+xi7(9}Y_bh*`Q80^>a4!*E9wQBu#-mkk&XUICJUL85UR z7t^9g)Swm-9x7*nAUUx?^TWNJ{si9OkS0V?t;Zfp1|eD2!&|Nq}P-75j4rRIxGava~1 z{WBd8{87s5Wm(esEJg(-mBEpm%V~K(*QPw<-cl?5FoCC#@zsjEX+5ptFSXiEu003q)@mPbE**M z)#gq{e8)5wTTsqqMvZ-f@}1^&M3xxEluj-QQCfgb6Yv#XM{=iAoFb46b!-b z;+`q;+-8s{y2KX!;T0=5m>N0RD0zgMsD??Ch3noK&N~H+JnsPl!OyZ!*cr7rPXX`| z$@lg014RQjqJTyl9k(|)pq~PnKcz0#yfE!o?Ux}ALwsFqd;1 zeX4-V$D|mqPr5t3;-*MBU@6~dYNv)YP%w*xq)=!wqF#`83IgIKjLluIFJpKp=Ty$@ z`Hm%yR&F$1da9-aYR{2r>=DuF88#2%z~G&ef=^Rcw-NVi2j+x#vhzBhgoNm4OBT#H?94|Gt_n;Zgvgl7@pM3V zY1n~QQF@ho26Y!(o`o40XD)@3a2EhMK*ql%hAaN4;TlXc<(vT>&<}tm{ojGl8^6R9 z#G=&Vkj}g`vv+Cf`-BOTIGY+$NxWg9fgX`7#7SV7bD^A)$=k?RDkr6KIC7Q`#{GXZ zOMn=jDsD2Ahxes?YZRXs5Mk4NF%&;zBBlk|l~~kn*c22#83h5y5Ig7<0$fz(>5?oS zbc!xK`%FMFuzk!ag(ci*>4k2@P8@7#ap;1Nx1D5*n3AC5E~Q;bo_NC+RR1F0B`ON= zg6d?J^2*pXxYFE350JB;Eyv%Ys~EE>k%_pgj6sv+U8>*GNc7cdTLUDCD!Ts_moX1D zY!y&8Vq+6L3zO#COb>sH5vT;vtp*YsI6}kIHl^8pB{qjv@$pyJE-Wn4IgSF$OT>SSd`YbOeC`k(CMLs>nS_-hQ@%p>A4MV?~LW(=UZu z*FS_fzppRk_*me9QF;SqZ|tLW_I;-mmgcZ{#mH+}&GPnRyFgmY{DeGKk8g~+6sQ$J zHDF5rY11kNhH`uji>K8?L!5-Kq99~8*3JD{NMG@ZL7e#l+uV0uwavf&$kFk@fAeWw z!&+2Ep4M8m5+^rMsa3NBLB&GgUd5fTj6?PHH*YvAmmShx_@@!=j_u#CAcgD*6A4Agc+gO%y$_qsvD8#JNYH6k98&m@w|JVpo|F9i62ST=DiLfIgyK!gO~oJ zRZtac54>*<>Uesg$X4r)lT8h=;Dk-^8f_Hbp_qTF?`jY|$Ndc>MCqlf5b1|xkhNAB zXMCc|&i? zfTXESEGOw;q5BhP)`;5zae89fxUdDBLmAA}GP;0XQC%(_c(`?bBPk50fjbN{q&DR`#|`z9{>xvQk7b=8;= zRdKuO5A`a1Re9zCH+3R_3||hpI$sM*-kBz<{1%K0NuteJ;A6HGV;pAPbrrEaa zArDp>&6D+f?ZLksZ6+hkIe65FuIeqY=JJ2RN{~{t_fJ2%ktv8Moa;Q**o>=FF|)tj;oiZgaBGmgFuMs>3VnBZ zqO%f{PSBzk(l*9)mZ2LKU((FpO4?Qf5yG1OoS>}yC7nVr0V;3njmy7?xv}A(D+R6I(@|8R1RLVil2kgT zJDGrT6nj0iBm;YjOUS8F5tPU=U}XBkz0(#6OM5>PlKdnih|Xy>1ZKhAY?$jDIfr2k znGP?Yp7|igJs`E2%N_;`CSJMZmg^kitz`VhGrhqR_>^oO< zNh3?`IktuVT~70?wo)6!`PyoTX%AkECZ`NSB6$mxHL8s;xP#+EGi@<99nmtEve~f= zWr((h-UK0kAyow>mWjG|Uq4@XcH5ZEptzMK<(`~j{s+3Ss?Svb#}wL8*h0WGVVvV$ z4Hf!uAI)U|E!h$2G&IRtgdKO?4XD)|G=usFgV-{2o>If8W54L8UK_i{`~-N+izGei zkp77an`SopQ_4kFrrK3$_ofgg`9P${$h|FlrBwBanb2c0?MsV--;b@fC=|FF8=ASr zC%%6BN;h2er+=!eBHcxnOeIs170`9#uV3ZhtV=El1w&2SH!9SXf95^Fp@S(nfU+zP3n2&V`!w)?b~)aLCas0uQ^ z>LTv<)*8wPRZej2)9M|{xSR_fuhtYfXU7?>eCYV-lBXBb=|dZxuv}nt7!}TF>9A?C zNgq?STB+K=IXF!yH}j&|(>nmr(b61vK9z$PZM{)fk0weQ(*eplN_p-#ogp*EsBgG} zoEhO5Ld&B&^M%EQ1K)C0Q|I#7 z=DU>t+D%`2K(rj6_gBnMFohhM&CAW+qB8Nd%{q?wwcq#83GZLK~Styf|b{@Xv#p#i8H>1wka67WuNm*j?+=v zz4B!pS;-HG%!WMbrbh+n;rYws1?EDJHKa969hAxC$ zliILLi~Mc6(!a$&2cgB6dS_kBL>3^+IHky?q^9q*B3|EB-m}aqz|x%g_$b@tIN%>_x-~usfRlvBaStP|@ulbXJi{ z_P9^Ca`y|Zx1rj(gsBiM-|v7gKgS(BM&l>=ECFcqW?urH{|-2sytV;=70GN6 z)|f8oAQYM?Vpycm_*4z;M+;S$BFeCYOVD)DnN#Cm-i6nvHErN{*a||lS)P9{y4+T@ zvpGkwAHbBxu3%HwDoXX=1LXQHnz=tB1}jbh5Q1aB{T z05pGw)8$gg7Fp>b=gLE|gL56t1jlsTY@@YAn|#+Um*4j0w01XXZgr2Ru2{koC) zclNavGX};G5NSr@I)uNNDX(HXnp7V#cSQQ)gmq20I{L}E?_^RG8#-oT=Y)li_;9ob zmQ*$t;8~_)r1|k-NqVBYW;`o%&f_+}>6}R;Dw&ucxMU~Fm@2kG7FamD#Y8hC=^_YL z=tAR`u@qc~KWqH!=q!1Lxm-LCDMOQjZ=N1zb}0^hAd+AIZr@!$NP%li zvT%SYV9}2ao=V|cf40z7U|T9nNkK3cDg?yg{0QPHfSw{LB(#sgm~|euw2G9@120p=)9+n(t4+6XsbIWd;8)IT%(6y4tR=0*s6CJtMTZe zK=^g7GeGoH*==FQqU0@L+m6L*tLVKS66JzEwMOc*ckdgqsHB+__&nAoMuDAf2n0)$ zh;ixs)V3M-a(?Goplj)S{*n<%98WmJ_!u8n>qE?hTKU)b093zC+5Js4*2N~Xeqppd z2&H#b1!(2g?g!D^vt?A+P)I>5-U}1_x@tr3DdTUMNi#9Tf*mAKFdrHs?1F_D0y^6E zIIKxiBQiaohcuiELT=ao;Kd7km7OuQR88UAU%5ntn@A4aud7bL-P%-vwRL5w18HU~ zP3@F+SsLYbaF>&(VQ?xhYZb44;MKeZ8^-M;p(+9}dz5qdk9{Va3fr?DQK1E~cXIN? zKPS@X7=|7%?_6*=(g1S;K=!96uq$J>1_qbq4`t1Ao<5l!s(zp9{4Gj}a8@6_8TkXr{@q5@0%+?kp&RY}COvJ3R+B?+^-;xdf z)bWh*4`U^6K=WoHXC0ply=3VA{gzAhwb#U{U5P#kkSC@Gb_!SzlRaE;Q0kE04kC^1 z6%|X|J<9{w>bUk(3vlIM1c4KzaW_*(L<63?p)MSq2cW?XVG-~s;;b0Iv16Zwx>_cT zstmQjlt)&AE&C^&Ra@lvJq3q_0x_Fdh|KF66WgNzAk$YLG~FDd4&xTrf<~nVnp3u) zCGd1-%)#Ml1Q}xpD3Y?6*g^gH7MTj`jULtcu^L<&l9Wi+qw6gMF}H+J zrfxIEt$~357U>{peEm}o_l_PJD?KdBw|Bz>(KTn3lU2bOz=?QprSzm9{R8%&LDR#A zbq?M`V9Ul)NBn|Bl=xDu0hHUw&f+30Msm|W<)qhYPlCMZ(-ne;dhS$~imEsPgT2A1 zl^_JLjyL43#}xpX&he`B+a2O~NSU5190@97DJS-xTiyg<-2<3GnlrK)`$vk|X1`|L zk0wk`2&pm{PO=v8l}5|@5Zn&j_coX23GY9xgBO?uE@Sbv+d+~VwXm1Gq56DHGaw4j zyGaCWybd{ea!$R?0+t~KmCgfc=$g;}5Ax;EEvGoc+#VpM*OU^9t2F(N8;_zSj>8sw zl80rql{KGx@Kw3T?o6u;NmeGeb>dK^iDk2)P)MO{=bgiS7}hBYPJVEJpw68R?=koO zTZlKGCm;9&1Z-xte>vh>&K1@CWB9q58&yJ~Z(~Y!szumb^Mlu-14q;#a6_b)TBR-O5 z`JpRfAA(2wWaf7FdxfMMZu6F-aClq;O-U?c zxVJu@5cO4s-j0qb!~&WmYB>vdO>IK4;I*U7@Z`VJm(*xcv{^3zAy3JUK;MCF5*h=! z?u_cr3CBpzehKGt?=c#7&_XY#VtiGo22$$#m*g0OudJN;CB+dj(YCPpU3rZ1ny2z{ zDNv(68C$qSVgTOYv0f|gR6|AzV1WnupelxxjJO8%^ zj`~CBJL#(B(9;&b>EGho843i8LT(6?Yx3xIsXvJ=ELT7Lm6m!KC>BZYOkP3{3pzkJ zDn-;KL)}}Hbjk3@(#f<{d-$!RirjKsCsddeGt6fu7F!GT`9 zy8HmPN|^=^i8&Z|-{_BTSY0;8QHef_h`umfY_&-u1r2lVI56XPi<*L6EgK#*Z)SBX zbss_qycJYnw=IFn=V2UOJ!ESkM(}j!uCXA&)+PUQ z%FrkOf(jdnb?KXvHS{5K)aG;jqHH5t*LZ~Op0hDPTOBb5-(S|&Jb^nU&_xj<9*umO zR4zim-Ii`qXD00qJA~N3=3F)*o7rL1L~Pa@1}?5S|LxJwmI=Wf)-s2nzIVn;+-wR3 zQF}_OA08S2J$qY5rCQ@Hq%Ip3{Ty!RHmdN`&BsQur%Bs4gAH2-@*+n+ zsjIa9DLgd=Yq8uLOL`U}Cr8HYX9wG+i9!SvfPKBxqu44xc1UVP(LQ-hC8MQRo}_#c zpg`dm_`;f1AuHf5WXIB|ROy{zPMmmUf8eBnufP<$hUc;)RPa`6ba|AJ49T>tO~e$+ zfgpFWNXWHdp@XjOQ1~!f=1tJK`99aH8J!0;VwVk&9Yk5HjWo11u6W
QwUtG&;+@qHM*qTPJv_v>u~#)sYP)>qD*nR~uesyH<~~vdEnK)3XDS z_ijWKhf0M*(}2u}^pHvq;sc44=onvW0qtRgGZeZFy#)G@dLD|SD%-vsEpRZ0+({@$4{$+wkC!c5N(}?^bb$l+UdL+8arkU5kg`1h zLE)#-8_|{b8GRZ?4~Iy4&IbG0sM{G>#&lh^!$|yg;=E8`VF$j*liF88EF8@jsa1q5 zDylj<3oKqqm`qTma zzATLp1Un@3p9ovAj;QVTGJTit^4ntB-8)G`M5lD`?R4c@*MM z9We!p+wM}YEIM&`i|9Bg^65oT3t5YsW!VRB`G+qRPFJC!8^aFuh)lX8ixV1uW;}g~ znwx@~bqf=x)68L8cv>Va?)qxkrj zaHJ%4ZX%N{*1TTfXr3xEFzy>(zEuQuHYSNnn>Jr(OG`|>^$y>B4RkW}^iQZ-5biBV z&InK-d?_difYcSln?M;Y6kulkI=Vtqz zuuSwL3qBYt3um$(>&*}hz=SgR&9-N%zL78QaTIbWSTGF2Tir~VB)D#wgRMdF+E$Cl zFA0#IE2QoEe%|2^%Ogc8Tk2-x0@naSZ3w9}S*!SdD=s)k`yi~yjPmBP{F<=8)OlXy zVE!|Kjj0epDV`xClyizW*_dDmzMf5C{QDeO?p(M>82~L_{xc1L2dUG=ZI*Jc`o&Z- zgIC~AqhQIZnl9rd0iSiWf`Yk8CUty>sLn1kqBs-`Mu;YzUk3Q2mh$o<5tMW#c%JWjh?LA|hKA=n!_LUm$98EYNYT}>( z#dPF!cVfB(@YtE{knj`>f4G59O~Z$Mg%a-Z{PK5bMzMLqCkfr;K1*!lbc{Sno5B+| zy2F*Qq<2YHgG$6AWbpr|@G{Xf&ak=7Y!u7sz7~dqZRr4biKrHj70iZ2ckSZTFkeLY zVj0?GcGwCqnho?2G@v4ICw);Vo&^fJ7L5N0eUi?A4R`-^n2f>OEcI@@`kDsOz3Ljj zQIo3sA0ABC(EqjP2x6J{4>m4Yt5q#L0wlgy;j4LBZ_^epvA+~`$G==VBaK3Xgc-L# z@_+gfFI`XZ-$;-KC4nLDoMDekrHRei$u0|4+@j4or=(z4c>eqe2^%<^F*+pbtzLp& zQ`^8BBg)Xhy=;U_%kQFBM$&r`jl?^M5R7QV5{6jfL85bWh$5I`Y18X?R>&s%x)t+u zmSj%=D+@&sJm5vz<7~MLGaEaO@a4)HgAhvRS>r`t4gj3b0?+$@cT$`1BEGWs07wwb zOXT-dPqUi1L4_Hk8WaW%s_+^Cs3gO(&ThT+i%A$b&~hFnF0rPuLc0L@_)8o zNF2WnVCo^KE0zzTkvLh`Egx97d+EXUcP<;XUrcL#$PtaYtXvI?9Gopyt4R7!iiy!! zc3kWeZqPb8_&V=k-m#NUpT)Aw18EtZmx`3bA}e5C4SQ;tm}T zHgI1qXhcUxB4MbUWE;SU4<=D7zI}Vx1mDph535VZ)2(u&{E3*W)1b&qsv9eDHNaHf zSf-^W^fDCw%|=l_9)~+Lp5d|iivqg+*53()_tuvsx=4uV2;i#_{gHa2%a~t;Z1AN( z`)&xG&oxiSTT=MK>$#XPbbyoZU#yR+V1%sSi#}6B1=o4{JV69UOfnHe$ZqJ~K+y&w zN|G>9*Cn-qLe({CvfTg6h*#zf2?-Mn@x0JQoAX)Cpjm^%$lA}ti~!!wpAIeFH%mn!*5B&a*$$V!SM z_BFdsmem^c@RK^yRo$;swSdc5Ms3&GjKG1`6~O+2=)3(&h{kt2zNX^qnePtM`$03j zkE{s!3jWn(!>LYg{k2B2MF|CgPfiV!-!w)v`LOWUQ(GV%xW%n5-xmhVW15#R0?^Pg z1!_H*XREZLUS}oagv23R{V1KblXj|spsDpM+GiolgKsP8>6?wp9btqQfIqy0Zs={s zNBci|Q|MPvGxMmHft7!qi21oL$StGQ5jpAYC~gsqaLClIo^q7{Mif|!=rQLK%id|? ze*)bfLIwT=Wo=g48P-@l-@J$;+j=8 zRk`mu^nbKrxDp)SW1mqAVR_m1XvyBI?hmZBRLA?!d)LzNUZa#xlPP}?C)(w{zPdLBDSL_IS90El9CRtO<;54TNt z6L_fydx#g9vl#Ya!GV*e&pb7qfq6{7-`NBD$(Zu^goQ-BWT3%FE|SIAi7bnkR2Dx@Pz zIjt`s&F*a@2r-(9uR+tb{KaWvFrJ0O=zq;hgwAGec%?A7P309 z`krSFw_GZN7z-i=_qi=I$Gu+=TTn^?=T@zo$ZgpkUS9B#2mr1W!|W~%x)8ZvqQ8`dLes*(M_86syEzuF9w=lz44zd5X(Xj% znR_H=W5@tyx*>BVuLU|FPj!^Gf&1c+icCHJ0tGX73~jG+Q_<&YZ~AZx|NAk8A#nnE z=>G|V_0zb_AJ}QU_1gO9e+sTBvy`#dypwj?hy;E{s!m&tu4CxG6-p2 z=iS9Nu=@t|cL<^26AIdb_JU zjlt#66O3`C;Mn};Z!@&XAr^<+h3j_c6Qg?X`VNV4DuNOTw=KB>K zm031UW*`wV`6|5R6c;eB$S2NS2M6I>DPYvY%NSX#O>o`I4L1VUa?qKbnh|*p3agt< z9G1pTOEVoAAwRK1MRcJ5IIDvG^mA71lX*1n`1^`8Vj{8#8zxhPL1#%9M9b;_Lr7Ui zM7T(dVq)SNyO+^QGhawbhYUwd`4@|bQa2uQ296F-d>9Z~i2)=EQ&fDIXt?3d(K2z1 z?aC^ftF!`FLCeRZ-zmL0pPGpRRochQ&E#4L_A7Po zxou@X{~1?vs3`vxX zVN|jSYG|)wSwdvK!u;)Gn5>HC)Vkk~Cn89wY(5}?Egj7RVc$7um8v)6I3J>RA$48~ zVoghh@S{}u!dsyI4o0>s98nDbXhwUr*sFiFKKVdR=uk+RxL<4h_i`ZjyzV#a4o`;~ zx020cYQbEQws)*59o#xUTqpgIwRRe^Ir3@QZ3%_^4Hyp~yl!J^bwI-+EIwr57SOSOxlqA6_4w(zW;CH|}Eh_#}TT1WA(fLsGc4m%AJ~ zw8}VDf1mnRz+Eq|;Qd2qps)TsbYjS{exzEmo9)Q5j-0OlqO}M>{@W6X*(yQ4p!?V# zTrW_Ch=S_OTB?j(d6sM|a?rLR^kU13bqos_ekKguBoT?Si~B3a+cc*o5cC_*=gfk` z1%>C0S*6e5U&1BUI$eX2dvZg_lcP)es9Qw-K3STAa~*uT!@+%uD~WPz+3ugD%SGU3 zMveykz@ColfJ3wPzbCUF7&4>rGI+v!xte`W5kI45uh`rmg)N0g-=I1&e=Wn2IXjej zO<;ipHCC-V#drT0U@Q=SM&fQ7C#Fic2fL0Vwv^jc3Oe?y;f}ob&7V6X3E_Bcq=S}A zG`oQBMe);f%*VyDmv7>0RuK-w-*4b0bD%x6BbIRuxLnv|8W!&-p_vhsn&ue;Wh7bU zcOX#)08O+*(A{NZo-uDCUFS?Uj?C_!o2H!lFaJT@sY2&Aiv{}LAbbMawX^Oyik>7( zw1)q!lH~?r_8g80r2Q0|2w!Q^ep5z1iw>1#(J(5iiDJI#MI|u+K`Qdo=o(RYK2CxB zf<{&eSc|%WD4SfIO$`k^GLSV2i=+!!(}p1^KN}93;86c7)OTz($F*luA$f_x&SJ8_ zG8TTOwEl=yMW%mD)cUS5B01-jx`+3HeXn70z4(-rfG(I@^IN^rq?fMeLzn1rBBm(2 zAGO37Hh--5{9X}j=Hv!Bza%5k#OasIQ09InO@$AVR1=?axiSi211aXJ1cTU-yp;p!>xz3sIx34jS*$`t1H#X5ZwJ=> zjr0_+!`_Cr%6!9}N(|mZIUMf{aARgIHEEY_yw9MvFqb|O-ml!YDQYFqvcQ%1k+dEk zZL(W>ir|&U$-QXGh5TjUZNG4tg|1Nc;cZhXPz-$81nc)r-(Ak2$pknD85(54e6@jy z2WIlL*4xf1q6XHU#!fF4TEy63F~ja+rqmWfijopzwEnGbr7{d zRuUQCOqRqwBe8*)09hqMVK@48WopQPlOyj zO$MXzV*1UXmw`c10khPAo_Ri^p;)frbgB@fsUcf>U_07UcQw@sqh$E(n1uzB4mXKq zAB8T2#C|8z6T7bNW-;K}NVo>ku`+8bbxD8jV6IO#jLT?lhg#~rnJ|)fT6Euu7SSw5 zHLr4P_wuYdI;XGajeGu#z!1(Jl3XROi4iUWXx&a_G2YS7%)|sjn%HASeeh%Ihg_RS z16{wlOUfOf-}y=9Qib$Y&Ov{ckn`XfexXH4sY7DvJIg=sn6@;z7puSoF;-|oe)s8? z8g$q&j-2~o^ODtd<4a4q& zx5FXU{lAmzb4fsBUe}*ig(aA2e^N*-j6t2?P_@nsk&khL5(95}k&`gla)LvV#Ak&0ed2l?qHDY&{V+3?@*BLh9{9Js zH4l4*N}txaG2Dg&9)J6qbhw{Hezy%4P%;68Me|_!qU-;g1|_d|QW-X|#nHYHg3VFd zv~WtenMsO=$EeXBbVbtj+!1U`3qkMvl_9TeB=3*NGC8yKj zy<6!}{XXzzW&_@xctDz_@lQq(RB;ttVFf&#=x%G*)5xLDhmh`NXYgG~*fK&eo@Jqzy4wAN+Ev*pl&r|s2^-! z%iK*;^%Bt6FzpOtQaI+bBXCu!Spp%Tr_U#sU4iqFxPOSQTp>JN^{D9E-0vFDZhV5a*uoRxy*8c#HNs7}M3LL6a9GI+ARs z>WeP#0n0z|I(Ad%eot%?6qN$oqGQa$6Ho7RTUnTBYg-(E=zX?l+fF;7OX`GY56NkZ zs!%U^e+12S-ngF=Kh3X*eZ}KkPW~}$&~tPmQ{!nlUg5s17WjAa>w=Yw3N^mLYn8MP z+(>%oi%9Lg*k?Xj66u2RuY!-n5UN^_Ea~bae_^ z9e>J@Tv--v(Vhq_Rs&J=hF46VhKU6^1w{_Qdn4@N{PqHepw_<;EL>F^?-t)TM>d~kn$@M?4L4;_Fb=&*XigHW$I?g>STYLy}- zG9;yJhsbT+lVO#W+9cij-%|e5w~7Xo9Op>N_?)DRZ0KWy^;wq3{b!e-K_n}y&*#z& z%cXe9n6RM+7g>cgeSJv33K0ZY9S{iNP)$Um7LBcS zk|E&G^0`E;3D7~P-Gs?yLo7Sv1SxR6;YsSo z+Ex~`Bow4S;=)Z6t_&^#5;`K7aWg>aWIS$dudfqS6urWvQUe_7;5+ znZeRQ4YnbXSZe&VuTFU1-<@Z2CoL%XA?>qg{jX8n(>r)*Khm#U{H{mChwY>HUi|Lh z;0dYJG4fx%+-s{iWp2YTCV79+Z&GiL1<_jV?c!5~APb6a`f?W7x8+=-MjRx#NkCc= zwRXB9#-Rz~U~hh=8P$O;JY=NsIs#jZmnX?&XWX7aKj8y!<4|Yvyv=d6Z9%<@NHb8m zd=ATvA%N|^^La{HqSn7~c=0(9woxP6JkFd(+6aP7^d+$h3BS%9L;NH*`Ze_H78sWr z$9EYFjs;syGBJ@Gr5%v*LPggpI9Ts>@zi@LcC3@gEGfM~#+7nLp4dAxdN9C#(iIIy z2T%LDp@@Omd^N4S0J!YGadJl!VbVkq_xx0)70Zy(Kudc3E=&NVHTjfTG?U4V*spk@ zN4C%dSqYe0!G330+HCDoZGGgwh8+HSE-A25Z>g63fwj zFqI5qqt+HAx-fRwJnh-YcIe{9VVFvm+EJTb5lXI_t$irlsIu#-u*J_HNBjn#iAAY~cduZ#KRJx>9-n z73t^>VSFSm0~XCUEOSo9#kUW9y%&R7Zp&1$_w!M=7bErG&%O{2i+#hK&>|fBHCthf z?hBA@g*{q7R%nDd)LjB)&>4&uFY#u&!msYQ*JXLd&4^knGjwzw;gt@%lvL@~?ghe0 znmtXZfcQFauO}OH$5^USMAkdKJ-H#C?E+nlCld0m6Z^!YzhVeoVvXA+3)%7{ZF~lH( zY!#3wMO;9gB-3GOrL+4MP`^r}Lxj0bXB@QJ*M=3YLxuhG{$c=xZV_p)C2`+o-&=39 zrp{p2OS62M&ErRGfWIl#lE6m5CM=~T$ zm=;SuW(8S-$^DU8&EZ(SJeiXJS8 zj39hziF-%*6wA1ifaC6~2yxf}n1!~kZehgOdH3t7OBq`}rSaE7LSs1%BQ6Z6_utr1 zaIwLwc(8|pws#_QKAWEMDPFDL#2#G#C|C0L0evo@hgL38u?Mu~;}K3=UvNVHH1D zu(B-39Ya4(H^%()rXt7D+2An+?(B|_a&=J~X?Qfxa=CvI?)pwfx?J)|3^)c@ss7ls zIy!&LvF8Hq#RK4Sn<-(oA%6j=n@YfKGBYfT-{aYn)W==2BPNpAR_+56y!=+ZxSeBm zSxh8oIRMdZ=V~Ou6Yq4|H~U?4eO4k(Ei;D>p$0dko~Zd_zcD`g!#6~9&7nPIXDGSf zuZVf3-RN@I_eaNOeh~zDEF2Es9UYIkp`T&;PzJ!TDAW95n26?Qw7yQf{O$D8dq}vv z(vhlI^p<|)t_J(Y`YGpAG*B-kCI-l>U23V2+Ak&Gycz_HjvUycYeKl?JAtK*Ie6&Y z{&4wvobOKy$g_WH`SE*pgNV0w?a^V3Gpb6;#g8p&6Qqcvt>0Ia2^syY%MW^Oj%Rg9 z#%JhS=&^yxLLLUvF%F9ULUa@+xP5$_$cZEoz)Q(0oT(4be*6Pk)zt2bHP_)?KmK8b zx!q2k^nP|@jwVqbHVr|t1kP(Li?nlhSv{_~*&G-$^HZuKw&U^o{1>)8L2`Lex{^tY-wN$F zwV@4QYYuJjM3YW_caYfPV3-C|qqMg8U9AgF$;uHf&r+f({a|92>skzJ7t;@%CBsfq z&#}(m+J6fx!t36KYKD@d&wdhI;@tHwjK~YhYS&7QSAy2WE(nv08Y8t%cJqb1vp9pP zgCZG|evwdAnH`&Ny`#YsI9;BROe*s{6ZMn5O8JY6mAw-qVo>!i388#uE9+E4=Ve@l z18|iCx#sTeef1F$159GZWsmTFiuGVdjRjz2gM^P7iGBRIziiS#RS~gGU1q-WZEr3g zYp)F3Vj`K3dxnE0Z-#`5BJKYViHX1;99<`V`OCZkyvJ{7=0RLXUkuxJZ`cP9PM|iT z8;EGanc_9?GtwgLyfKqQF;5Xq~vILcIuc#fCS0ozoot4NVs9^R18 zXmH5MvXLA&6iIYC!X~oAD=qHNAvdV<)pY#R{n>8Z1T@g&Jj(@UG?+ZXJq&zJqZNIKODAD^=HzCvUp1C zo3w!0KHi z?-&$bnn1rXS6|ARi7x^{=f`aYvL)BS7duEI`k6l-u9x;Kp?hZcf2_~t6G5XiMwI(i zTB%RucZF)R+p>Ja&Ti6z)$5kn1(b(K#!pZ5^L-M83Yo@G=QWbIXYjCAD0rICPg6P~ z-hqp8k6LWP)UGHHTVcUKq40E1nzR1#PErq3Xo^m|Wx7Vk6-e%Fr|Vg!R?}8uYrTL? zhcf5DPb}6*vlk=*0du<|@5F#Y8%p$YV}LanA&P`q;Vgj#bQL0U^YU(W)?wN}USl^m zy0Qsda_s0S`24)Y1(XQrFhSGsdI?T^YXnY@DA|UYLd4syiEErNWpYcULhRpo!xh|u z_v~KNPSGitZZ3vzrr%2EA^k;Hf*l2g z0-ysr46WJzPX{JU1Dtns)8^r2;?O#r?{vlw8>w+OWy_PJ3fQUuP(yYEHmtV#6i&i- z1h&d8*HK4V6~%^FMGvSkEhYO(lYv%8CI;oW|g=aQbY3 z93x_T7}7W18S5C5O@@zP%384q8OM-#6#-JWW;<|O>xvnF8`N4h?3xr@9(DJcTrtr6dUP@{aecER+a)r4UN2LokhjDIx^3g6} zc=Q+hv5|UE9w%$D!ZrbfH2&;X35|&SBd>37!OQY`qqiNGR%6!@w0SyL-xsMHBrjks zo{YW^(X^N354R{J?NpDs3#XU0N5~*GF#O~$v&|pma-Rya+m39U@4(oBLhL;HJc)X| z2Mv37l-s8OfDv*%xzWL6uG(Gz5Tyd-`{aV_{>*!M^hs$!M`L}=yVtfsiMZt?C4ILjm!G2Z_eY(6%Wx&OG zYFH5Qj|f!@2ZD~bjPA-m9(EAAPwFZAjxXIG+qhSrBW{GZEZCdt#x^O}7iA6$#QgvzK-#~z0NASlW~!S$ zvj#HnOW*cbm0+W~+3kklHJAEzFh|c+zA&wknj)Zd4Fj z(DJ0qGibz%?^BYif`bQM5HA8X*_3as2~YDw@XO)w8x36d1fMnIJi#q(Hp?xf3^wn1 zSpi-EoA7AJDN>HUB48bOaVhEdOrW$KxBw+U+P_r${dm93 z7tz}xxg*(*A8KRo*v*L4MfTaR*%@OtTx2n<>ELmP_A{3J^_I?(bf``(j9(`%P>PT5 zuj3m(;C3Xz|7>8$kA(e;@}pb#s%L4qu%ZNNSZTE{wUVbaicluchEL0G-N{FjKsS(M zx2J>QMy^*(Ha!_tapYjit~%sfb{50&Tn!&#-^e>v^>^^oj>~=+a_0)U7qQDSb}vRQ zu*#1mr4x2|X*s@qnzwnOyDXuQ1DQ!W(+>$Q`f)Pp`o*F-Bjt*ZAPxR*HpK|J_?hfn z3i}fG&SrC~x;`z_U3UZ)vduM;pl|m9V%AoQ+f(XLXWAeaV-ukc+x9O_N*t8%1%aVR z7V77h1Amwo7AgU|;GEPhLB|@lG%3Ko9v>ynbZZ7YhG-FqH%WsK&PTYt68+=G=pCO@ z^vXi}5t?q7umUnvS{(ZVioZYC4Z@-+$F3K33;IHY9$TAF^EH0b(G;3IT_&C*KLZBR z@jr$I*xl#bZ(s@!`&NL20GolU4+;*EWYgXmqiN#3pHeb^ede6l?G{dpym+1`H+D>KN+C8jYeYaTw#nuT!2?=anK<=a zE2UlUAiAu$7@Wb#lHLdu@sdHC6D2BHu+BI=noNqD5b=$GJ`zCRzZ$zE9K(iU8~oL8 zG6l|*4-VY_^Z#ZEm70FDSbS4h1!CG&+?^&13x`&^Mj8XOO?vs1-C!W^YrKCFpE~P# zw<^cS!F$MFtKI&KT1`I0&B1LO>8t!Gk7^O9xta9di83z+BqZ4{ zm-pF^Q}x6&sf}9L>z9w-dAqKr49R!*TzeFCSct%@yp@@`$H=I0`YSh02O~&DB1rLe zwD8Fir~57AI-USaq!xQFWuFrrFHErKrM(KERDiArfgnUgw_Z0tH%PB_iIys5OrN3xC=OsW zBC9yVY_F#VdWKI%FuUXEMGU1(xn`$=yxd}~yRFI~Q4q+YM1*`7J4TFzqTquG>=+%s zSBq<6!wa(P2roTcP5oO_n&Dv}d{)JJ(S*aQ02kd=y&OiEmmqGYR8DQWxRxy>IH;2& zcXk{63fmLss>@(26j&7M&GF~5Vl@=}*$-HTmb{T=a_l;^{#SZ1GheUMQwZB5*~<&-h1Ov95j_G=~A}n8%>_Sw~Cu1qmYt;iCsG zZf-=}X;RYwKf{aN8cpFYn6efl8|Z*nMbC<9`-Qsy7?e|!tZ%FR{myVZt2-tBX_@7N zd;_0yFpNC-Mk4d{lUIlM#+uOeR9>;qvS3xpw?Ys}j$hZNbY)TliJI^KOd7)yu}6JG z=~r(gul;EnH(TX$7(TY#S6Y9FAlFzxK0k3P$F5`KmM{@=Y+~xoSX~#%$S1}^wBp4& zr)>|jLhzeyMeka6TpZHVb}obL^lReih$Kez_Df316X=r+#ahL+;uCf2I3z~8&kI_a zcP7yCW6y3~l@M=h{wlTT7;ZPz_Yy^G+kn`eFm%|s-rx9m*_QnpSFQF_IJ0c5qnt7| zeh8a^Gh8RMa7SqQip=z-pXq9U;>y91k^D$g1D;3QTzw|+p*)Oj_?7J;)j(ct&_)%T zc;wUQ5uZwgm{EE$Jp-{qMF#`_b&qqB%XYDT9Qh+bx};hn-!f=s3F>f5lik6Na^5QX zk&=5DV2xF86DwRO?@mDsSJfCDq#&KN1t*0GnP;-D8MKlhJZl_6!Qnv|v_tvc76)XWG(O?^5c;Y63u_g{z0#g1 z#GiA_U;?6$YIYl(e>*<4K(CM>-jUx_p<-=WfMj-|8qoH6RsRxXQQ;R5_0c=!@R*|C z1iV>K!0uX2LlUb&B7rl&m-N+?qKC`8r)&$GDtF6ib|#pCDsnR(RbO9qjkt`1@0~bq zA0tOUg+!T5q!6Rfr~YRC3(0V+r46UK9BlFH;8Z1x){vj}xw14?wgP)TQ@JNXeuQVn z4-|~1;d*ehtIq}h-Bw9#o^d=8UT8X2QR*U1Gd&;#eIkMulquwqmnCvP>e@2t^$Tmt z8-Wso{y-~ohDBi8EUZJg{#Pv$K)RxrFA7g;`J27Y@aASj=5Z5kg^l3t7xwd;^nF-J zS0?LUU_~Urvt})HF8+)wYu%zfe=uR%B-Aq_4*a#a6B3yEjENFKRc(;LO^OxPVods% zIx_9@K-*>kb=4kI0PjjBBrxB`zzslcE%*}@VVOka@X=q79B0{FJ!J`?^$2N^2$0lg zO;F(Ab%Gnl{}McoXUj_Fx~;-J0h?kuxGJ)gx1+RQW6lDig51MkUNiF!Jd7Q)HcLZu zua;_;tLQ`!8QS$%>~-0>x{RgJH{X$l*jk;wDyy$|dihs?|3oFDTqG`Lx?7!og@$If zcFR>2hjOfxG_Z&pX zKlao1y&#B(@M>xk-FGmZlVU{xBxY8%OY~Ws8(~Y!%m(TH;e;vxWyg?5adP0sNuTsj zA^eXq#<65pXukK)f24eiOm$3PpUq;qRaqxkIs$`NqW>0J@cPg{CoV+`&cq2Eni&F} zc2nO3l=OLhV~e3!izj2g^)+3VN@@qb14Al6;MqAM|3`&sJEuh2Xd~%O_jA@mGT-{f z?hHJ`b2ne%Z4?9OS~1#&-{Zu8F#^K4N;RKrQ{=5?NRAZPz(9zljHPjEy}xm<>BV5O zj)FgEZW9hYm}M6+CHD}vp1ImzHO`kR{yz)@}Lr*pX8 z!ZCqb-T5piw^mtc<%`CqJV(PLVbe$YDGaJ*vYN!0xcW7s3Jd%lTbhFZ?0Ts-^0ChR zOe+b&lbO2dcZ1nOC>Dc8Zj+ENg`4yr-X)2(?N;MY--tbcHwnx6TWne)XB9zp1-3g8 zu9SWWHbW9WEiRn*R=*!%y6E~4G*jmk@EG-K;Jf^(ThGoUWWzo}HtQQO2fDGPym>%9 z@(Si#x5wSzpwDh5gC?fU_ge9_M{5zTxNo~VB7|5}Yq3%`r2u~H3#=~y{!usU3@d{M zMhpyiRGet*q1@lay9;kwIc~gVfO}{nMlnjs+>tr^iDW96AsY-gbyfumIK7${kUkc3 zC7p;ib*b!&^hTnBMmFq~7r2Wb_xPg{DzG2!ge?XW-;jwUiMB;g@@z1x0@lnWFPMHEt$FvfXZ@mhx3sL^op1}I`tFLg zr|sgg$quhYEbsTDw*0%Bp1wgs?>v8c61xbPVA`R_H`K9agtG+n`l9cXfzXtNLob21`!Z<{8G02AbOQZv_eK^q*U=Xn@Y}8+4<$0rxSk^u%#jI@&B*;MCQ6 z(~>~{%uBkKssq<|1$pvtO8cuI{!)2EEnMx)G-aUKLs++Jp@335Ebaup6E`BAxEUax z?6suD7Z}$OhU@2;V|7_O6i>X+su&q&ff%_`fO2!aV6?>uYSKpr=0`|Dc|n={1_&!! zOC@`>Kc9VM@LTL>&OaVVS*)c&5%3WN)S>*6f3JYfi8ub$JtDTw-3RJ27LLw^kD>oZ zC<&VwziQ@Gp?EM1dXd-U%=f5uW4BoFo%pj%Q};CF?I91}kpdiCl1ykV9@*cjw>B)?yvU1WtM4p6e3|Cn^???qX-H6p(NS+^Yb_d!{ zV4H=U0}v#Ic#9Q#p|l`-`gY!&+L$YVbQ~$asqXF+{Kei}D6}9VH^04b;z)`xSJvT3 z+Jyp)v6II^|L7Jbu1O?FLqfG=#UG4h}}ntSuZA9i%)w!6>nC#7u? z9lA&*{yHXhj**6LOO*;^@2zEuU+lCiDKX+g`O z;N#j*LtcYdm>_A$n^|X>J|(4Ip7=vF`XE8?8|MyB7mtSrra9^u2@}i&9lTNaw><=2PJ(TxC?nu=3!`#0NGDRO>wh zew2;E6x*~4(7o0)_+Ut%a{@V4OC^q;+ViT7LDm;pRsHG}KZTZ>7D>=~g*t$Blft%= zk5)co3^m-C#}ER3e9j#;?bKm?R||vv!jME6GM$v3`^~G$`20olfSFSycK5UcrgJ{c{%tNJmO0?yT!@EC&1?YL zw;8LbY^nOA+$_jlR$xZ_V~7ugC|$()(-6eoNKI!AWXBb;qNl1MI&ZXXOW;R{D9lQN(gUfv{1+QNv=+Vufb-5 z9l`AF*1^d2RipV%nQ8^m-6*h80Q4-J5wOa+9#D;e4JQiIpHN{<)CBsXZR$pcdkn3hS9{7W@`6dDb=aF~}K+Tnr#Lk)20rt}m6LL3FwY%-dnfpz<9Ocxke*HWL9x^G;IGA zNu<_GfX?HD{k;=7no$8EPHBGXEo`j@Tc5+VP=E(BZ~9cM?vVGP1Ntl$!+B*>C-N&~ zRE~QhGDmY9N|XX<+4pU{g^>2#95Yf2i~q3=X^-nLd)h*B3-9-jaX72`65z)?ZBl3c zAnMhC1U>H$Z$N4lG3vVYU>&3C%RAkr4OON6TkRkx0I$&VKT)rj!_sAw_f`IBYNd6w zU$1v@tx=@pjQ4x@@KW-|iga7+%gKakk=d-2Q0W2Mv$vkJX~=mFp)Xh3xqWd2*W9#v zU-qkBA&44uMK0`(xK|o5T`}D~QxxkuW)5@KcXuTy{E||Df=rBBzjp98VBt_^!zt89 zBKhuXAVb?s6BMy^2Zb*}>Zc%Jw`nnKnVnsnf zE@t2Q&Yj&IS;4~-&5T%Kh#l*N6qt_~Jl%Z-P_34|!_^f&rkVz!Dlva$-VwL@##Xx6 z!I3BeL(L>E^hL71ef*TSvL?Bj2G_(QhHVlhS|JQtYh642B-Qm?~xu z2H{cri{mZw9F79Ewjws^FEM_B=Z536^jtijjvx)|)wgH`(jkxo>J)CyWDz92ARv<0|TuqEJ9JGXC(jQ}B|d#6ucIYiw+zafnD%Q6=YP)O!} zd%!pfp~RYNY{A0wUVZ=cO@{Xu7rn>b6!)YoE;p{vxK+73&I%4FtkTmbV&?6J);Jqx zsL{f=r&f8X3}J;N&|N+#cTN0TU` zWNGCYQG~D(EFGUxUT-D}-iz}eyrn<<%Op!CY)>;E3Doys9;I0cl^oQcWeMaU#6pxfFaH57E{n)7$6vK?ZjWc+aJ6!c7XyHq7`0~=W#pDZP>do+eJA| zNg!*e_==r9sS7&tm=u}A_CcOe`FGXR3!x7`R(H}B) zLf3#c7n7NwAPPHpxWfx*>qiNTeDUh`^VKq~k;_OAgV#>UCM>*<2;^Og z_~~1Kh8aq*d4u&=I7{+)Owh>vud0dZ*pIcCnq^13*-n4K7Jw>aKqgK)!Oc4+WCRFO zUoBbzm>iFKW0;vK?niWebvW$Y4LQ&73K1v$!|b&Y+2QaHO{Tk&BdcRlFW1-RKu-jy zF8~gGwa^Ru&G@GKzXhun)w?vK#J?B&hNfxSha)`kO89k6j7y{(1EDFABv3ImgUl@4 zlS=)QUPMPB1)Hk_SZSk*RKli?73yiKb6N8zE@NYxaytgbVkA{ExGUbQomjCxL&sfQktB!S~^_%oM`acn6-vwVaPw4iHd7Jk>810*kgq_J|MizSx=i1#C?9W;>#Gy-x%84y>gcaxO8VQ2=9=KI?`s zfqltq3v9qTvL{y1%Oj)4BR6av?xECyXPC1VP}89`=jM3MM#Rpm+1m5NMf-5l-b=Hn z$|9-0_dw<4U_NNIW>9~VW>6buT<;D5@Vs#!Y+Q6cwJADO!0f_tSBlU=(Rli09v?&U z?{G5k^!X%{ToB>-u$(H)rQ9m`#X@%CjwEFwwBv=ZTl)eq}^n6+LYOGt%(k-g{ z`B<`wnwY8bt1QTf;V#>y*=awFVVR0}XncUL&(6m!9PJA9u=6Hsa@zCL>H_f0;w-8U zo{Hy-+KZ5A?gR@BHJMwU=WrVo?S0FuzediId#rj^j|?1KP+e1V*L;|PX_=#iy}T5K zj7@$0>=!pvum@Nf--naNnh^umf$K=#TEMI(CJJdu0*=fC5n?wx5v|QX&8S+lo<wv5=%_L!U?8wM|wzSmBy=lw{@cPm_-DkfVu+NN<3IGh7&P$gXy zLo^W~8T6#jj!|zsW>C){tgIKFm;Oax#?IiN3Ix79Cp9PzilP`phWKlbMC)IWo+6t%o;J>EVl7pu z0MFE08#^DJ0s^MHMGIo0;G9`OJuZ-NbXd}>JIqNs-|{?llX3&;vCC!7fB_8aSFN1X zy-Zo?0^fw7DEXN+2fo~+&BC&I6WoBcslf21FdKqjbAECLAB`xCn3)O9!B)-_?8tSD z(}`P>>Torn`$b;ei|RQrPB#l#PEW1!VVOVVa?Z59x>esRqrhtGTN8b6k$@dNIVhIh zFF=dw1YH)|F28_eS4Bi7m#vvEORz~aF>b=|$;fQ%}HN z0pRjO#_f5f9=GpozeKgsnx%Y@GHv7Pe=;JRY2h+!92?mp6ox_G+D5fI_6`N6DA0gB_j*VW>*l( z&#CiBE0kZF7Zm!dtslQ`FNHX(VaG?YBr=LE#{a4Sm#u8DnD4kEC<>cy2NS9nwajH817eSagm|ObnHF41cQyu8dZpOp@*xl` zEoB{&)Y_y5g*mn+{n^By5A2D)*o{Zi=-dBj+0+vrwS2dCFUgibeL~sAWEg;uGU^NQ z^E;{^7*)Rh*`qmD(r6uN02l5wv%#UYkcZ6C)hHvDNK4EAq;IOeS{(lj!$-41@MSk3 z8Ph0?6=tUFc+zNXfWfvUkK|=9OAfur6G=c694+BxDdQDA2niX4zcF67hN){Iw(`9W z@-AFdXzIrz&F0}cRJcj|0W*E0F@iD3wV`)K<#h; zhU_Yr&ddP~hC#sEeECYu6%`sHszT7|KlcD_t3-?h1rjT2ufK~=v{M!q+19KnECrmR zz&M1(C=92P-Y?Nyv6h<7yoHi`p5{k*m-CcDFKb~J8zPxIn2XdQr^>u)=-hsl{=3$k z=}aBkOry62oKB2k3)feHGTaTcny2eWWg%Uj2_@|dKF+vlfnwKe&fF&Ons&uMj`mbs zu9cn>v{la51;>%1)u}=0wU|G*r*<>zz^A0*J~U!!4IHc3dR5ajWTv42eo;sssSuDr zY2NQ*I9%#b-rKC<2Fz#&vn{k&4-f_&8(*w{&Q~}2+Q0MJAuvZ4UZV*vYVQcc)9~FG z@wD+3n@F1j!S1y}Z(F^W8o5OzWc2%nk$4L*k!DEzE>vxGOCqjAiUs@`D2VTsvzu0H z?h-c-TbKhOq{jeNfCjrQJ`D1!zN_V_(G!%~X{G-XPw5m6iDWZJ`J3|3J!+p~SS!42 z5(+aAoWXz2vilI%nhnErm_T#Q&VnTztA&OhaN?m*G zYV-WV>800nCm5YYYF6kwipk~*{e>;Z;)@bmThYy6jb^5{1>R}=LK)fN-hj8ch6KCW z4_yKe6*SDhqc>#&HTWZ5oi)UfGX)Rk@&B+bjp!7YAz4-fftBf%L~4E6o@;_ z3|d`fu*8Ry-&2-lyD(_&V$L8IzEs_0a(WA+gvphv*+abUE%M=z=?1yR7>?#%tKPv( z499VHM{$(&el07sDEwDkxy;k*rnTsHuYfH{7{?6e(_C$$k8mErFYq2=0Jba0kKDBt zZS;=?72#$aeyqF^Rpx;FtPJRSPYMKnAU?Kn36L0{2}VCKAE0~Q0k>8en>Q#lkw2B` z@>mo~fkUSh#{Qfd1rhKS!t|Sjx5ze{Hr|0y@3^Rn{7E;lL`t@sJxxioVsaUxGa|o& zuF^RNC=s2-cHbEg6-1N0A{&ea_eojQRn!PfioY@g%Z)+(fgRH(DY^7uh$UuvgWiIj zvpNkwOFZp)h*?i&KlWGk>!;qTbFTnEitgnI8_@atse^U5rJQ#Cj!z9;2G4HI0e@MQ z^A0QmxeT}exDhdMk7OkKIm?gev`LEw>@bwU@%t=RFqEqyoLby9$2N{^msb9nx?9_syaOu5Wc8rAln_2ty!FdT+15>C2#`UwM-i6ZS!nLS#ii zRE<56$uZVdiZ%u`q0$7x4+kRNhp`B9#d*o4ZS29XsEWHze=^vpCjGaJ0(;|cW9;DY z3kb;FmL3RRP-Ksn!?AR1CHkPTjRcmVcV;T+9Th`tM9$y_NV7du`lD<%@OH4)24$M; zT^rqiA>I=?Tz*)P}OZ!jilO1+dOM)bP^+rL^mHq51}#6{nK6^U;n~ z2|OPlT17Z%^P&!Y?g3!Fqi%#Bl^XR{` zsU)u#hJT#Qz+KW38{mM$@lqpMccF(ex8B#z*r%0d49BqPy>0|IBhzCvBZ=0f|qd_#0XdTsJ~ir2~;vW>Y%>)gVtZc~WRy-2}i6jW1q zbaT^KMW_v7SdaV+KrEkv1on`X!rvl-58Ts(RauEZqN!Y?d`v3#MdW!mY!6=GgN}o6s%E3kTwS~ zFX&rOV$o5wF=qEI#h|;5J#E?X!A<54y!E(Qmc!jsudg2h2mq`e?OJJG?+<&ffi z@^oc=sa5yG*lPofAEW0F%Q%|43FmGyzLORN$N?{MROnfjUk@e@!V~NArP>!m3u0@b zMmF}20Md}d`}j)C8B?Ljbzw>|7~;CegEy4N9%Jr!W#u@-C73Fs{4cezq@5pNO+ow@ zoP4`&YJcqcuqSkvjacuT=sJYf1j`WT-X$tPNwW6M(Aw=|WjC|S1gXGyfa=xet1r7V-zW#t1E2aVQsV7&U1$iE|o|i9W&^B^yI1Wb%bRL znuu|5T~lr*TF_#hz8~Kn_W|`%+?w{ehTbmu;u2sAX4KfOqEtPIzAwogLg#4ZMF`XI zQ#-eoJvvt4m;2*b9d}Bf?yQx+$lXA525)&0>POqU`##$DWK+PFd`p(Odhh?@y)kSv z?y@fQ+|ZwKsegdD_=o>Uh?@)$<^4K6yM;1`g<|>o+nZ29B9aLFc`mlrhiW+f!r2Y7 zchCBPPw1(Vu_cb=31&zSh}d&4+Fe89*O`~Uel_70muu~xiOKWVssP+fGiJ+2h%4H8 zUT3Mt6t30wx6KDAtg^+ZX`7&kw=eloTNzP4 zdhKj>n#aR3fK>F>SPj|(eAE`^@wCbHf2HO-8y@Hxzgj4Ylitk8f6R+lkhtu^0ZQdZ z1aWDnfOu(J>z;HeUCP9C$68i!C=Xyxop^5Xm#3~ptH>a45RC(7?d<3X>q>`suIXch z`j^qmaLk_htO3+*QjM63u>P5{sjBcZGAIL1%&4CnB}Um7vZ8 zuK(N=@v09locdO(XjS9=tYBWWh5U-p5Rtr($IB_I>mp<-O+YEj2pLeB53b;)3p`D; zJxUb=Y)l%cTty=wO-B}d*1r~2ip|}D5`6nIsISPp{NjI(9PK z8u-XkkDv`5U2wpG(y@ z-sl(8%-N|+cLTsbER^9Xa8HiT(Sd}`k-DK^RI2#PXFEdh2YpwO58lSY5||Ridim{z zP+^AhHk|N@dlUxzqcOP`!kb^i0AXrZhD)NCn=tOO>X$M>UG9%|PP?sAT+I|saR{we zyU!Ykp3Emyvd)2}RW)o1Og6++zN&7U!}pP~bi`$h#{SeGpg)y>CJl4y>6+V!iKXNz zH28I2`}Q1#2}$V^+eo7v5_HL1+l~ahg5HL~5Y;QRqnRew4sTsZ56GJEcQQ@t=B_fV zmIQJ*u{bG`(eb9}6jF&$uXpKHX<~pC$_X50yflmWT5z0c=dgtv(<8P8F?S7{P7;T0 z=e%XIrL58UT@~sQieg_os`hz`MG)?%*xm>dicPl7KG{Lk(5B1l$!!!iI?t<3K==M1ScJE+L@>6D=vdAAkqqC;Ma9Ss| zQ-qFyov7{R(>j7DFM=tZ1jAPoFhV*Td1YZ@&263Shql(gsmUqHw2Ac63jW0>FHie~ z0#ald`en};Q#_0S-vnk2?FEy$(tkHZ&PiAgl^Qot-4!VjJ4`=gAXGcS4+1X+J1FAm zw35hEj5cxHazY7XT2@?gtZ(=aw+BX@d8|^_Ckl&I7eG_Fv8x)L@buY)P3&l$H|5 zZKu(|@JZK7YhC|Cw-pWCq~dhRs!L*`Xuh+uS<9$&ufV{ zfSXiahYRi+d^HKRHBg(MR~g~5N}y5{o_#*B`?LrM;}v%HY4AbjivPHB~$6QW*{5bkCY`w(UoxZ)118N36hnQ!I4umhOSw6EJhwJ3A_#44a?Lh zs%7S~AE3Bq8rXG!tBw)x@Ky?HFJ#8BE*RElQVJZrosx{7nPB8~cTLe zP|0pR=brPQX~8&h2uS4pWHbuuI@vcsD+tHL1DQVAV01>h#nov~4p>7M+NYUWTM&lP zhCgpBda(c+H zHi$LcEzK{$>eF)Ayae)twyX~o!CKJItXzelOKi+pyxyk@z7ILrb!rb<)=8$1 zRHlCb(@Pz+pX{H_^^}*HQ(I;jtxCax^WWv&?L`2fz z`v%_Ih%RVPYU7KimaSqg3(0c*XtPvz-E5^Gx~H)(cLH*f6xh)pPSSuk0w ziN%chpZ*BzJG|mvc&^+wNj-505Sqm^9$->yU&RbNJ`nnNdw-3=IL;ksPlBZz0rS;nks#muSL;kEXhEXJ0`<_-j11LrQ_XyP+`j z5I{bz665aWnN=gpy~gk5;aU{W*F-rPC`0kzV+%#j0wRk@C28q*LfTuo+ROV5+7BqN zh-BLxex6AyZhBhBMTVYjh-X|D<<3z1KRp$F3jUn~egbGo;8nY~Kdv+?LT!S3+|Q~# zh+pKwG>@QL1focU#{kwa++bK(TmE`uJneY|)2@5O&-q76ySMcFlBl3+?P76hP*8Vf zH#O_i2t&gzaf%W#n@wRC@;9eM+r?6iMWz5*ZwMrPRwZ7vR6u%&%3xlXjE~(%s&v5i z!YHCYbu!U+S32dmv_>1EM0?k$n&MHX8rV0VK#g2ncKFiMs#8ITZ%#wP_bosC=&DLbN*5;dJG4!9WM3O3s#ja&F{UuD` zoPYkej6Dthm@a59KR6W5)S_=h0RKW#nj-5P7vU6(yK5y_pQZcSaI+Fm43QMcx5{)W z2~-*AmyadHn{kCuM<0k9M5ENH-!r~ZfKgWHvYcpINg}emsvTacz(&=bZFV9328sGQ-gLxZBf?6!d8YvGYa_%e4X%fYJgIa*fsN^SDcC223!@Cqageq5ERalFs@YPz z#@EJX#;u%OKU~|#O#q5UT`wjc7ZDh$6g!bV$a$*mWDF~!hA5Vb;)@x9H^vJe$8p0l zRE%%N1gZlF0I}^XZcw$A3EhpYOP`?lTL!(OIAuAzKD0#s+F@35Y)qaDHn9YD!2DlN zmt1(;dHRWv5kRHD;mFmuu7%#b&!X~agQMcs1@BZsIe8{@yDaO=s*nrjvzA0vrt-WP zAP~zpJ||jdVLdkn@F8IBZ33NpDiTZ2Ig!BT_@pR^oun#ylvm%O8iyy<=_D6Mddu05 zp@{D;J4j;$Ga~mLkj`MknhMl2i=(ZuTBsQiG_X{mb^GME*h9N{p6bqFXJa(L#h?)L znJ)nFDDkuNcJMso2v6qwH4qwn53Zpme5^j$iTsa;mjK zAk~{RNQwwz_+}w%W>&5SJSk-)SB;gI4Pc93Q@(!uwYiZDZ+TNb^Ex`$O>`MNM@-L80RqSQm20;Lm+g zQ-T!lPt4m+gC}CRAH{J&E>&W9B8O>6eMFBL0EfMm1$c?GwLQkT`eJ=oaW4OIMPY(&Dco44SdCoxtEp1N2E`$2O^!PyC2PN^9^Z^+yH|;>9}3PWM@^s>gzc zD#nkBMJ^e4tD%&X_cpg%i2mF4Ffw)r$XwC1p%MqTX8jmDvM4T&E0XsMh6JGYneYG_P$HN%Vy=SxZWE@EC!R)JudFU(`N!!#;nRJ;WCRjNE0Uc9pYqCyzyG-W z3;;Ki0TJr<=}d-;1~T;KUxXPb&GLxWnRZEAHXr89#?$M=+0;|rxoI8B* zFzDfv65(5CL@d9r(6nG?X1RE3PA8TFl0`r@g5=?QL;t;wGnm~f=)g=*x*YcJ9Akvt zEo07%{TiU<=mD7%!||8@kzebeliYp-$H+QUdbTo;#~PP5l9i#;9P~59wX35I7_~=v z13RlKtKC`rm97r5-w}E1c-@P%q}*MMVf?)cK){MW6^H2PSYCnxnKK@P@@=S_fwes> zmCS|e^F4>DxU?`p;XRVyRA7^B2ZBsn#ij8C*GRNG&g_nhARFegvmBpIB((yS)d3v;bCJ-h0TNQ_wqS?rY_Y)1+In zz-1kBvV)X_8LWY95I~18FKbDohbH;Lc*nr`BnV3XDh1=_nq6TuE)?RkZ2%u-Ja{mLQL_TDStn6X5O7kKu+(U!9&`r5*{Q8goY|0?p2oK7S<;& z|J$`;el#NqU1}Osv1r6|fXk6$I#*!t%-NVEP`ieOk|p~J9n2$XM_oA}%Y$$V9=So~ zZmx`>7!ofREV8ajgfhwS#er{%dx&_r>mTMDG+=+9cxyIbi+a)_EU0WGDb5~hSdM@q zr|bqcfMHdkQwAZzF!yS=rw#C#tjDUKg-`?Sv~`H78AP_J4M>6E$m9y5F0M2ahP9>? zak|TWG)dKCku1NqBX?1PYRdtHo5`x;`6dpNcXG(pN{bMG9NJbjY^AWc?n5hUK_@5J zHfT>f`tw;Tn&lfxTMr0D2PA5llibjfijEnLJ@LwPsI>Tg5Mx|t!%j;4;>mnHpFOFu z10Op{gfo9VISoMUA)o6`ea-a7?zSmQMK9d!*j@Z8XBuDY`2?+l3MKwBG{y&0j7fH?Otzo0uv?QH?oiKcm|%52 z`=}OmyPRj)Q~1tN zKheobbn$bHC2h+Oba{iC;{&NIp}4y8OqGYth$vQ@tQ{MPINAJ6*>HYlW1=?b}wi3vEBx$mZ`?G!WZemR)%V0nUN|vJ|f9#YPq|1$2 z&P4AFIV`V&Zy^+3Zn25WY(y)~T0w+Dj@QDe3n1t5Ndkv*NhF~~qH8#om3e$uRZ?9l zWw7@3&(BG*bZ!W5WB}5fwqowAX z`BfI>1*@3*d7}oYyTYvSWEvewus~4)=ZW>pV$lQM-K-rXd1Xtgann z6ztO+Mae+pw|(w5tl2FoUJbKXAa&B097QQm#}#Oq61nglfj+vVT;f!4^L!Hl+N6*7 z1taOE5k^|QN`cGa=NPPbC*$y~8dm$`TLY$wi2lF}px;ot(*6fX!e*0~P6E4OAC>q@ ze*1nRP|sdWPz7to8Sn0%PV{<9R02r;sPY0{j2pN6Ucc!NqtELBa>(&6;JKy9K zSUL7SBYE{^OF0-%b~3YGDo9ik#(I6$b;q1B$2>1pMOc|02Xn z9&|R)Tjsows3mGjX1@8yyF8X>0)4h;1#L=v>U-;-ZsH{<1`1WXXnyOP3+wELL>9%P=QzEejLs@zt-W8FVFmKE(1%6X7d<0<%irw7j z*s!A;YtA&4=!k1zbL9BNbs2SUDn)yN+HkN*urMiIUM*QrbT!Rx1Ow?+(gOI(C2^Rb zcY^3Aw^3cZG()|3#1>!t2&mCt-}|1ydM$Ay>8|?zyayxidq8iE-$>@r(#CxAGLwFC zNE$=4MyShq8>rH!Kd9vCBlAcWt|1W5TvsjA5ZKjkZcD4_HEyvEsR{SZKFEM9Qe_xH z5h#@7+No}8aVS-5Kj97@N0xU+JRcFbZ{8c8VktR%vA&NLXR%3@Y?*y7dy#x%@bkEC z&~s@$c{*)p30Dg^JP*UD)bMCaqJN|uHwTWa{e_?nGTp|Cra1SU6O_-Jcw$rg*fjN~ z{P;b5O-R5{dXh5T1?r;(Gt98L?cxi`hFhtn>wpZiLtn;D0|`e6#L`DEIZIt;H>HhUA?|!ixrS5#sZ~+I zA#AjZGg%p;3nDcDGSTiqpKON0W}tfiUMJZ`Oi6PY)ey!V8lwMARw^I^o~e>Uz??N5 zFFQK&USpZCQ6S2%I5eDI(7$!Ni*L`Xp+9uogL{E>Lxyxf5ewE-bNxSgF%5Ot*IczD zGwp^lCO*;sg4@2_8c0%cF^$y9>2aR_FXE9)hQYX^OE;@hDZM41`$7g5E1i zA$e!QWB1BBBn)e{lfx(brv?EMIF0O#8>mmH}{?ns#_b zO+;=4cnbtQi@HR?Bb2Fh=Mj~kLWUEo+-bzeg3E^65JDl+C93dp%+KmQD76VbN}dDH z7ka3G?4eZ2(`(L9Rq*pnc-Pksm_MQgb$un>SWz#Cles-%!wql^54c!W9%7)B^|bO` z+YW?-hxC^DnhDZaa2$L0rXm`J)<4vsv!64h*fq28@T!z3(zj~`qt8@FlGYrSPoxva z6zDwFx)v)nfnhZ2&qy2c=hJP)iP0*-l;FHS-idS>i7&}p;Bch79KrLO5A?T3NS;S~ z61%AEt>#T5!8#xniL|wlPrGHxry_;xcz~}tWY`=4k7V;gWogaV3#^{L&3*QH+^9FS z8D}#)C#|C2-n`BdSboPkCk&Jq82N+TFVA1@mK@U3WP*m@S!!2Q!+2AA47(s3z*xGb zIM9rTi$`*yeDo<)`R{Fr>0iehNlL;#ZJA+<+-bC-Ov+rg(GeHWcmDu7&=iSUMYlC7 z`t}@a|JO0NtyCuGKP^fVZj$fgJhR7G3T{HRexZXJceh`W_3g$?H8MFS8hi<6;P9d6 zSZ!VoC+Q1VspTadbrl#jzr&EsO$!YNssZo(F8s5Y#-fu>T|fWRFu0YpBooJ8s}Npb zgO+d5QSl+)T-0<$>g!wbBAA^L-9Vb-!dqMRh10lM4aH3PSI$M0cqWbG!Eaw$a z){}3Xl9H$2XIH)jjMHKEp;8%O{-%Nzy-{*Ia zUa(_X39&t^2|TYt(vR@4?K%eJS)RqQE+s_-a8(5X|5d{xS4mj3$Q!HeGMA)UMxkv3 zTN?W6jxh^}bsGfOQe~PEU5|7tqBoWyHiMG4Pqe|bmz!fNZW(GC?f zu5UKru5S*VNuqsnzGwMtaRfAK%i-JDalvnDzUsIZA5Ikw@)mM75n02jQoT)g=SYk&B{8%Z%&rIe~(?fJxHpYa^AXKy=88&6Z z45B|ci)KoW>92b?CJA6=n<6+rAL_H*-_3R1Jo70wwV!N!{d8xL4t)E!H+WZ6{mEeV zin`qoE{6JU<<7sbBBt!sA)qGR_AM**i`>pDlI5N{iimifqQm(JHvgS=hB|WWi3j!+ zR9hb3(hcS)qd4|`9)b1WFbyjR=7i^e32gf`lqh0a-&hXHT_r3t9aijB$+6AK8}rxB z2?M%c{J)2oEfe8O%S)_6R#XxfLEz%~%{IooXeQc5ilLb`s;;G?;+obOG}5b(SGOEGBpx zh%-Hv85#(ezL9JSaU3 z0m=5azgdXLD4j3c5yYOTcyINL%Q)^B$s+8f0(FMgS!FHx6{(5Mh&E4Kf*z;_UT?f+ z9J;JFux`11O35fnW#4CR(yi@R#x{tAzN>OqVXp9vut+c(9NltXJCa4;=dzk^&810h z-8$L-Xbck$+ud#M!wir{^E;rl!Ms%-G>X^?O<-U0W|_|v7aXpZI_6Cl!7SDro4eA@ zuTm)4+-y1k!cZCzDCG4bpW^UzarW4cO}5yrLZNWycee0IBrx~3y2T6pMmsL@`-WX0 z!im{hq}gju?2bfIEMR@Fo=H4CZJ6Qt?m@Xul*U`JHKvIhv|&?hltjwm1k#o!{sH{m z$?8pV_Nlekp_wg!lf0yEL7%DXxsYZFBt^7G_xOh1hxp)h;3}Zu3r+%DN&C^r(|Ta6 zG#;{K$%#~{tSGTTC%QhM0G{(^%y>TmgXpyPS8>+}-rbWgC#MIXi-iWhy({Kspk0E> zSsv_Cj)Z>R^7b6roVJm{uOE~+FgymJ=d!RfgOO=0k)1lsZ2XJPWAcPoe^d3AJ>;Vfa19@^Rj$!!>1f_Jb2bzeuK z#y~|ZnAbM0ZodxNR28=sxn1^LEB^%NT+n>eY5sg%XEXtXJmT~4fN$?$UT>qp(SuBi zWc4&(vmtIfTMFDH2s;dSfKM$}uVfw2gd}odDWGERZCsx%nhxa&5Z{zgOQsdVi39b0-$c7csFx8j zTG_#KPLR1+N7*Wx0*klAv;?Tg=fPfP1H==t_ZlW7zZQq`Yk$2c?&XW(@Shp` zNmNV}xj5S!lwVifZM<1rydA@lK<65yifQ#L-qS?Vr`kj)M*?U4xeI}*i8wL~Kf?h8 z@g)6s|14}>B85X#4RI-=9aq?}2!i;oAKH-Jf`R_OR}knFAnI5@JZP^~nGs6#OcDMw zjoR;pj?%|P3S|t-qb2IpL=g61pxsj2U4^i$aJGxSf42uk56Rm!Lt%{Kl3fCNvn?wG zU~~;ndb5f6H+4XVCBwmg+Lk+}tTxXOzA_yh>g1>f5VkR)m%f)UGp$8mce&y1W@ zX<{x^6R8D~9L|UY>Tyn>c|3pWsLg+O^v;GjUmLB3UacZL@Q|0zp z2AqxFhs9saAEjpssBI-ZHer;5+ul}nlPDuEx)n2*K%6x?yNe2I^f2XHGB|RI8|{yy zd!He$fHrvSoL!gP%tl@f0oG?gJfCi?4Cn|p=}tYu(LI*=w6vK`pVhk2m{c`uJlUq% zyI3<%nbtMVznZ+RcC_^8_7>Z(ja2*<1~4xt&QnXfUq*|(SrjxwkytdJ3|m&(2;Qqn z3U%1K*+ILIO)(B0P8B*~in`Kn;X_oYcSti?&RR&Ulr;?ed<`4H(q~#5MTosH{JAk{ zh%-YSEYGdpbKmF8RDm$QL)#hcED9U`xH%m_a+ZjrDLIG%7{0j4VAq3Z+l*L@`USa? zT~@p^pF!J6CQT|IYBqiBlp?K}h??mxw$V2^I0|L_U`@wiwU7*l0f>S_TVZwGK%ypA zKY%2M5pr~OAu5F&pkhZ@0j!O~2c|_*(RPbyid8x+I>Cbp3){N^aL0DoeaoXzAQX$~ zJ)nfDH}RRYv41oOW8#446y~A}7Mr+~cnvepCB-2(Y6u}=;|)zWO>o@df_4Z-ZY~5F zn8WhdPjg0w`3xob=pRYk%^fA$IY`pwFx+p$*P8?h$Y@gVI=WlJ`I<^*1ahSVCRzYR z@_Wu#iL**CZr3ZiMF}$eNl&Q4Pn#o81*2RJs~qolaUH2CR`HX3`p*PYknp(dS!<|J z3gPBqg&j{f@KHSJQ=Ve* z4Ec$tD9AGR1x@GSR>$_~6yqeXfYLug0P{_@$VR2Ozb-hxb zxw9W1@xkihS+dkeTspdz3W0c0CE>G17*u}6NeVigVjNJnR>ZsB(L&CIG_BPvQ?%4J z{(MlwiWfOG!_|vB`;;z+D|sWjt<4KfVJx)AUrBRME9<05D4}boi8bnIAN?c)xb0H} z_H5IQ2NTlDOvxhry9>Y3D=g>Fk}OGjXnvN(F+AeixEr{8M&~Mh46yZf13F#XU)9Qm z(ZxYlhD9wrP(HCcUeMt6#iA-TLB+g|X>fy~YJ`M4yq{-=yUx)PuW^va*1NcTf&D`C zf24q5s;b%E^#0NesO9@4h?;+fN=67iy;`WdsnQLb-z8pGn3B>oc`U5q+tJr2sd~4G zmj~6POFrnl8e4-_Ed*%m(HYp(lYrQWYb#lEEv6S~P2)A|=*4x*n%D>ga!0#`t z`Wusgc;yPF&l)A=*<;YG832OOH&W;P8^9^LG*c8=Mg-!-z=dLe8k0JrpZEs=jQ!** z-z*dbO)cZhndzRfp^m<;PG2339`H9S&e+2MOB+G>s`(zJ+9-?gLf-M&=i<^e>Pja4tuCJWBO726U z&f@mfX_+@H6n21*ekP6a(mJ94>iIw^o654u!IjxJkw&6P=7MHK2BZ@b-;oM@C?tPeW8+YZf&Z^^#+$GQqkLqko z)i&oj{NNgx>iOwQD0qP}19K*!m3>R2w<8irFF8Wh4E2A=lN-sUBq@k^_sk7uhciRR ztJP9;<0lf@{~hgu+uOH1qnxdfB&_O!sDjMoeiPNdsmLGFE79`)5wc6p%b!*y%dtx$ zA9C;=BV6TKJI|44ER{nqZ`p0#}YB5;SQ0`8<;i_gi20BaWI!ma#{0Ga8@D!HBkv#+*g`!m$| zB36OBh%e1>Ber&$vIOzUcqlbwVb!&lJuM~FDVq-hNj}HIpj8KVV$Jr?G)5@}BmYL5 z#NOH96znOBC+DYa`5tJeY=U&22x;hUZ28+Ls-p<=P!5jqAozV5{S_rc-L#Hbtbnyy z8Mb55JkcbhMum}(Aii3MHvW+rZ71-+49VW{jOR(v`nle*V8voTuELWYprRXr7U{ig zuYt1Yb*vRt8JJ!@tZ{I5YsXZ9{90eVK-`qd^Hw+EqYDfs^*q@tjU||Q-re6x=;ZhF zoCKXlsD=jExPwDb3(aKuQYi8Bhte_9s?B(WuGc?9S=2nOmTO`o; znst{H`s9b1a~y+j9mrCv6=dlW0kpBGrVI0U@{kUrMhL?gZqR)--!vt#;(aKS1xRxy z>wl&yivU>qZ7ck{AVsH9e2*1T^!k{eE^$F}+)GK!vP2el7{qAQdzP{}!PKhC`!jBP z5$>zfV_y_Hn;Oiy361j}8knCMJj_{*oi-c2tArY1t&dwUb#ABr6LxH01K|SOC+gK( zSC1dD7|T>6k$z#Wxbn>O^fSSmBRpvF-ksR22vl(zv9_2;O2W@9Moh!yDnSpd{5OUG z&OHuGiDXXi(T;L$_>3a-g&f&k9q`~>PME-UZk>9ko(owoZuck8&MOQgfkhROcy78C zM}RDD`uNT%W|E(g2OWQ^*z}r3E9WGK*tj;rD5f2?ayXjvDcPj8rIh@iKew!&iBH`3 zh(i5+E3;cXKFZAZajvZsGret4H;|)evk=9;WiG~_XG{3?NGu1O(dZfA53uVx!Ov<| zUJNXyy{V=*LDz1=LA8@kq5XY zTBNmzCaQV4MOKZeC5!?@o#4*a-i|h2UVG;C81wGz$&0)g;QhU3mHvr|!*N!FBg!&+Uq?Os#|f-_ZPb zFhafsu$?)zZubQIEzR+&HRwv}0QHx)3gjduJYSFIFGy(s3rvytpfrx#Ski+T5ESCM zXJOa1LBSz(?~X*4o9%WNkdxC%%1FkyrWM*W3!(x3UppZq`gqzIXj?+%O=eQ zCj?877XR%NDsv@&MmHxbATK|RyE3x8Snk*tnO5~Zwwa``|9Q8A7kaMEZnn8XP(qNO zh@`-mbXmCwS9 zhfKQrL#@&OM8P^uWZaN*GNh?^#+wV*)~OJB-dq$RLC5mQ86uOng2Tz*8+oU<3Gla9 zk-R420t=AO^cH-;F8{6wKR9fTt!xVaZx8cK!?{pMHs?E4ZZS^=`Ae(zd*N4G+c<>! zM6q58jCVXpf4VO#Y}gUZ-Z)5gxJHj1(;g%s>S1&BMN@Z96viJePT`$i=|0fM-Nyat z|JEyZd)lXZef(iDb=cuL^s4#8Xg6LDu8Pl ze>KNQBPq&*mBHY7oKIiG}!Y3 ztLLH!p`IR@B?^ZECn&KmRW1iuQokvl2JLPu?DnD#Akf)VS6}Ra&g!<#E3%)cg^Z)P z6-$nHRSIhZ$!COvzh7x?h>YQ?=mAbAR(admr|gO_h8HY@3*|GSobI_jCeZ?C&GjIk zQlqe0$~Y}EdGsO=El@MJ3`Lq;qI9oCr1w8kgGR0YeV2~+khhbF?NijKX`!c?KRvd& zBdVPNY^f01-aD3_3Z$ckH1VAva6EMJ!o57nfq3H=rrZ07#~o)OuYiwsx5Ie*CPj0O zWPeyWcFt<&KPsaN8RR7Uj2Gj2Z^Pf9;OHo>GRu8bjafc}s~wwwSBgwM9o-gFGL%^} z+p@?jqi%%b*$8|dY-X(@Mnq>rB?&G zU>t0kYAT)6*`PkO(pV&z$_hle5K z?6M{{>9x+%_Pn>>uP6kIf71`Gb(y5Nb+KTyn_k+^?S?w2Is}P$3zx3}K^{e0xiPpT{&l6&(U7tSI6$lQCDz7A079SfBxHgpE{TxwvmFpx#fcwShpztPG-_i(P;W@*fF z%g&i7R;P%KFZ4Zfe&MB+VK;j)SNzMKQ@IIq>DMM!uIzR#(CHXds_Y2?sSH9L#of6O zZ$r)boY6A)Fkn^z8R_I-y`KX7e?$6=V1$;?NtGC9-(72tD(uM>hh_qBcKUGNV)8Yx z=~9xi5U6eabje$C(OM2=IN82c5M;#t%U31GS^%+N>}}3~)QNw8Lp{F)+DXKd&TpJT0^cL_3#B+`vwJ?C)uu*N=6aPSonA&`<1}vFoFBN zLFxK%RY(a}5~>`f9NS~~-#PZECSm@!t0?x4d@Y8!fAy*cd;Og*E)idE6kjQo`$T{m z#V6~O^`X9jlzoQ7U>sb4!5hP9GVEdlPx0dUK>}yp`$;X7F`Y$^- zTn1u6(f8-Qg$&%p?VSMM`hvU9{Y4#x)?3%!fF$RU`8NxynnD=yzKx@GPRRPmIjQehFLXttqT(J#>p@(h5F1ntZ|oP}!gv$>_}pL_m0#@w|A z4YaZzO%e^-JpG+Z5HL2R=d+9<@RX{Svz87SSKko_+#OzUP$VmL>j8B3$WV88uw%6p-r)dm1sWxQK84xbyO}LL+EFhpg>|SX{Jl zJTUwi##`hWqBQVgCV)I0C3 zX$Vb1VCnfGL~m<< zBwXEFfZmAJlU$^#o6b!>;7zRJ}Kem+l0-IBxmTT*hYQpIC< za0g{)|!5HYP8S!`?Llf*KgUqu#s+U@>8s=^18x z`g~{mt5dBG=n~ymp*Vl5iU|S+OGxO>HN;oFGU2v=WCjZ$;w|J!5sa^dC~5&0b#*kQ zjE9B)ysP4|yT{C1JHVeHb9_dzaaN}Gy57)ym%OxBTv8+J2YaChXosO2P4GTGLYQ0IqptVJi@-gnGYeH~DG-A|A=HtNe4CHS-#{h*ef3(_50#I_WJD0JJf+9cJXxu$^|_Q|jNsL}p!gcjQys z>TEphH;6rU|A=7JQpr-5i)^#=Ar!zI45gS2AdLNiNoYu zuoGsYw}6Jo6Ca*|c)E$_bCkNFCFNG(GC_e$8nE$O4q!Wp3YMYY7QiN%(dA0o;^+$2 zbJ!~Bi*VFp2&UD@Pw#6iSe*PCMo@`=&gH_^9LYoU3dJ)!o+feRiZ;{(dg2+5o)~g5 zTEnyE_i1rF2Xt5*1U`Se3)I`x`_d5@EmaGVMt80B8dUIvvtqgT8?;468d-u863-KC znLU#4I9;pTJK1=yJiz0N4g`q^eYGpPu_{+3g>Y7!wVsyRZhAP*fm%JnHMrNPg}Q!o zx)qv%SS zzB!ojPW87X%+Srd*a(h1zP7@C$Oo+{HMaFMOi|zz-?w*-0Db8A1G9iBHovMms2e)% z_@zXLt6JVUWH6{8=N;i5`a3M2e`?!KI5)_S)%bF|yHhpaUy$LVw0_~#-?v(rg$bo7`ksq5 zt^`%@G*DT@BQ&3Bi!bS4DLPgo3kqkn4fM=(0wZZIO&)I+Lb7TCm@&Q|CGsf1un1}wLfd$oGyYBOV=9TI^j-oiZOV;o zCt8p0ixJ(`JT=MxjL00iYi&r!x{X*Cd}0#^82`60<~x~%umrfS1B@r=IN?KD1$r)$ z5zxj{=vraqR(uLo{KPkHGXBSu43p-9AV26;;IG3LN+x;I3E)hRVve176Z8uW_7*k% z=?AQZ997NPmDV&Aws}}2xE5y?efa@T>)fCC0hyAD#6<5zz6voxogrwG7{$wfS)4&k zjRa);=K$$Evf_}oV+Yk@WQdggsgDEm@Nkg*fEj-CHW9n2vA4d9b?X3%_nWKii$8`wFQ&MD8VjMZ}m^h0G1@4eSK ze44B`40F^ajI+^YanTE1)yz~tFcn;Y&cb?M<8t9snS8NGmQteK1dL^I{*YWh@A8A zb>UdSp$3ieDs+p&3&sLx`s)#?(gZ6_=-yq5>S7UntUKm#2wpj<o%XN-;)EJ=m0|RPr-Fp17|&ZNYZLFW*AHVu=*; znE@uqB>Ri#TjibjvxA!0C|nxxdf4cD3eLu;ZdFx*Yn`uc96q76N{dBRXv0)qE0W9g%MYghUIoRD<6`ErNs+N#)dtl>e67h2*i=CV5?86#` z9CrZF6O@aBk2g>~~_+Zf(J#KwxL!|KD(0+4o$g7qJO{!k&8rGAG+E#YKJus%Uqkdu7Wfko^lwEvS(S zkXe{6C!1j$?4Ok(^ICZXci{kxHe6Vbw=zRChy9SKl4Cu`QnM1Ct;uWYYkmBuAlwY3 zHeeyjA;65%*co_npGoC+Tr#s+dXp<#jNM=a)zNkXr-(v|_Q#<{p>}`g?Sx_0M#-lU z8uVR37Tc24Ft+iw%9s20&A}n83A7PUh2O-u@;`Iz(kTl02X6CWlByaFM~LeNT)QhN z2;b^%zpn1@V)Z|GXn&pU2hNE?mUW5@SQ}}NAsHbM2RF%WOz<&_b=U-5KCA-|UHX%E#bqDvmi^!l+)OkJ>bZ0I1mO zxrpo#mwhmOxv?wL#v`QNiWtsxg8lXC@N04=HRuvaxjDnydzn|R(@cmU#VbDtL^Z2mDZ=f>%Wfwfoxi~jwvyVp5x6EwrqMm2Au@MKFY?0@c4u`FTn z2hb}nSmJQ>lg86vn~-;jRh`i>2b`}kqan{ruGG=~$3+<*6&zhmE-hmxh`If9Yyf`dBg-Z1jP7Y~IBl#~>bfK=SY)QKdo&Q$O0lEK8PTG#WZ$Q<)7$?xuKuYi@> z?7;>;{ZQ!T7{Xj-xWY66Ujmbu*|eOzT$^s1YriZX*oAkQ2ZF~ewb{tn$<@+RWdX7x z<2b@!7nA>z4HneqNM5}4eC(B_gLyexHs!Ja#0_%lU5~pWn`eFGC4`f%W!MF0@d8I)BxM zh^VM>{;u+91$kv^AD_D9m=p(t=Y)z3mo*PQfl{sRhmwj7)EGD$^_AAW;Q3_fBsljR zi2^>=ln}6H0j8l=ODiHauI>e(HugUp;<~|6VWCH-EBJ&M5M|L!_qU|l)MDrL4Nh{B zGgz^;Z&C80w3oX5BqwPg{i@$79-K&3B+?~_WWMA|p$DW|9@wZJYdN7el!9ET>Q_j1$X zDwDNOQYqr-SRCnPE50|=0W}SkAD}k`M2CAIMBor*yf4aDP&IDT0b4Me!#W8@oBzw! z{o~7J>?pkDDj0qg3d%1+w7|z5o7JH-=KrI)_BWR#tKaU7IgB)vnqpr5G;r-TpuqMW zI9GB{xi(0Dn$%Y<&-|s9gU3VpEennQrSpA?vuw-A-;$Qz<_Nz>C+bP(Tgmbd`e4sQ zO{8TR9nlFrN8mbCaLXrgadBXkFW{uW`RrZIJOwS~8=B9q18sd?Y-A*pm#`N6EcwqZ44xlaO$sl~=?Dbg*M~KDt|rJ(I*^xmV#)85ozBuD3*-rGsrp zq&nC4tPayyPPab$k@;eFe4UCaIM-n9X`9*|Gs{HaiC`n2DMogh`{8MK9V8y|L+!wx z^N0L-Xu=_%TUQYMJ7g_Rfaq%xXa3l#VM2`QqZl6j;H1M#9O1#&{q|CJJBxk%H`=*W z@GE}N zg}p98Il%VXYp@c;$Z>TbesaSFAKa*5)88flzAfVMMn(@~ayI7EwUSgeJwc{R4Ilkr zy3S)I*DbGFz*x=Tgpz!kO9m=`C9*9V3ZMp`7{CKs!QAvjRGBoYyvW?)trX6DJ!Cs=6` zP{kO(D3TGoBjW~=xzDE(z=iO|*NzASh}|3x9C-P<%9jNE1UA!|5^=v=E-~!Y=@q)+ zplqnXm1Zlae}36kY>7L9nQOnxQwNAbii;CsE5(x6l2mE6{B^6D-uRAX2L)st1>P*gtU%m;2`-+Ry2xxjTK4e-+i@gHjWN-TU+m+KGl_zabLPFU?oNWWj zO!X30N^1H-a0_5c@e76`v+WPhthf})@oajAg@Ub=DQQvDsSk4o5##caEQQ_4C&$$| zbsJfKI{j#}uL|v7i$7$tkN0>-6DC#6NBShV8oSoFA(OYGBf0L=#$QV=EF;A^KbTpJ zKjy0!o1k$i#*35!sEMOeN*!$=f%ApB%_AQteCbI07Y6LqMY0|WKWA|AmZ?AcmZqS} zu8aUns+9_(+r-(ca_ST`cPTMnhIB5Pv3^W4=<1W-(!41CnJ-N0m^?WOLu1!@6_hdF z$6Q2PiAzPZp@VtZWf~EU-Xg%^uyJOS&B=OvLWla?)kEMAypeEXBbEhh^yPrX?se8< z+9E-Wtz}*OYCX@;_mZU5FnHz<0pA6|f1yGwX);ME9NBsA0<`UQhZO+b*F`0co{AQT zy^_qoAix7LFF{o?DMkVIuzp$cpBH2AkwxN({HT0bVyB%?No9OhtA?KJcwvO-y ztyW+F!%byQ*0CgR+`UBzNVV5XIh$W%`nMLC{KRnhvOJlzvHis8Z|^h=>y>FRNe8_ zY#ja@d;1SgWU-ASzC}J!%EX&BEr~{h-9*dX)UoCp?77B|1v9=W8;R4b;0#>|s~Yl1 zLUqRsP00*>PW9ojBn2_a8d8d3l-t*v)GjIj@FJZF*0)J>v88vz_y!Unl~Wz=o!bZ& z?dPh85IMMHEeqWx&k9h`b*spNJ@y->xh|I}T0ytwfQYeqj~F!GjR##krem|`i7WZo zz*Qf^`%Ii^%S@lwvZkI)xX8!fb78L|&p|%rqb}Iq+q%U$6{*E>h*{2YL{q`~Vwc=F zcCD$yg)FXGCnYb!r>u+Ga77K|ew3H-GmQ~}DJ6u$T+vjF%0`EHA`SqmV$8bWLe%~JJ; zdLvZs%bm!T#g?d<$}1U?rH2M%b`Jwdgo}*2tPWDKedTg zgJ@aSCr}PM<}BwF-+sLuCY43%gQ4MNGiejF?={O7S{L+uH zOfW4b>(v1*Fr$?%l!ig55|ctpv>`j}HAANGNCpzMtms7kg+>}-7Duhm%U=D++9VRWARF=3WE8Bgqa6l;ei}JtDj9^pOoy`&^m6QD zpFicUYI%%sARlBJSvT)62ifwwwmnTP?7Wfh=`5t-zjiCmg>m|_m~@b0My$|k?`!Jm zX2a3DZ?K;z?WIhMn5@H@770lX(f0DxL_ZJ!KM!rlZu9UOe`1ZU&t1GLduzu-8c{xh zlcjDb*(2v8*NC8zR6ChQkyhBc<3$79s%=B=F>64d53bNHwPVX%tT8{47CRdD%xQp> z>F^4Fa)k%ONu_o%ra2MMb`}17%Nr{k&WJbjrH(o7N3q%fQx$mZ;3-+mFq`{r0}{C)RC1YM&+D6qm_9h)e<3G!|po%%BTHG2E&k zAMItVr6-w-eID;Mg0qcRT;(t+9q}+o$C3dF&g=E zlSNhOB{+i;v9%=Ap52E65^2ZZ3O}2uoq&qjf>w&i;T^fPz;VEOE-k)Etbq;I{h)=u zx?R(mF`5vxZEqz5KPY7cdf_?aEX!I=0RYae=`NieP&PjFYaX7WlWL<{nC$5w>r6@e zi=Zk>^~gu+d@9B{SeNx2$pv`TpQebWGkU5SkJZQ6PiuGIIu@+{Scicm^LVIE@h0b6 z4QsleWcVZx=_dW5lAJ=OubIJvF+Q0+qu^-iX@=5{^iGBs~0F5aNGruD^8hg36sBA<9!EGP8(k;TK? zsN3ELH(xbeJEhO0r0~T|Q(!uJcz9(^$`{|ms);sXseJBOFm%Q7VQ{Aoy|5i#zFQSC z#c=S8zBW{Lm(DPgwhSoR*10G6tpfN^5kPAf$k^@0^q4G&nvMc+J0Fe-hDgf=)IhFd z%q=BWsg7@^PKp5i`j$o<3Zn$mDS_J6hCw(Ep~KI28{b&J70Zc;E*59djI9ED*4^D< z$$x7Tlq=|`8@p-@=KV~JG*}*tiWzOEcT@|)kn^)#& zxB9(BL$C{g8-CJ2{!qdzb-uwOJgDqfD@)&-FDObJ3|qN7Fs^qe8`b%ouzxMgfsiTL z0v~NxSA{PltAe051ZdWdWdikBj!%I>u>qFG*cUC`WsRLMGug!%Bk^fKyK|2KU*osw4N!`Lt!< zmUFH@aB2~BL37}jA1U)7Sl)5AcIUu8Y(Aw@*FCD4E_dzU$^q%h6~@xdhaal_#W=6hDk3ij=Iv@6Sx z8_lxK1}Jr9o@y31F|}_{SY_TO(Ouj|87@4WHDh?%eWaWY;zGh6Cak9cP7D62rj<25 zBv(Pg0&e^cxDZc;WR3!A_*uUdi=J&}+bKkkf<+WM9AlI}FjNrUsT`Ctnf!X=;;#>0mvK~&NhA?#Xa zF|%~`9A4U~M7H5!)Wy*ykIwt42H+0JA0sy5ZWum|Xo5)A01xE614k2b*!g~#?^vhQ zxDLdtxx(OFYMtmJu8*2nE6OjoKwX)(j6fIFsD;aU8f=*f*0jqguBcF0#MU~KTV^5U z5VP=@x_hch-+|N>UQ$tYkbXMwJo1%0ioq+yZ_ouJBe}jevaX zcZ>%@J-Q5$nzv#03BMbm@N`uE4~t+%rX~CkfP(0@yXkiYHDgcS>P<>i@tmIyjIYe# zWB@rp#=pmuIECso$HnWVc8%I4?cok%26l(~8LXlzy`G%0ml-Rg&$Y|%VC>-DF8i~- zyT>KoA!5EU@+h25q`v^%Yv5#%0G*v`nj5sP7fPD1O#wikIP^29UiDF@O!sB7rxcZb z@y1dLm-$&}Y5bs!6TnZ|Z;kMhr6Ho%!}jr7*1kUx6-6f6j_u48C03Pz3?O5RfeW_H z7U_;|nR)jAfa*1x+{zf0=+ZJ?c5VB&ts$X z*&i5n_d~W|E6ky7<*G_svJMnSZOLsO&P5Pqc2)x~zbfz97F55cO?&(F3yedLm%i+A z*Eud+Ic9oTr44{H5d=QrcrW549L}v+%0CL_lOznD%Qb$1`!`uEnpZp z9|h$DimDuTANA{WN|8R)ymmLI7=9o0761}iX*D@dZ8?h z_x@+w3DPkOxcL|RowKr}ic~bdCjNdXm#xr~ye`K#=9kD;wktZ`uXGxfAfI)-HbWo< zQH=D&hc{LjD!}pV5?puw<{vVKF>V13F8;bTj3f%jT^&2$8A>&#YUQ}>$t`%WIx0*> zqQq2Kq2q;Vdd>^^83Wm^Dl$}%xHyKziC3tAS!ZNQ=Vl=ljS4n^jZ6zrm8xGu}Ok21M*q4$MS(`2W*Wb1X zWKbs)kd-$a6{kTyAg=$W0L(na0y(}SUi!Ky&K4ij)nJOM!k>o`=XMI41&37H1^J@0 znjUi=VuMJNIiY7GANP4)6wmfc458C4s)H?z{sxvn_?Mhf*}5Ir>IQ@(Sxr3>e|of& zW_Mau7+3ct%g7J{Adce^7KMyqkMfWE-_SXw7TG!QEbppl-wF0XBU{NRW6c_mtl@r= zeNjxti_tH4lBa@@WKIAq8AEB8(9! zN@3??`;#P05(N>mniRUNf4 z&X4O=(aD;Yh$i)LI1P065t0HqSG?xXFJM{=m2s#@$; z)7l|N2Pw-vet@}HoDR2uvWfXL%=c!%JX0YhYrz;1ky%Rz3|#1*19!G*loked?&ak< zEA*OKJX-_7yEO&Bz|sm^dt*2a+`;f$5+@{HDAqF-qh+?{`;a{fBVe}Zfxlc6hV@Pa|<(gWK} zW5=qp1SuO{khZ5=(wfOnrKsbIYtAm6XVP8{z*4{xF9VC3E95?XseV_S@2m#cQ(6hhBvyfbPS-^E-@HajRS{{``{nrYg`{3?g-+W0{;dV zrg6M4!66vY;v=m{8?N~BtM`je`lP3wgf5DVOM)*2mK1FwsOehCHUk%mR;iZLea0kt ztqOf$iqq&k2WQ6O!*=u2<1s7$^a>X@tn(h@8ujhLKcZ5T5V5l^`d7-f2^GCon~f0H z8&06Co$K~7^(WzQ@QD{BZq|}-J*E3 ze*Kl74)ych+5Av)$9lfEu@AO$m%fMF{Xk|NL67mFMXORGT)2TO-jOLk4?y{HYe>+g z!=Fn;HLMhI|HTOwFo!O(ciwxeb-o6{lQ1C8Q+eBlvgIQYYtzcy?oRsf>tXceT`hCL z8UqXc^J&;!*ngZP5CUZ$R@`~MkDlm0{%{|Gvco7VNzBVz2G^-ecZaer-rp!98_R4$ z9yLFRvoR=qS>J@U19sVh9m~sHK~|58__#qz{OHAW2)VR~naR`l=xim5vUpPKy>@td zWA85(a83ny^#all7&}pdbD;7C>dZL~0Y-A$ZfsP>7JeaAO{uig)K!B;2V=?l!^M8dL@~zO`lhA}l!3M~A}z&M7!jIp;6R zoV}xJS$g-Mkyp*5Kt!Ur8|?dT*iZ&yC|!&eo_5<#QV{;OPFnvZIdX^wHDCEn+kHa^ z0O7cK>-Wi{wB_QR$|DnB-C9OgC^AlfbKvQx`(&c77@V~nJfK}rG+H7e?uCWPOR!WC z1J8`n6HhD|u_)o#;I3aSl0DNv6JI?dm&hU>IHEdyj1I$(p8-1cr&1WK=J^x(T?KYP zAeZ#gs+w>qMWQ#cnQhUn$|YP7I8@&AsE>4%A9M~&z3dWchOss8 ziBgS91MJ|FSWref`qo7mr7O2EL@RaP(N|Xk!)nv~z5IDv43u9(cRZOtLjXO)-m@Xo z6`j%l2(+vO=_BCC!ZX{#P|O>8NrTcLb)&8{6>o zQbRGbf%Ax|Y&%dHx;Yx|TyOA{iOW0bAXr#p{i@8LzbS9LOY*Nvg0sDIp2vb6iSoPkPn49 z4k`u03#CYgZS_MHWnFB6#)YpkW^>LfAhXyLdJX!TwouLO^Q>l3ZYU(x8Y_>ZTkb2^sm5FqkcX3Xs-~=@Na%hJt;juqH z#9xsC9dWbrWu)Qi=Dy=VJ_T|SN6qEF|6H^~I(@OKgb)dMBLcTR+ijS<&V2NC`VO*5 zg3JU6+yc|~f!(M+2f?HMmp{R`|CVa_51m7KtW`jO=!u>DK?;WcQ3Be3ewnwR@wkQ% z)LOLCS=a$`i~4ReF+^5q?HtHw^v^(8>Ph}LLlw%9pQEl+?%mTNMfRCE+Qi?0*oPqR z$_q)fI1rh`JeQpT@j`AdXLaW?GrIUt?~>rbN-)8G#h)x(uZ<1q)6^#ob&6Y*TSkTk zHpFs5PaZ;j27zrbMfKq0re{9*-~GO|dWB;N^US1D(GDfqOZ4I(4cH@M)KNcT^IbU} z$%5~p(oHNx5S>0<)w;|6Qrln-bDVvxCr){TJZf$8xM^7YL@L47ml-r(c?iN<6PHIq zvaEVp2HGp*0z%wSJkzRJr^9;d?|+^554lB3S8jqLOOQ=uu}HD%5-DFUsUM^c(oU>) zBRS~9S1(D)z~WLA7(YwlxvS<_2q~vU_WYY{CVM$8t2da2xAsQ(()AiIj4X}?e5-lz zi8Mn072}cD>?DL@rrZ(uoA&XM7Kjmmxq4s!G^BL4`k!-*0wNT?(0xW25Jzfw6DM42 zdrq(xVWYesqB*Q1>4;}ZSNcyu)1(52#pzl`nXf^)*1J?OMF11a=av78Gwoalb)}-a zT$zWNPr>~sTQhcIV&gcW|3XNudlGXbZ#RN?%zeSMS^8InOI8idQKgP?#s6TE^|}+- zVv(Ts%`kl$i7HR(Bmes$2M1K=BbQ(k`&@V`H}G?>&*>lvI#{;Rx#^^d$T6Z5xU-_S zn;hE#je=>|TCs9?Z5a7(uQK9Nibjq~@mfrsFsBtP_n6iG_E@QeP_kHAu&=dK2)-&f z(u`_s#bV(#MxLoMJZA!KvD`!`I%C7TWuOOtzpWVsWbKLk#4u&Ak$5& zYOs!%`0Hw@MD{@4*qs1Md$;%1nejOfW?oEJ4=b5|frvzCj49UJ7iCNX(dlurBobH# zXm`U&uOItWO$`Wva#(4Q6im!yh%+>-TV+dz!@{t95R^Lbq3;I*8K4UZkh{Zqcw{oG zb@V5fD!p(SLv<3C)6BXBTgN!ka=UH2PV{7S2^mq_5E2th_YM|9QY`Z`0h&2$Ic^)I zljzQoYw+u4{pIUK^`p&Aha!>ex z%24YVFy8s}4|LiY9jxt`8KaH4iL~qCi&iZX62u&g9wU4QwnOQ)t&3KFxTx_T2VAic zZfRLL!oEKjXG0SDlB3WTY+!XB6w!qtlXR?oG|Xdf*kJ!mZ!EDTAT1YeKZ}SCFlm)^vP3(_DEN?_E`Gjer0fJK~VnwZ~a73;2@^(j|;#9raOPFc* zrR=>XB=A7mmIyqU$iNi{nZ{}dDH`leS>!go$L2!_^9Aya4V3DOh9QhIssF&9UNp&L?od`9Wyr@kZqNAy%7pi1e z#}<_H+Qg}b902)#Gjc9l5Gg$N>W;gQ3ZK);{7l{e4^^bXrYf>*u~^Iq(&)lR7P_DC zTzxBpudy6Tq)y{7t>SJ?1{-e0l3vi7Onl^?@tVm~&4xV7ysit?!z1j7eGf~lozk(JLCP0-%?f8exNPD#-50B(Lp^Kn7-=&FD z4H8caW{fK&w^cF-hZx#SLOeaRk~K=sxngqa=Ow6C?+qUS_(sTP8`^U_T_$M-)k%TJ zSmng5V4VxRv%Vx_d;bzKzat-XT@R?jf@om^ z8(QbV*+M{`*p}5)h*Gx9QQdl)<^dBccu|rS?5naXeC(zspI+UU_&#?1O2H*iz~xJ+ z7Ag%!*kH2gi|_r{T1)-Is52MiOQLUTuC2D#0-LeY#&TcmJxF$dL))$Yy#NcAOs zM^377=S}mtsU14#)3luZy=x|s$Fl|D(fDedfbeVT)Wka+z<>lj>Cai36HiK1R`vVm zZnS?jX#Y;auOZ1*Cb?Oi3X^T=a|;6G2&+LFCN5oCOc~M#+rjNKx8+|Pkkw) z?wEuG1y3fL28*R;`VKguN2kX?8pk2N8JtjA#~+DXay`|stCy6Qsu|xOEMc*0M9g6e4(2bq&?&~RMhdb-0sq+NA>$RABS^>d~f^h9f)t z6RJ&by}a=cg;M!2v}fF82X@6b{mM!{B;P!Ec7e7@?I2aL=HIj-p-UIDCve^DEbl-# zlY|ey1Mf0Q*UYC%l$WUHdX*sylv7+j^~{p^IJ9(i*Q5gAId?QtW62$f-<3neQ*OFZ zl>#F7t{zIc;rt=CpULqw^Qi28#w6Pr@mu6Gcms5V0>tU>LLHjbgV_LzPM}`4V!RX+ zh+2xh&}uMEf#{{}51O$Ec~1sL#O4J#dd?=w8FHZtSbTxy>Fk-TKW8BDJ>!avjU2Co z&K@^`rU9U3*bsKtzMvP9&@88=-5Q1p)L9fksEOX(E58+PDn&`|Y&~hi12vDlq4mgWGjB6#Kq6Of_{F}DM5`*jk$!!H{(qb$I^zG|NPG!J<1 ze!j5arjOZ{duF@EfU|gzR28W(`H1SqK>;u-fgDs7gQ?l8A$W1QDW5}tfBgso`7~#2 zKuPbOD>8dzdr!=-ydBaC@uw-AA7zt;W?JQEQB8)ChkMGIY63Z&!~I7UJC5Vq{<}aR zuW84;O9+Z<=Hxcz5w@}f%hKB(I|FIe{vaI&#JJ4deu6 zo!MW7St+raQNw_{O^VT7LUO)s*tE6{60y2}`$iQ$O+sjLc#|2JIA8Rk0-&OeP25eD*sA!~rY9Y<^{ha_UR$|v?4H5eW_1c~e^ z2^fBnB4fIW6+B8o)|{O+^u~-%_sQgkq`*t|YSGF0_pX3Yq=(cjKQ%l)2KG#h*i=-k z{Z_5PBWZ1hNL?i=_P0eJJ%(HIp{(ht3cvL%M0URzU+h zJ;|@js@ezwr$9|Sc)Sp@D}r6V)Js74$?h{Ftce^ChJ)lAuJwh=1eIaddF6Ng%K2EX zs+lc}zeZ(#m`xWXuI=s&;2Kq)a`Y=+;3(2MS)p@k5DshK;-Pr?#QVDd7sX-Ivx9z` z?C1NYpq%E%665J4VQp>3zDKJVg%qtrw!Dt}r#p5JzCF%vO@$b1na}12R!0K37eM6u zm}|$dm_Vs6TaXYqxJiqsWBChVcxyZ?3YJPKV_YAd8k&(`DW+$iyBr?0U=fXc<-0#$ z5!D%IXvB>ktw{>CxTP!O-zlP$}S-JsjedUEL{d$-z}fptD@liYMY&R zv^jmU87_SDHsg0WKo|cRPjzwfM*UxXJ=4U04FRL#`j}jPn_V6ytUUsIU%ZXXw$reK zg&qpZQw4}NF957>!uO9|a!q#Y*ze66N?L$4Unn^tL-6KD@R$R-;LDOt@s}JOtX-N9 z_ZXWVN4|m}?oiFB|3g7{2dHbF67QlxdechmUol~X#I{6#EwkhRP7|fMrkZjXUJAlT(D0n4icg~@$3cz&sqxcd^zp| zuCIu!lw>~QNMEgTz})A)vY2d8zk@IJzF7xbV6eUmK;ZF0ax)-sY4ZX;SFcVyHmsBH z2!uH){@NmRAKPCAUQT1tDO^OgDm-R2Qi#RF^zIR3U)z~+7{-?}dqQt6)Zx;gN`9yYgoowc!?d+YP^9%HE1by#(CFcVH1Y&5 z*Jw@>&SJwPdy_sT9zHOktc-CGkRbj<(NC@MSIsOfaxs>Ydg)+)x6(f zC0|Twu;u12$optI!hPj;ltqd&-H+M6!L$c;9o|txfK%Yxew)Ry@o%Sk%DtgC&OJl& zLR)4`>16bS0zif(GR-GX3+b>X3*zXn=Hh?Q0Ftnlo8?j_s;#!}3XNadbx|!xV%Q0F zi5afqK6r_-HlcfAb|T!!2pF-<{w z&rA)|x2bGFS)R1*ecbh{RgsBaTC!9OZWf2xSMAS5EaC5`FwyJgJPJWV5(Sc<*Ur)<|Ck*s#wz1-d+bU}QfdKTpGRuUmfd7@cm@smH% ziorkWKYf~{hN=h^h=~IX)++Yot1>CR)-zfD0f&9(*F3NhDhD#)jouK9laKGnOaOtu z4-!|}l0qwh|K3a~(fAQAF~va|zL$Cs)Fl1NjN|P$pee8ZGLK{sf7-0}Kw)p1QzJHm zaYLs6K$(_J+)A*)dZ`J~Lpx_NP(dFU*+yS^>-CI4Jtq?-P$V9-#)08o5bh`rG*;ZD za-Uqgj^G|P!W#45Y5N=R6rB#B_xFwpyMUu~PnC;TYjUIsIMPSWQ)%Zvn`2U;g}0Ea zRlbX^;-gUI8Cj%@s%anm{+oeUu|0aZi(At!tynM!FZqwK_WdNW zUfjk}om#E&pP=f%R|rkWJ8qIaEWE&Gq2Zw1|IvOV(!E@cj={C2tnw(mvZCR8bN=x# zo*B;GbN!Clwr*H`u~PIaZecSCn?l$84Qm+3*f4cNJRZbMQrB_S^{W~Xax^LVi;t7* zbGD)WaGVI-Jg-7V@Hr#CY9nLY(O~(v1ui-!a&W{foM|~N&I_r zXrRMJCQD}X*T7AJRFqcR)#X1!Y^Fnuje>n)NQ2QycyEy!n^%;Psh%dA-!L*o$+o-` zuI!}JtC!@Z`$TbkA8Vr)=8T2CwZ>If7eqN>rlR&uJ&qR-!w7w4uP-S^SVlIl*c^@f zPWOGZx+#I*sM4g}COZ#bdERIk-laysqzfX`E;Pu$qvWN+WPw583+Tuptd z%PGB~1B}1B0SXBAIODwc2HkEGb_5EfOX>e^ma54sH^v29|CO=Yj`N<3*fBf=&~CuV z=`f#-i0|BG9~Ja3;o*;IW?g3p2bjjFW96DbbsY}{6lJJiaFRzVjW2GQ&w?GMR}6QX zIy$kqd{RJxok&D}QFt7XbhzAoB+Dc=jo9~#S?^0@cm*Hm<}$G$?M6rIFZR2skBE-?2_IEtHWUE%CaKxNAE{RM&NT$EqPNu3@RFp zWLL|MX7221${b*khcCkD4HHya>)|@FV$Cza`X0)@$Mem=$S)7Ui-0Eni+jZ$zB=*~ zHQCo9Q(-Rz0wDeOi@giP-JT^Fl?aWjZ6Ujn)KZ4jzw@;@wj^s}Wq@X1BV4VYEj=V1 zA6`}X?-Xo)KQV%I84e;`ZdV zD1L~^xq^5U5)ZzqKxv+oemw(g80zP=X;`kYfL|iKA)XP%Y-68UuCYa#00!-)LwWCbi7)8Ai|K z+KMG@jz(B> zI2Olb3$HJ{j7%azQ$uNF-SJz~ zUQe4@5mBO|$!H9Kf(AnEg_n>f$Xhoim!lZueXVu3RD;wOc}Vf=Y~55@H-OMO9Z^q$ z=Gs-zcDLWJP~4Er0>6??tBp_$+^rPRwrsi0Qq?7EZ zqaf4*lu0pCtBpaqgvQ6eb&qGN6{*Ycb;%8*Xu(!@cz>0tsByKH2#-Y8@djjmimmnB z+Z-ED4+sI9qL%P%y|{f@+08@xCJlfIQ7)~?j_r>FRZfItts*?$cb1mun{yYFT|6ux zu5XlQ>jYXi*sVU8+3XIuq~E~|7VWl$&Q?^56lK&a&ZzbbuR{5oVq?#S8pHkwK&luL*bCbqA+Y^g~*Q`1SD+QfI3<$vKJ&VGzTmnvXAcx&{@XLly`O;a+IsO_XSAyA88~(XA?DPP&?pQ!zwW>1~xg^+U1a;_U#%ACjPF=j= z*8(7^n^8ErSWJ>R@D}qQa_VU;SSx4ns?yR*Ja8zF1IMuJ;ApVukI*0)mg5ECG3=@6 zp9M`HYr;}!H?e(bsWQ_RjxV5Sme)}*^WutmB|}xE`xiK$OQFeCoUDBhBz%}(Zgwn$rX-d^LGBS_WyBoS@jY34~O+iTv1CM%9Wb8Djy=H z?bk&pe?@707D&s1MgKqWBn+ksFuJd8i^?dJsPd4l18?0=X`wN$hI}%YCT0IjK2G5L z_W}&=31sXXz4qt-4`1^lURnTF`7_^Cr}2x_J8%7^3nY=_lB+YUjy+@2Km{;3qK<$0 ztjM_{MderHK%I1n@HFZCGKeTp2`gUZejf4O6|SO+K5%Q<^a+%_QEb-`e#)_-UydJH zL+NtI%u4kOP(L>YFW#7dB3KD0ZE+ahw`0ov=6NgTz@a*ZvN=^SF5~=5n0FlT08wFF zl<8;!gXY54IkOjH_3rU(cZ<2>XP(fOYkcIg;cr`P(sM;aaN8|ZJcekqAe5P@aBrm~ z+zW$~&Gkn$1FO4jbi#MSJZKzXRBDV>?PryZl+FHl`>pD2puNU<3I7-%V3{qPs@v5< z)%BPZt&%&Q7nu~$jc=&~&qrBDrZ_{2`74PrBx?oEU&{Bj^7L1+yC7up<1?J6-O>Pk z#f~*)xX1t!g5GQkSdr0ax83U~`#$HXQcbhIUb<}a89Cn>Pys-CQ}Hi3aEGW@TzpSa z^dmcwLng)SiveRp>rLsT0rF?Sw$^HQFZvByr4k{7_`a#56G%;o+s?lBGjnrSX4~b4 zW<%oUl9h^KpVVXA?UX~SI2zaG4;`oRo<7~(cgMQx@qT?-iUPNPXZ4q%B6Ov{k1EH( zCRfMtYLd6~Rd@o**8Lq?09Kk}5umVdAIhZx5ppJTRC_F-DEG_X!PRalr5(WHXk{yo z-(W!u#t_ce%&zaB&m}qMmwj)Z>JjfAMmMQae5&K@y+*&kEUJ)bte-ZwLsez6JXq@y zGp_ne=^z#Av$#9=00xwiUFqlgK60H%v*qh0pBwjn63_!#JKFNzc&08Is~Hequ*7vc z(azmmCwD8wP03N8n;E!!Af;4MTi&$aL2e^xg_wGv#dB1mhcuMWWc|*eoM>i5{ao@f z4;+>R0rb@iX^S4N-~hi~WESJDI5S(uESewDna5HqfADYlH>C&!;9U7yWYk6rGAf2VVnt)-=WFRA4>G$3 z79~NIJVs+3x7eQRz)8EHKOGUuSP67QMS%jO0w=3QQc9rex3B;Ml0ZC|IZg_PQ6g+U zfIk1opV2a5zg7i_t#0|Dg02t$ZenfcMVc{>+WoD9)cF^=>SyldrZ?K)VQNLz+w+PI z1t#2V>Xvlhx;2F<&^`9ZDny^|C-FAwO94ps@6vhKKt;5*6L~%CkLBEW|IvlU^AQlF zK6{4#x@c$}1==*jfL7s}5^)pt|W4!B!I;gxHfG&VPuo0Hw`|VCppwDgLd2qSl zJ#t3osu}_h$b}RE>?-7#ym;mHg(|^cU+8tWA1h>~etAxAfWsg(go)#h5KIVrOfP4y zZh0h;vs#T@j3{A>!RVSbaYe8U?+n0t~FrJ6dxu&tcT%HJE4p6{zDPLzwdVUtS4`uDo znz$;8`Hxo0wEL_lx(T+ zulL|v)xUwDX*OLxrFMUTW|EWvF0ysnwIDvVtfY|DCo?&|B<7A&muD(MxrU#N9%pas zbWS0l=v#gVu}McDtk!M41lhhSO}nVSuObf$ygOnXM~9X)(j)+29)M_Bis21puknWg z>2fFPKxl*!o8eSyzwM(UD5co?LeXvdm?_yDi@H*qUvp_VCiqRIN}8K0n%~IpRv?D- zDuA)3*;>TcnbGxmiv~dS{;iWsxDzX}nogoWObeC z%n4?x%L5Pvk3Cso+p#&j6`|#O@ehrcn#GtbVhiyHx}J2FyMPiO{JgbfN;M_f93dJa zQ?1Bwz7Aa7FToQK;dYZ3c1?bv=y{cXxti~=Gic~GRv1yu$R;!q1Ese1(<3_U#??~p zB6g1$P~J(c{lCzcm4EoA%MTpe^4paxkTmc-mR)8xW1}CzkbwG|R6QteOs{5Ln%u&+ zbSew_gW=BBp>1X^(^e5lm{v0SF#!8kw(pyjMRP?677VWjk#u@O70LUy zo9tza8gc6uVpuv*5H3L7Pu_-g|2Ohlj)vm6p@dcYrjvHI4GylRegoe8$j533J>A7` z>8j6H7-pN_UrikbjvXSqG17Ob_-@9y=M-dQjqC3K=$I9<6z5oF98qx+X&HxzEX!x< z)MkL(YBoQKSxJj3u(l~v;`TCwM$X3c|L8wxv84(o-b_6w{Fo4WY8#H zJ68iq!g}yr5pxB(=RKI!8*!&BRj2RP>#Q1Uu|2e0OigHB+#~97g|wv@F=TPI+QZUP z6Xmoeerb-OjAjVMkxp6j3C- z{f(*%OW<~ABXeYmt+2XHCpoX6U0c8ygqEweg&*8}i$7v7T)yR(&a)?l^eq7jo*gBL z=?!^j;AIi^zfD=goaev-qb>_qJYBkWaggt~RnFD08&ycgfC9 ze#E?3ZLOgE4)iFln>7T-u`~@Y*NhL27vfh;QJW2MF-cDjX^ocEpl2-YkOS!+2emi0 za@TE2?S-^(K)E^(4>b%Koh0O+NTjPY*3C(*gl+rMO(PA^3Q8UyQZkS2#u>E73Gknd z!ac)n1NLloIuU5eq}To=p%mhm%rkFw7d;uC!V%AH+SmuvJ9!CFZudo^&Aop+7p+#f zhqBA@l6_#uRM@o1! zQcVeN&2>-IHM7FtI_l*uTMz(n@FV<}L_-*vFWtCU-?)+*6cBv5?U2QPkv0H@h*`^r z5XV)CGr%#l)qyZ^lf8sQd8a5^&_#=u0iT8~y5e+I`=jr*0qO{NnN@^G3K-@(N2FA%wmK2>YCuo6n5r&JxE@7f2|vMgj3koy{NeB| zv0zZ+M%GEkB#K_xCO!v^&=gi$1L0>?QW%LO^h8*3FC(^GiaBCi@Ph(*Hv9PpK$KK5 z*2zi-?f0E|?vrAwhWa|Bl^76BQ}5bCvF^py*~O{(=k0z^othd!y4q?#{zqP|$Q?ox zG<-a92f16LVVN@v{9|*xfIN&`K-QY29Ye5cxNB*obQ;y4)4i}=Xw2jAr^Me&Xr#t@Ty?2x8cLMa`Y=~Nc3#fkZ z8(WIiH3PM|Gbncla*xp+XCwS(y7$A|O>Q1KL_0A~0DJMgDA9zU2a$W;aU2rJri+Tn zAbyYyJ&5MGeo~Kqg{;;2OMsNq5(Fo1DjU6Mdk!vOG-uk8bE)n@1Sgc?v!`$|6Oi?i z*fWfu6tALL>RJ2`^7ZPFRnm{TbM0wMZP-X=q^kLpFOy>bhua$9@qZkISbZHVEGm6e zn)r*}AAi>6^#T)e4gPtM%mHg!1NA#^W=V=K%pV0tx+X!4TS6&^<=yTR9fad4Nn@xE zZD^bKdmuSN*y74wX3DXHkW=}Ey~3)A?z zo1aT1VaTDpc**jpzt-EO(VHlD&GJV8;((qKd`$|BeB|O=?a-xI>&Ovc_TS4m%G-w5 ze0u9EtFg{c10yE|^+@13(UTs@0Ewn!^o)uUaN;numQwFmOxGM(@_i>no7kAl>B+`f zcWz~eaUDeqd$_L%j-&%nw@lOT19>af0x?5Ys7~uT?xFoC*=3T z6i9SW9hDrHUAAFfLR+Q6gI8DQxrUt|;>CO44B1?#tjQ<6p}TGlsBBQfs2r88!xrcK z$?4&eT~(O727~#mmisk`x0VQg>9vWP)kpl{Lv*ofd?I( z-v#oKDnZa14cf<;_Oiv@=r&(JHG*dBrJ_B>RWhJyz*M`Mq|b{PyB*q5Q-)?2>7uWZ zE&%H_oNe&)yKv_F{*L**W5%HDZu+t(RrU!vt0LGFNZ+>enn^-flg-KG#mhh5NMGDS z_R5?r0Uae*yI!brv9)_K?_H$!20A%-M@gmc@Yu7K&ezS@>BdbEyXNrdMdSxNs+Gi4&;1*h>QDO1oKMpUa*}6)R{`Y1iJ&_p zK0ThS#@V;dw2r=2axCJPl;V|Rxu8q0TeGKZK`>wVE+&6w21@#o((71ynC=r%a4E*x zB+i+9+oLJ`yqS+U#pQZ!3m-_URh<9}U#qv4TJ@RV3oHgYGA3{h`(U_D#?b3nnw-Mk zM4ajpIizGB3mBVg2myb6k)GU0JUmin)n`nBh}P`QTGTocL9zt6f?Pj!vD@O>t+iUc zIT8x)kwz_E@Ad+ddpf*{vOgZlojjU9;5J3pko~ zRyJJ`^5?WFx;ekm%#^|4JeKz^60i!c5^Zp@RuTal4 z>>sa_dp|OlV_8wuIB+tHuM75+iro;CsJj;vp2>#NV+ygsO_CJ#l_Vdhd zi@>Tj!vNXM=Qc~=hpoQKS9t&Uzw91Lv?;gF&FCg zFwU0gS=@v~^_hRQKo3#7T!l?Yf8s~sC@jxz5Aokofxon#^86JIM2c+$07*n~qDk+t zJ_;}(*NDbw%f?QrWnAFBz^_DAP)Ao+(Dh|}?}>$HJH-@W5iPQUj!cwH@~)Dvh@40% z(^(hw$H{6TRpJ?^R!gv{;E#yz-A_jMbWVn;T~EGfG2Yj)=mPU((0+-vslR%jP9xs; z3xGpmmV_K4U_t;^*V(KU-c|<}ch1#G+Pw_N*#!d{PjT}K!@R3sAS-KCW0G-F(bmgw z8U@$<)Y{eoUl$OLL_h^%ng|GqBq=iyz zej$F|!AwFndrxF4yjVO3A+?|`?T$8-d~Wa{QmFMNGYL%@xx`{9Wq$vDdtH(w5fk=b zLS}R3?Uxl@b4`4~bK{mdWw{TM8oh;+uGV8~86%04$TZgkJIBAi%~0HqA$n4svvR?p zI9O0oxLn1%=%%xa9K5T31SG4h))gWzh=r&tLu1u7Pno=J<7S?G`CY|eR9S6)KQWn( z{M(xkdT2&NLp6roj6>Y!CAsNp6<6{g0j^Q za{W!&5Hj7FC2MKq^{DsjbeqD`(wXB$;Ni0UjpQ~7^t#%5cD#O`x5QkYa{|ThFQni7 z4ADbeEjejY1aqcu2nvzxIT^*OIqUaJ1Am68?&ehCoAz|R{_ejNCh*X_p(?eq=YNpS zUv&qy$>*xlGXbXSa!4*eL|Afmm6)Yqc!#7SC;>T)q(Hv7M{vUqfLOAyZQiBEw>kF2 z-zg>3bota@u2eiVNum_0Epu4|=sgAEpO9^8mu$E;#@GDvp|y}Lw>>O{Rq~fcIM0cR z9rNTXr=C(o*)Li>WRW%<)+DZXc=;Cer?!(TP_lMuNAfFF^od`i*OJl5DINdxhG3K^%qC$h8H=~(8*Z}b zA8Ch48PpsAFZ@CYN^h=VY?>uQipUsBf2BGT+zPVpCHk$+w(|>eYzbM41)~M)w4vs@ zYiF42?FF^CmX!HKR>{J3!U+Lj(q2cH8+G|?VF1UX1p3dVNZ^_-!jR(xj{b4|2Oz({ zo27p90Tpcte$B`t`P*2LSb6|{@+01?y$Y|#I15;9q?+Q`>A;YXe%Otb22S7w*daSwQa1uJTVVSA6^oGEQo=t+QS+8R4{V55*iP}hppwB5KChT!a z+qK!W=yWDZlUr)GbiO-)f`fFAqWxZ>U1zJC zesg{9?D0tKWbQYq3%c=Hjh-F6?=zNu#aw}M2GM5F$l8GGDkW+5 z-bU(VKDIlHKL6a4)0&y4EykXXpow`#Ridj{8V?-`8jFUK(;7NUhV%wF$3jhWh(|4C zfhU>5C48cGTi+7-YvouOTow`%6XP;WZzfOfH}kj)-g(pEu}7*j2apTjKKo(xTF6?J z&rlH^QE0&C_9r=^cIt$#=*@AG_EwZ-XHEDNZODz@OaW;Qo9M)RQh45+-Aa?{!jYY| zW|;stK*+yC+Ovg45-_E4F~x~D9)LzZ>kzz36$|DOi8*$yPcU=2OGo5bk%QCaTE?%7 zWqrr=l0Ydi#;j@#dlb4WRAh=yIEw=}Y#yOg2R<(Rj5C#32!xvyOqX>O>HA7E1Nop{ zbAGT2hDhnIu-(^6Sw5$;!rmD`IJvvO&YR?UTz>HYgxi@jfwr(t98#Kc_X%kaO7@w) z5pu406{r?)fj~A8o#~1}JNRfqmOIe-{cg*ozUMNm{92EGsv7bl&M({ulf?mTXSsJsTw2F*05?F$zxs8scWgq_wm6=RVBq6iBDcNO+r?WO8l(}; zh@48BwDe5DUin3nU8HMKuAaSebZU_jqXHJGXo|^qzLq1^5L9bBAe&ZI=Ail69(aw23#|0 z7-+Ij*#%UfSzY5-PnmFbkv%z4!;I2pr#Q3Y!Wfx3?_nxoq4!+%=7Nj32}i+H-@j^- zD8VNy$*@qWZQnyQC{4zdLFSqD2nzpF-8DRZsRs-D+enxf?Vy*X;6Hj6!|})9_8-! z4zT44^2Ks?EQq6<8sJ=VT}6=b1OAs~kgoMBgS0wviihq=DDjNJp^Xe!x(mz11tblT zPcAZ^d|5dod*{(UH}m-s3VnG?T+ORm?p`l@4p;@_fe^~KGl#k;-5Vh0%PVu1qEb$y zj%4^zD9AE^QD$Qri?M5I;$@4Bm)(m+J-^I<%OLB-3G~}~3cj@ImO!K$Wp z%NuABT~bfEG^+|L^IjwAgUvpjV$8c@Dxmpv%+sC)rHZ#j|0(FWFybQ&WO>i~{#4iR zCyE_!?)1=qq(ic=5Ss&n3UxyNZ9TPqP)W%B3Ta>+A7WmI^BpcRf7? zxbI-^GzwiPzWxPY#0JMv2jja=TBHuhfP=ut%@akZY<#e^(uv>R$YMIpyniveC_G`| zh*w>Uz2*Dnp=e!HWy-PMk}w98MuWCKT%x1Qgr)(jY~hutqcxVgo^}MzeKsX`ivn3L z0jie{{%8M2FzY{@S;i_d^-YNr4<{>+y5TR-SnUqu%Rkub%gJ)#BhG+We)kmrjhw_h zXzbov;Cn*SIkB@fStjj|o)L&WbQNb)rXfKHvtuZ>Xl$yFM{R_pPrflo(yUX+d3ePy zn0x|Cf3-QgU=d#qv8lR`Ga6Wjm=_q0R>)5=GL87+g_S$s=yiEp5*pb3|E1%Po9ST{ zy(0$r=R9$*WAa_cYE6F$q#RDzLTGOqCCMXnv0kJVN|VK%JvdlY44GeJ@7Oy@ zrTQZ#-|&eSXhqe)=@Iv`JvyA&eh0D7vfU^$~*CP+-qdaSz_ zIWVGceT#J*A?Y|0ZpdramIZoP0!gqcAe=mT;{(&}jj-M4pfMwgxIPSNM90^m1>AzU z$+RhNX=%2RZ~%-wdG@}X=NqcIPi_Z%?_gM|`+LqL;ULAH+RF?GnDw_SpB{q3UEDe! zoz|M@cv-<&S=Z!Y!38evx9bBKmPp}AV;wzag)Vz*qi6S|Z@Kis2!l{l^E_geV%cst zZopVQE)%W*lh9(BEE;>E9-Ciywj(kq5FS}rTN6anc72w7!%QsP?;}JuAc5*w9O|-M zaWO8aAd|~*5*PZ?SLk8X^~##xc?I22a5H;!gI`12B*<*!&$yNURD$ue5{4#e^c>rO3Nqg5IQyR@!7#E(9Q zGPclv{Ro9npdxEhZ7YZu*p%}6^{h{`?H&`7qw`uXm^}N;8!O0T41 z;2;mMO+n!VDzzs$_-q@8(LyQDhU;g~|%(zjM2i4sU1U9i(oOf&BoHrFhs1rVp zv*Lbj1qJ;rtho*s~y*A_l*&))jHoJ9SB?0yhXTLCcicM5}5|Gw!Ev1!Pi;B6I{Uxya@!IlLe zorebhm=N420gp`D}nE7((^@641NM$AbtWTXxv)hnSd zLM>5^Hqi7xgntXT1fQ)E1pHwX3jm0%O7w&J!-tBE+DlJvJ)@7bRC*U{oU(VXVsfLb z*54{9t0>MX|rf0}hJ8s+!dn zu=(%d%WYx*(+7jQfmw~pSBl@I8a##7F?HjwjJHPj^vL;vTPixf4Vy_Ibn0tA^2ptX zso=X#fE8Z#g0A_)Db(-g}rPMq|nTY^BC0)LZ8>pmColv ziSnnmg1tZ$TBkr3r&hA?25ssAQ&gnr$u3Nv>0upyUgJYX=9x`=AAcT8BDW&t!bD2djihW@gB z#K?7(Evi%pO#_1!rOx$60K#BCwik=aDoL|IfmhR&BfOuIb~;pEQwh%LsIvHF)OyCQ zN^GA>w&(%lMV#$^O4OS`u%mV))Zb4bd}~2)C6l_7bQ(N#XE4rhPKoCijdLNEGDzfR ziwWzj7hfvGMR5-bZxBK2csqCr#C{ctfD6uZ! zLR3|KW*fd#Hpoo*#%85Dxg45!F>8 zdmO4RYs~L*pr>00R8@OCC3$z@@{YHFW`K*+$o1+#nK#gCh@r|N(l0|vdh<-|YJoTw zZ`|lS5Ky8Npd$m{{clLJ<9dnDuHxeM_}kd=R~wJ7#~of7@IU@|cpDz4r$wF_O=%u) zglji^BI%f^hnMxKl+pVgxG}}}Ke_{1pZ_qU4ThpU`ti4arbJArJ0f5rep`TMNRVpc zRy+!C5|ffMXDRwN!G*cFVk(|w^jP{4 zo!?#`5FX#yRoeCr!7$>_z&HE68wJ$$0=>OTr@vO3xKJw9>{ew(?E^-00o<)W^_O*d_#lq4@=Vg zf2h-iIn@-KNx*C*SN%uW^3j~Te3q&W@=Ww&wsb4fC*Dain=>=h#;3>{&d)&cY3dSF zW?}kOuryfvvWI=mmcsg)ct(B;_~`i)bHX|f$pJ3}-4xN&28=vM4BOoUa4>5Wh3-Wd z5f1~S{y4+FQ`}Ix->+g1AVe5IN}Cx*?G}D?L-LpB8M^~uq*C8TdNH6*!rHJagiiTO zjDWzm7Vj!ZT=;9Hun>xogeej_xfPcO>22Y@E+#Z+!+h~D|e?1CIyn-Oo0jGKW6&1=_(UtX3PWS z^b;jHZxK834l~pRE=Os7*QK_90G-c|y;2!I&` zvaqyNPg9TZ#6>d#A;i~8f8Td~olsCQ(WhyEmmht*S0b5Vfdmu?uO?IV06Mxxx$GX) zH!a;sfB@1)$|^aHl0&}vV!x?2^GIzg@-ITa2pZP)eL0po*vmGV?h^=nhe7n~GTH*I zZ6?XngZ}-*ze2fz<6MSLq{9#qQ*&L829SFBTHhK$pshYFBP*D6K+U*-B|Hk|`bAP) zW$f2%{6ZU<4#$L7bNpr|V>wFPOT?mffkD2Q@4R5kwB?Qy){{%Gzt5FWtV}2`Yv$d z4|j^>J0sNb{4cvY1=Up*7WZUZjL3@@f88`))A1d{RR>4nJJs|=8Iqrb<9wP?hA4RQ&1c!a<8Qcb@6s(B20 zeo>OqLV~pO{XKZ0$VjEScx^1c{6$5W)qM`TZG{Ss*q`fQ*MD0tHN_6JI|JQusMHN) z>%cE{Zdk%`4;^5>I#~$$+D#RVI2Say=!ZUVasa)j_XIUMcp^ONPEuc=gSa7HM3^BF z5t~zKv*aTe2*a(NQL27WfIdf3zUn{2%FOYfK-_wWx89dwrFoPQ%BEt6 zz@Eo|GEL4yQ`Ahj-o;5)e&#yCn*|Yr;oeDY8QF2r`9V@+;U(jdI{7CQ$OXPA2ZM9o z6}0mO6>vOqzXT9z?3+9Em95C!Q{>J!w}CBtkP$UmGG7L<$)7+HJnVVyj?K{?P;jd% z7RImHGijZtmu+|~(uX?;C&#+}(sQM%BKl)>b+@2AZ%UQFTEh7FK>NRpJ!rx_v2YG} z+xn=lQz70c{bm2jLM&%gNUjCY$bF6bAv=l2hdzqbia-GFv)h|_Yi&Cr5r=)+5) z+x@1!99ja%_#7fTBb-wSr)acPvLqwF3e}!d4 zdcS>%!lVa6+spz`POezzv%hJO5bVmOIuKCuzu~vOnxa~%+ytGG{R?RYcKEWG`z3{Z zvq`KmLKyB~|Bs%?Jm9mHk%bc0gLrul>MV>3pKN}*SnsdK627-E?v1v}ek_w%1D} zilD#C$>uZ9mnNz?N@Dc!E8D>H`^hIkfp8gW()`|ybj)LuDsh|7buW$wbX4NZ-bLI~ zk0#+HCBJQB*RkI&g#Z%py+RRe_X4^_=8mu>!0yESC6z1fPsczv($tqyF=)587Q#@LsG-fq<s#wZ%)>65e3noAG+`lkcfN`U1E_7G*zKEz_l)| zl)EIyxFm*&@5&$cWF~Wt;raQg!3P`fEkz9ZyON-<|4FlxB6kAyvtS0T;`r!hq7`D5 zC%M59uh84=rW1y!NA7r!-=VA~Mq z%czp?9dLmuVOYd$Dr0b8(h<`oZl;`!O1u!AqRp?OfOX8};X1`Rd<0%2x9+v*!Bkv< z!l7uc!G;y4ofZwuoyf0Z-U5|B4xQCGsyjLvIT_x!Os?^|5p(M$^%8lzD9@D@Dr+U@ zl7aof)mk=$LXvLf)B$3sFcH*(;6_OXS80N41n)oWFr(LT`SESoH3o27yu|6OE>O0Q zOB+yCyn+uW)QH(sz4-|W9EHl^gd{mU`LcqdY+?EY1BZV<&OV&XV}t&BtB6YT=R8c1 z>*tc2ihcsZ4jPj+CNZNAgTk8DzyOl&i+W#mTflyi7W{z~h=A)r615BWQ#K5$Bn1B@ z%5KwvaoR29eaMbiX+aGvrJv4>e_a`8M(@H3;tAN?NN|TZ_nOrN&~tS#3{eZ!Mcz#H zq_FWSC7t$Ah5<9Qn)}_@*B`UgdSx*QED$)MDzsY4xANF-gdzrn8Dw2rmc~+aN&rDB z7(Rblh%cpslC-7M8vfrjo%@ZR5|OXlFosFl$zP;+3oDch@fPx9b$1gz$jVO0aFN)? zJ__vonG#iJNooN$YHyY`LY!#KC7Po8_%aRKPzqANNqZCYXxR!gv6KzjN>tA?pEAZge46AE&r#s5YgM;HuENoO zSkMHDnYO%C^zE6x7i5i;$hcj;RCY(CJ4L-fckY|BD`93_ z+Zq!%wcje9JC4H~u-iatmz&+t7*N3vb8Z;_)7MOdPkIEUU9#Xgrwl#@18$=d@{j0lQDtR&k8 z;jf`HP7K}kM_&}bfIJEga-%xAzOCttqSm(c&l@ZMuak()v|ac47Vk6gqskRBWyS?x zj_Z>+DGls;PG=3!2YflfWd;GOjNgML$Peuji=nM|=E@erhsH#0xaWR<0vKJw5JhA` z^78i;vIU`Zw+syyj3s&_O^htvm-SP{m&;c6dTkkzu&b7+kr}z{tI8@hzZ!}Tzx*&dM5G;l{e&BDr9>b znANFPZ_0lNxoHG^`Tl{KOhPoz@By46=9@!#rqn%=)G`@;31Y2l8dT2*@N>{G{4-I3 z#qM|=uIhmdDP{ex9(9haD)C4iT8YHHdE;Y5bY>8zgrFW(Y9)u8I8J^y4%+s*^klI6 zr<2JuTwCr7CBTi!k>Mzk7IQZeC2yCXr%fI|I%+SOo5$cKgAf?yDRd@S%B;AvW5Xsn zn1vp`!7;rt#OfZ?R6$FW6?azit$i!eBs2axDn=TY(8c(XhHNPM);c;ZHUU9y33YYg3(Ry5)dcPLV-QIxfGd#8wTb#@ z{VTg=LW2LvIy@aP9x{6AFv6CRv{e&DDov*-Yp0o4)w0?GX=q-Xo6ildNv@Wu1dcaP z6ZJ9HIm091Ifd~V4)0^Ol-dUxY13A-RYU*~X9k-RJOb}f3Y;Q zD)NLZaeWVBI4g8SX)wALszBWV?7YGe7tclkswtLSA`}ng*gSOlq4RsNe>j~(fLy(T zC#ka2Ey-c6#p^}Q-cQJ)WFJnQ*1U$g6RVwVj8>k5GsOgZQ@LNOqEJw z)hTpo`Cq@Q+ zEkinnvSg68ywL*H;plX@uuHP*#$DMrA(G82IYCPvEkoPi#e2u2Je^58)tfS?qbaw{ z8?CcSUIACe1B<3#jyMz$7xSGFDlgp$5ljkI2?05%D5C;ZDT~5F9lI{h>(6ck3;#`G zqP$Z^!UaSy{$kpI==F-LuTl$FBy8+eo$K6l0>QCXvF zjHY)8=8f1Q~6!0zBKg9_(EkVdC7VsZO%K8GZ|v4!XSZcctV36&%sgbL`CeMC z?M|*IwEIAHIN`PBEZ!8qpXy`~kRhbbz)S1ss*I_;1nT)lt;YvpgaH0Df9}?v--)0;xZNU)Y%W6h2+G>nWLkTvX_(Mop@pV zj$74cnmA!HXd~3Va1{@Q8E7ctZ}nTQNoAlu?!*fS)R^gAxO{=Y zKV{0m>>aCo_+mubmezJV)Mi}hb#4!19aWunbyCBcFABJ$-vM`C zNSUXO**mnUJNy+oG!H4l_WP?y4l2EU*^AK)V!X_VsIULtT+4~vZ2=#@%r|^IPWK!q zxL^wYKv%a}6QyL58uPZ?ct>TF>Qy+^@y(3axoBaoHq>8zQ<+g>5Z|*sc3=RMW zYpVffKjr+phB#`j;%$fcu7!>IXPry5lG~)GljH;|uh8M4RJ{E(Hf4u{aYE1gwX;qh z4vwq0R=wjBZP!Fr$MfFUp5lj$lj&2fZi8U5xzz5h*OxsB!#piC{WB?v2+bEB@mMwL z;2?<9vwWK9IAtbk4jeQX5^C(oJH9IWUUu2RZg*9#K5Al`R^2tJ&kT=neTK4+iuipn zg=bH2v%)ddC{vh?f#~~v@USL$u zHj!rvm|Ta5961P^!3zB46Oqt^AMa&kLW*k zImllf+~l{_!|f~m-l;89xSAj*$EPzh4qVtSjMHcT6tW~KjhT5BTbF$YbB6AxkG%JU zKh2y3p}=i~Zm+P4!x>*;r2(mo(?UI2di70u;_2;00PF#`M=3O3*Cu{_p&!h~kPLiwty0Ec~?3j)1v#zwmB*|&TTAenR0t$nc;}u`49TMIgM4AveUv4G|s-8tHXxE`M<7!jxE$Q#cl6;WJ3{(YmS8a%k2`=t^yjd(^P|$XP51XRR|2vT{S~ zlE5_-ET~+%NjaLOzow6>V0#tVGt(iA`8horQD1#`r{c$z4zpcFA~iSacV%kj8+h)U zg?U{?u_hpVKj(Qm5OFZm1!!xmm=v-Q`74XnDWF+?{%REC*3 zYcV~7fZX)aQ=ldK@p-QnN*i^S)ZN6Q;~%OD*_sl6{^1hY!I;HwAQQM1_#y3Jurlig zOCN)2&es)SOiWIS-KPp4)q9QYsR+m5?%+$#VMb#TqF&o5a=mQbkuV)k;y8miEzLV* z=uTb9w?|rd3742s4y7}@H3*KpO7!hiT2pZn_UeSN8Urw~!>efMBFn*YiL+cVVPNtF ztmqS8MX~{RANCZv$SLONd$1-f13~ZplD)atJXJc|f^d$64$9s458Q8s^29YDLbvMf} zPws^Us&edJ@xr9``_R;uq1d85h49)bt;uyc;OyUmZ6cb(@C6C62Z*guZQVgA6v-q0 zVNl&jv>h6=^T$y~Z#4p#Cq@gr;3Z6;?7wamLG9rFbM0pfF87GRQp6HooOtli%^C!2 zyv35ju%&SG*8vmJS0S8>xO(8rBizmOucwe?5g5w9X?N5lHdZ!LjJ|df{|q1oojMkG z)Bd@&ZN;eC02QuW`vUUQ^BG75p1x6yo+`3z`sr6!mjP5gUY&ryMq8L^HJ7cG0vZh~`!SB{=|YdKfAf zBHUPiJP|FMja(gLf?3(?Bs|}Mm+udKTn>v{n`({ns%>@PmFPohbX#C6*jlm8;cS!S zi;F-CeEJ%da0SLj0PRuy^ut4|-k$Zmcmu8cNZB;wKx(IJ{CsbPbi)x90$?+`@!l# zJ*oLO;sLz4f{zEWRV&Q6WoJ-*!4lUFW5K_Z+xjGfTtuh~v4O0fS=9x# zq=fwxr5U`&Qm?=1CggcSkRyoT=kVyr!-vy*8@x41yz5+TGlje|Y&ZT(&i_SP>S+xh zgl3~A{?9d|3t;q2TJ!G5cz@VAEhFkTz|S_vc6Sg8<(CTeXP)tK*EvEXW*WfWXCHj`V+s>=gvTMjx4X5!crNl~e+ zS3BzozT}A0(a58PNY{7=c+i)DgyFEps0Ra6``4C;%0IO41yq?t`(N0-HW3Po-?$~1 zY@UWQ=j%CGsj76b_!g@X(4Fxy?Pj0$&^iwNt8{)>hQehfA3-KVIUP-=(R(^~=^g>H z4IgU@AZwD84CbZw3HP0dxq@^>$?snv*Zliz9cw$5p{H1veDvF_O4dK8JpTP)#KX~I zvx6N=MCziqKDh>v6OK6tLTe-RwCD;oTMw{#)#2gosK7Vs4Z(jS|7-wyqp+_MUKxnm zdF-=d>+bK6+=;=gN}Km3;p!vDs_=RJ3ekd4UfuiV9KH(Ul_0`NosI<-iqz2A+;C9w zI~|Fa;EC&Hrk>3|CN;G7(Mjw@q*+V~M}gN*k_g)QniE=_ezDyvFlSo_9sY_NuG#;8 zv3!VrmEH8~3UH+==o*Fp8LLvSr2}GE9SkFx9_SAh&i6Nq&F&i0Osah?Oyo0;F{;U4 zOIOi+M?xRzL`cHaUdN<7=6R`FX;@r2nW4?a08ORsLZ!kJD^@#T8e;TD?*Bhtzf4$4 zCgk}@x#lF%MN1<*Ud-p$d7_k=VcgkVtIugB*Iv?dKLhkUCeXg0kq1~$P|aR!#S}Pi z3M!8%!J|}lDI0vhm|Txj&-IRSQg(!_kTnGf;(&Lwc7upWj#O<|CVLc>dB;_B&knK> z6jBRK-~~L0G*KLxiR9V$p`g)rMX~K;k|$+MihH-DZykpWJ(rMmE7IC}m;)qSW4I>l z74>ol-SlyrA$`JbF3lPWZs`&0h+#0behWYTq)|nu97K~`aOtz{;JxD0D~M@LtEs^x z=V?;}P~@?Qbn6&M5!#?=bYi$Xg;p3z?w+hMSW5ssMbK8%idNz0?XRBM`bxEl;I zt`e+9ZEOMclISy?=`84G1Sqx9lyAs+kQPE!Sk6|hC(7wsJR|^v%S~TrXDcv(^22Zk zDv)zYkVX#8yU=Jp3`-s1`2JAeAT$W7uvFD3If{p(0y}}J`I-OgZjxlXTJ2*H&{!7Z zRnxlH+%JZDA;V63`I!oCdXY&p%TGG;ER<+dTRZNRbP1k7R)NkCu~gL>C9R z@SN#R8lg<;lGY}mieg7RS-%<#MF%qqhf$y_?b79s4Mu2L!p2m;|K~(_(-pHaORX(?sr(9Fn^@a8khP_fco?v5h%lr z5~vs|$#<>Em^UkAslE#*vYVh5 zu$a^zIz6n@J4-#M)qz#FE+z%wum0C!F}35nG5thJ8Wq%gQ&lXKoR*JHRafDL=31=p&HByY(^%RT9HFe++bt$1+N;8P z-n$-EONL-hFb43;c#~yV$_|;zh)Ukze++O+z&+vdo}LHOwi;rFAeJW6xZeY@ZwP#mN+>}roDRs+De-YBGTW@~7s*a*ywuZ+@Ajo~ z>W979R~irqKv?y|J20J2=WSzGeU9aqk6G7p7SLs*&{S^agZTHGJvWJP>LI8G<%0bh zVH4dE0!mIgys%X4M93?(m1B_PSU%CzP#x9h$Jx=I{ORF!o=!L&a2Ovy6sIw1G z1^KsjCH%M7y8pvpkv&U#bcT*|OA91(OJW5nbO@Nmnr#BEF^wo^FEzA%=ugifxiJdqw!S#ixR32$uW zkwpTw*$N~v7I{>xv^I6Q*T8tM=GT0GisLm^1o4wJ5ksnDv@q(C1e{VC0s?0JJ>9LS zC?ldO4bk4@>0c>>Rz@~&zshFbW)-3wuO%!<(KJ5nxL8MwF-6QK^8+C{B5NT~C<9hZ zoD}ItB&)>#8xv~pa1@xDD2JMq!G1Uol21OH(62%>&!iO$ZdWWJ;i1 zgvY+yaW7Qci{~!XtAcH;Urqq}0r7zK5+@437`8y8TEzNeukrT*G&(sZ`aizmsO_z6 zudtVR35v*z|6FEW%@052BOMD74IR^+h{}LTQa1Jj&I6%D&e$FerZe9FtzecEaJp} z?%)R6eJby$6(wFEdgY2pJ0lZL*jTKospEM%dE=BN*kdgS#+}0XneL;3R){8{Ev_a6 zueP_hIhsoaIEKG97#q88iZ)Yh!)FedGb8V{rnRq6h1-@k{;^5G8&E?i{I2w->a+~& z2s2zI^4OaWmBSR_B~2)TYH#na*Za!D`(V-C7Pm=sP)YJPSL&Q zE0kPW`H9o{v9U>nnJe)kw!WvXWLDZACmA&o^T9L6vPaT@gc00U zgI#OWd(EA+!jK8`6;UA0l=o!;!s2ydsaCv*jzGM(USHn`vL8%wvwRse^Q zK-R=BK`Sv|9;dTEN&0PF#f!_{Ea-hwLyu;gO}fsZ$>{#cP%5a0M^5<0%mu{+Ta8QO z1E=bfGfv9s?TFUzEvM^#NaA4GtS`> zC=tUX>eJ@roR(^dsh2tp9){L|&wmlv_v3MF#(5A=-0ZFV15)LT3`n&%(>dQ4a80us>RfVNgRvYdE1|Au3 zba|Wy#7-Kv-dntK=v2hnKj1#F;10mx(OYk<^eu?Q4%TRP4mk5h2LF%e1GZpAHJdv; z*U0XXE`hKcMe&FN8_2Cisg}t~nlXqPu$;_hY-{rKRkdDr3jR2?5;0>$gENWjDf?p7 zXT=Owo8cDu;eNC6$YjRsWIP2nmK4Y)BhXbC2qf+YKp|xH=1ri83BlD=&Gg(a$^Q2r z9fJCf9AV$Yp|J0)%?+T)Gu^F7dtguGBZx6>AGy8SX(=T)R3N5S+|{uVx=eY zU&lZ>-`KB{dF&|=kO@w6kIsVs&->qj9kDf*hU(CwulG;fikfv&54sFsblH?4wVjNq z87Ye-2k*^GSf0>|dca_+9?I~=bFw34l!Xmb#=H}o`goZI8)cVF+h8xlY*eWb7a)Dq z`J`J3X*1mwM6Gx+r@*kKPhjby}?#cd4IhfEujQW?{ zG;!2fQ(MJ@umef$4IM9q)3-p>&s8BM>mtNW`^%1nZW#wkB*?2*!7nVL->QI#J9Bg6 z70s3XihPZW-B~ij^lw{9R~El(E*O9mb^tRuSiuHXsf4h&)kgZdVpu^ULzy#GCAU#d zZ>9t)xT)j-FD?0&>rJc=0@nbJm}X z=1iLc>XT1MlPdJx6_!%#bB1oF|8_>omVw&vuS^4!2eeAug;ffjjx@G&FI(xkIvb%D zT(B1bhNM64Knjd0D2(+bgbbh#deh6!dj+(-i9gNqg#~qX+V(X^U}=L7HX1`#&t`*n zN=Py$3>!r-S%R|F9V$FhU+1FpLR{1(4TAl+=hq7_uuWIF7LF!UXVF72Y6$){(_`5u zT-g(iJh7!5&c)m;wOw6M)(muzSBJ6*tR-eCiX>->DU!pCKleFid8v|T3$c1xy*%XHx;6}o{4*oa1ujC;~lc}LnYelOx0$EbZ-3xxGY!Z}+P z*DX$$E%H5oPD%~+m>v)$hq&w}HEW(yMOC>ye9#-x9ceG&k6e1Russiyf?wEsWB&=D zWxL}>;ZtU1EnPQ=gX@!}zX7wL3xet1)f9n&0wCo_fsj5x8)l z3X3QNY&H@a@o@Em`R5upk58KT?G5V!AHDrII5d-&`G=yqZ89dUJ40*Sxshi29ps=k zJtAq@TPIs^T2z{n_e73ryVc$xr%OY$=HU$ERKir;mR0l?nAnjYJUOHoSo9ML#)^GK zW0U|sd3sFZ4ok?iDqiJmS&g^tOsoIWZiC2zkteZKYWy<~q0W_V7U|A{d&5|CWlv8D z(l~URU;(xVC3@|zr3gm#s15yEWDRL`;zFVwKa1K}$wG0N;M*X8XfS?|uP$g{Ky5ng zKr(zr;nWGhstPXvd$L=Dv!qRJ@y~G~V~0x&Vp<$MAH>%->&PY9I~C1=2VubOjL;im zPJQRLn9Z6${m)L6VvhL`&!gWCe<*N+Gdk~HUfX!u6gpX_KnRH3T+YeRoHZ9{qH}ev zX+)$OH!I)o>b!KDRB_nCS+(YX&${@~N;R-EX|IE6;f?8V!pAYb_EQ&GMAv8d4ttd! z+SZIVv6G`qaZDv7K|g4X>Z1sXQor6q&ZcUJO~P?``tW8dNHflV`^(0@`6M3@6P{ev zvi8s_NnLer=nC;+NbmquK&!t*#2bj3V7ew+?y_}yfPkv+Wr0j9tw6UsT|ycb&S#OZ zNgTVkX8Ne$j(QePmv9v)5*p|Fldu5gcE@?r2fk1{M(5~E#8`F9je~R{XiHa70l0rk z57`X&p~4B;psWN)L*euoJ40AW|1bg4H$6?;dW1#Ho<+31pqAJTG{@W|5Z~KvWwS(f zD5tDNQGnG(d3R_^y3lU8ydB_woI1)VLcP}jz<_Ir4hnkj{I#$1?yZ?EF(sQHu0G!T z&rGCNe=QIw4ir;DouuD%o<_mG_loeWIBIcSLaJ47EFc{AXB&SMmuyAZkmA9OYHZwP< z);Vm>-k+BIL8FCNyJn7TABD^){pP}j*13w=$+0hqaiKrRD}@dLH735-g~=0-kw8Ei z^t3_vv*zY?)CwsLWJLJ8J#t+&`_IA2Kf}1>^>U4+-)C^(wzjT|Eht@27#}yxeaNcR zhG#{vT6n~U>FsAf?wYvNyMgn3?QrGwk2|A98^70WWbpuLy>Lf@DIqMCJlD`zNeeoH z8Xc5AW(E$x1mJPB8nlkm-{{zB5tEku9m5P9#ik!9A_8F#N3sg-s#)?c$J^gD>+698 zm|-Rn--Wzj_xbV6ae-@fvcud_UK+d$Vqj1&5vp8+GVpC-ha#v(NugWv#Aqv{xzw2D$_6j-EWzpk+p zC4k8-B$TdCQJB)0et9VlmWA=J8#rX(w`MCy5N%%h%;{w3BoFhn@KLAECDP2A!Zc%% z4mF`tKOH-gdMVr&$v6||n(~p^E}SEZeG}N77uJ)DY#(lR}bnD#%mA4GSD3Wgi`*HUuf{F_UJ&kH1e~&Y?|_}Jy$rrKh4O-I^dT?yVWa; z@&UHwJoY@pTu>hfL1>zjC+>8LXjo`G5QQtHZk?D%{84OLSIl}S9PT>fZk622Wc+S} zpBW!S3ndO@SHuFtyJXqrWP&Oz_MUTQ`n4N~p(?({v0@I3!o#n&@JxoKz>y2`+DZrc zN1-?F0bDQaBt>&!`ob)_^Rk?2@RiRChhSu@PA#s)XvE8yM4%pb+uuaNtaO zdg1gXN=5BTuJT35)V}tA7ypaQ44Y`;bd)gD6ff!#+_D@52O(Gp#X)cPmHDW^UdUqU zF!ReuN-H{pe5K%Fzx>zlxuYm!gagJX>VTsqHP`I85OzI(K(dt~kPu4G4T!`W}XsB>2=ck}D=O_hh-ppe(9o9@C)AK=v6T$e*N?gCN#ChC; zOyfxFbgFJSN_%z%_N?WrFHbZ*X)p>qA2btkJ1RiL1BMykA2TCEq*q`6YB=wou0yk_ zln`tj15j6NAf!*ryM3ngkOuYi^8oLl7ISt2l+#A$sV)J=n10MFtN>F&e)8= zn0aoRM|%(3(}Eqb#=qe-o?sojgN!aG#@FcuRSPdK9;1)06CNdbgcFcH=x%nJRQA(+ z7Es{Jz=U(j5M#6*nv?!X$c)zjDqOp(j^1~8q)g_?3OjlmxQB@)YEx`u7 zi~JoAT~J9s!`uA3jK=!kF}|LzO;){>@FF-!L8l+H5lY(J zsEkEfSVd_cGk(vQ<)>r1qgRp&pr|H-p1*>Vc?;|Q`t?cDQpzZH%MW949x{uZ138P5 z%5E2}OG{brWGDja={6`f_)#qE9d}bzvNLr>_KBE&b+4xc69RAkX=mnf(MJz%l(cWs zF$e2F`lkw5mrB_zQw{rx8E@qSO-jd2)k_~|j{r+r!eF_77uAIL0hsx8YgVV69$sFH zOcuBblN{PfkJ)@f;4;}44%JEZQaH=pRGJV7(o&mTQB8@u&#dYT%xEOk{+txpIH5!b z6#XFT!i8gq;;8Ghs`!b8CVS7ST2rP7)3+VTNzqmK)BFHPs ztPPc*B8QHr*4@hbOuYAG2#32*@M{Keyww&#ZI{%`;hQtP8?60E%IA9|l42$BU%rg7 zWOELtVuV?`39ai^1`dbQVs}uq;&DHh5#sO*b_?UMuD*eVjICNkOrxX)h0RB!u1tA5 z%Xp0}aR#ebo~}p-V-A6K03J4E`}S@NSaWQAK(}I$eyj>Tw^eGY**5;Lv;*0}p%?|w zRfzX%Q+sk;?Z2PEXka}x&gGC(>JgAA39}l`U{NvKdZVC#g8o5LJ64^-Y%MJv^f7PP zeS+>yt^y_36%cj3n;HgBgXBF(HC6Y&9`?;f!f8H_H~!=9YfL6R?nXtR9v)}A5P0v; z%v*^6bJ{DU>^jE_&lP+K^xfcdrJO5b7bkkak{=xc+Mc|5cQCh=0G#G`R)Rm?b~4dG zXUuE`e7|Az@99=}1j|RuJf_H^rT~{5RMm>qkom1?HsLa0oH%?f1Vd6*|<3x=vTJp+=Rb>P!y};Gvlq~CitsCyTC?ndpkucK4HuV3@|r~YZ_=KSE&{%{ zW+HORgT88S8Et4r2Ed^j+zY#yK96br#OqvYFW*?FTh-Edo2~*F3letl3-{E-npV19 zO0ysd*=l7Si^><*dM#l<5UE--nWsPa)H!a|fx8HLrIv{$O8eIei5l>wZIPBB4A^A` zBQ5YVk9;OtW6x6x0ozN9MizyHP;M?FOQ~Gc>g5g7KteROr!Xx1I%Nv!Q%x)MV3lCf zilY}3#Rh0S1gKbtq$ZrJcryy_S5Jzp?io_{rRvxcwUJa}9`rBYDSlTZ44G|{xJ_e&JEXhY6tQPI6^_pXQ5TXT%=_UP%wd=VxOWs!K? zFw(Zct?a_nQTTPei$R=*n8lGCr0hRP2YBBHwWUtIS%!~JX)nS=;VG*28c302-S6UK z9~&;Grt7vl1^-T5cVWf2>tU=>6L@-b53^I(tg$>}3=IM+@rSZqy+1_)Yik3~l&&jEdu=0ek)~Nuk&;PW@|0UuN+#=tumc_lfiq6H`DlCN;q` zmV=^eLi)y;+jEpC2JtsHdRp_Ky>jUb2&Ssi#Xleh@qkWVj}&_^GW4_8YM@DqL-2(w z!u0r88$?#~Lq$|IrxZs&gh8Vnq7OlE=pMjFzur5BtFJ39u|jor9H?`rirj0q|~UPv4qmvZ*gL{M=R&!4@LJ0FHNEfb|= z*RkMPyGP6>e%1s^rrv^VaCMDF@QfJD|C)};7iMiZHAdYxFVQGsr&N~oQ^w!DlpkXP zs~lTa1xHOk6dWibeodV6M9jnNJmZ7rU@nl)zv-^9npgZy^QGL&XCb|lnJb5J9}5p4 zUpOPWRqh#RCC5E81s=o-Sa3#UaG$~VT|M$jw4MeBnOw5qsIhyrjrD>@F4Ft6BC;rg z{PxpC%*-fT<{PaL!Wa7fobI?T;;tfS&GLBj1jLTCInk708DRR5!es{ z1+AJ3wabeUkG*H~KI4m3+ zbLIs{sLIHgm3lxAi^c1KunPQO`*2>ajB1xI&>-|8m*GW;P4Re+QOoJ3rV`O)aCQ|O zrM3&07iXmf;$4%f*ZFhsL{F}TS-q6o|&W7fPV_niVQ-Jd} z3AmiRH4IXKTxtM7(ic4Q$q1V`RM6Sr6HUx1ENyp|IKwtf4WBgf;YvI01->2&7?(9p zj+o-hfk#Pn!of#IG_I`ZZg(KQVh4px+zepuk3+Qv^1ZeqC+Da=$xL|HYttontKF27 z`ktyfj!E8xp{-LOr+@@D?!UJUi|HqmzY1)J zbp^QfX3Yi~=8Mh#2!T8s#?AcIW*S<<>V%;#TPVLn>LV1fi!n0cLw|=`)Y=VP6D#Z` z#t8}8EEw++wX41>)wa%K9wS8b;7N1@u+j>G2n?(u% zxQZtZR#;pWbZgxEX1Nd*kD^IhEW_wvzFOR55LMPFjgQ;&3GL|DkxT*SIQlGLC!W3< zq=pVAeLkn6XX6+Kv zNKROy(!_EWOwz0Z-pGEhG#6e$VE9}+GK&!+HSc?8|E&oIJM1lLAaXv8|J97WBBT}H z-dt+EbQflEg%I1<6GK;H<_l}5`Fk&ZcnW0;e{)QMPZ}^s0?P=+5?wl2xAsE zH0@^shJXB6BDpyyPHoDq1RoTD-tjyR^5iFe8>MM3gmRN^)f->N4Rs}lGoW#IG2sIj zm02xh%Y4sb=)hFUFZI-YlJ$oqgh?NkHNcM`JBprvsWKe@1`_v{Vm1&2a!eXHc4;y& z8HO)iH_2eXi9M`!2+@B2fGOP{=O^U@d2?D>_sRvkwPYo!^&-+$2W6R-Y75W$M1Yfk z(6f_0W#Tj9d7c$%CIhe*{M@T%WZ$q{Ja&$eCDkK=!v>Mn#uKtLLXA+$c7N7rpHJ}V zwY~v-1zuzRaaW^F1O@A>F?qx=5EcMm5}(N2DSA#Cd54$ydaNAFc*yw#CTpr*pX}T1 zfy53TuKz<@^C44k0(cDvV1T{I-yLV;yfOhn!bqVj=LOgTNOrgg< zDebnsdnQ|`-225!A@i(@)5&K~iPnEItarDSwpEFzn0^|?XXizdjqWU^Y`*JPxA7x3 zij)?1sl!!p0m7^qO?&?~|L~liWnD>w;j=1)1j&)3l(*>Kem8=wvn8Y!e$U!^OZzHJ zvv3XVieNaQ#_zrO2iYucXfRlGRhB;o9MuZmj2C<~OXd`W&ZX^%8SkX@HMp8Rrs^6m zn)2-N;Q^81X>z+D4oTG;Hnt?%ArgR|9O z8^i$r7+2tg?l`d%0Hzwb!eh5$dagGm-Z!MQ&Y{k|!K-}@nLJU)(WBxH0QCn4C(bD^ zyU$A3)@0;)e{5Q@?p;t&+Wtx@=bx?~FybdcOyKnl;PZM;bAojC#^-yc?8}O+7BZ_d z*cT5GvT&<(rM945^RlPF_o_Dzp+LYn+S9f>YrE39dqW?C7xl*71U9IAo=h1;f+XcT zlRB0SZ{!SGLR(b65#k_##Zz>7W@22L$$jP=CWI{`q@nt3<8>Uz$l(uM@ozZ1-T*qf zpBp$6j~QaaBV!cy9?UvF5%D<|I`epK^~l%@?20|qx#&a%!H#&;zu ztc;6JBo|R!?xsHe4J2rw$3}%5G;B|01*%vc(T-H`S=# z%NK3)`>q^`JDDEy^+o;Sq4S4fQit>Np(*B<8Gub$6r1N3a>yfHuc8h;SvOjnFphbiHpIKO0C=T&il}>Q+kwn;fRljY zYE!lc$KR@$Mg`ndpXA;2v_%7Nhsk}?bLq2>J9eeY&78?Fr~W|0AwEyBmh{#b&ebqW zXRn2#gDXtPVsmCfULguBc7v&jFt-8irP#P?avuTFbfPQB$;7KB`N7frYU{RzPHVoy z^Oz7{Xi}^mobrS6Rp~|-$%I$yf0&6A-d=nRA1=jkqat?7Ts^6&8->6&AiLl<^)Bdi zggWAm@xy1nyf+hlN$O40OeiFHgBW_>y8{2q&9L56h+2~|kVLfafL(6KGai@JJRR`T zLb7)BqMQP$7u#n?&V=PX>d*WKt&{-rrB5^#gCf&XdzO6iz40uXO)_&|DQPI297uh{ zSsY$C+P6TJU1#J><2izt(S_|P8u6j~y74+%Ae71ne0UDkl%jB*syEP_m@_erPI>Uh zch-G#zUB{m1fbxRy@m8o7?q|hR^-E-Ub%vXCYI=;qro(Ndf4i9O1?D;zf) zG2Ptpa|C@2DaUyKGG9$IcDPKv?-#o5j8h2xYU%ob^7k3c1dIJVcX`}gLg7Q0))lQb z6Fc-KJ4tX2RTRR?GC`3|`&o83YI z07&(3)43spW!pl!#Kfq;neF6z=(N9GrLAkOQi!q&T|!xM7H}X_Gp81c=P3P#sLX^& z6Txj=#jv=Nw)rjGyJNcUmPgv9llBy^OV9NQb?uY?W#!>k6evG8k{U0p!8i|(#ve@w z)I^)$pb-7Ee2xi6%uWHI!@Xqu-Hx4|x=8M5ds1 z65`f3ZSUm?(&D~xvr>tkQ*gt!phIo}o!0m&7(ENv8}=v0SLb-X5SghgclW-;4R_}c zXKXYd>cS$0Gi{F3@y z83>~Q5OpC|B4m6@I{H-l9=;Jn7%k_4vvlirh+f=vKC=+rIP&CgqiX6ptlgA#s()$9 zK9NW5LOaJ!CK;t57W}#G)V)KMy#z4w_!lOttDDRVk$#DDam^H2?Lu-=}`XLK0 zZ^S)c2-DJ-pT}9_@6hXr^nG+^ttBua}T` z$dY_y_W{P_&6@J92-)}CCGyOpf5VfGL$!dYgoSd8yg;3^zB3R9{e);{1J8eYJRxgJ zT<*ueU$todc4;x#`QipN9Bf$2|I7lGAt0zDd7oDp0m~^d)o8Wam*?0H*SvYH;K@E_V zrUxUq(?zjN-E+MZ>rIAA0I+^gWLg+@cnpG#3?eyOnY9r#f}EYbM6O?K!V^QO@qZg1 z7k=W6pr3@Irk8L>FXZ-F%z_T+A20rhxrkkbi?-O&)+u9e7?X_R>!^PQ88@0TJ6>9~ zo-LT=>CMP6gIXzQ$-@g3(1I`2Y+uY?%badc!&mp|?yos5;$DCQH)Pn<@kzbAJ1gD9 zHHj@G0aa+G?)tSa;{Lc%{~eSXXWhKDA@Q<0HZTPhog^Pcs0A19Oy#)lniIERZ>fAxB1ZHq?jXFiF${7q1ZGhQIM--T$7@LME|h^^TV0=m)naXbEI)o;u>j~ynxpg zM$)5?X{pfowg%Pq5t>8U7J$!HFC|P|*cGbuLr?|(uOUe?@HIa={E~7>)aQKPqc@D7 z{91`Ob>Ob$;;;5b$4^NmD1zU5U|>1ja>WW9PauNA%YGp_#JHxFuqID#%{}*SpB0p5 z-?V$kG*!YZlE$ldv5v4$VF%j(UD=BWyf5f6z5Dq3>+&Cw3~~<9y#9)FeeSr(89gLd zE1rZ!L*pEEFZJetN9dkSj1(D+x^txIDf5baoa zi>F?YI%l=9sqegLw0hAir9o$a?n;bP!E6BdD>Nek!S@-tow(#Sx*Rq%(K%|r>Y?bS z%PY_~;}_X6ZBltG=(dtd>)wk6{3{gMjId-J+UZTn7##yjLb-cMRlLSABHJD1lD6!B zT(dOI4sDy#ZI6d)U>w0H62+|+a%%Q(38IJAi^R$Kv+Zy#^V=bXB(+Zp5<~^fX0+Q6 zt6l7%^UTl2l*JQQH$Bl`Z$2+dmXCqcvSC-1<2(+5Rfo_Vt3G8a9W=bFO2$8z7-ss` zlv{jk)mK9igI4dY(jE5nr%mWI#*V1@VUc)!vqt4vi*`goJia43v7VeJ3=g)@HL3W4 zwjDo zupLLFWLc_u*clgPcU2INi2|foD8eK_PXon~KgpQGNx+w62dD|Bu$k4OU7?Y*4_&i5 z^A??TmZEto3M|`9h{2*fwF+UuA=>y~HS?RzMdYSeTL#e8f&%zTLq%G4V50&^rz!Cz z5D0fD)`!lreMT$aDC$y*^CvVrx0=piD`kO|Gu^3Q*N`Q1s#nX;nH665eA6Twmkg9u z64gMR)c}#z36QMS!4CU zwCI4 zLBZm@(044rS(V19|Akckz|1J}V;_Wt+|!_~K)|mfIcrj1tQw;AC9dj?bh_@2(?9Sd zf1dnyGXEZ)BwQP#6YZB#o+meXhG27$srhTSHaH9wT#$9%CCVGERywTd^HI_JrJGFy z7yf@r*k)VGoF=pMp#2SDl^Bk1cVHyGdJbeHdBT(5S$B5+1}wW#I!|)s)H6grHC0qL zq`C51zD^1l&X0qeUv0Sr7X9V=+%f-mt=XG%wkC*5%R9apOi3AG%J;i^K>SJ@toEJs z+xvUyG^|vZC;=#bY|6`Q(0xDP{cnG}9p|?l47-`=HiU@j2>g?+SfVX=XyzBlXXpJ8 z?M~VI|Hfr6ul+4g3){&l?$vM{!m;GwSp0?atM0H6#$K@DHpq#eJ&*PqPn+cRsJM+H z&srs-UUc`xYiK_DV?}X4ScI(q*5XaEPBa(O(T#L^i$s<4fML+B7vI1Gp%U9+VK|TQ zQQ-sGjJhuDhy?R(T%8+eWPfH~Nk{%$+|vMzHGqZqswH+1zP?`!iODj|s!$Vb+M@2+zAbQ58 zd62_6XP7-z^4JhHae10m9?$4VuWd7T%_5hc;MPgKJL3LX=ORLIvc6o}SzNCmu*?A{ z(yHy`nYwLOsEQh1jj5vjdR!OiIhF)%RV%ftLE3;(B#PYcwBoC@KH!&o>%{#VXMPj|M~cf{9&u>!p@j1 z@?Z8ZZ`g0H`|`-QzTU~0NHXGl*Imqu&6k;j6uRb0nA2<=o3z?s>5=*7-^UIVE`P&n zmj}^hYVqJDlbFlby9Jh@pNAqH0W^lphU6o?gsEqIocrSZ!MUH41@w-76?ZtF7U-L+ z(?gcBSS-k(Q}l$?>R?E2aaeYN7sjWffHiItpSc&vKEl(&)9gk3^{aYv^VM-oz?!bj zp-71N2EO@gK)=wu2lx8HQbT`nxT z9k5IecOyce&5da^4aZ+#d=BXvqu`eGc_bV=@cbh<6g;ATGVX1+J06eep^UC(O4Ydr zjkag80r|hra^Hsl1M4vR?mt2)ncdkGT|@l1Z~5IKr@`O3mCWYDixKq~-=9adzMb%3 zR-D#vTv8MAtba;^D?}=!l3@!j{1KDYLEVqfRqjT(N-1J6{l`@r8;T;u#;Xy7jKv2k zxRi3Gz@YZiWxqElL0s?xyF2F_6jL=b21}Lg&7EFwCn?^3iUgD@oV36MDbE|EfK^oV zUx`c7zX*V?GIM57*k=Vw<46$w#6c~b{(QB}AXx>bT6*dp;4}mYvKNBFTD6s&AKCpx z>lrMRVV*S4LsLcRMGtAs9q46IdEh%bb$05Xdc(<>4GOwDW#4zGmeUcDHrAzLNmw;> zFTg`A_a|?7k8;Nz*ZgBgS}nIdXv&n1>_EPm3u-(Kq){ieqIE6Tsrg2zal{NE#WJf+ zA-{NkPP%W-tT&2%{m%5Y=31Vg7; ztvbagio37?SV}>mq)bt1tdx9CA3Gh_5WhT|)pG9`v<>4*Zpy8vF)iK3MkAl86r|_dF?>382-h{A`^gee+%;o#4!-wa3e)XSS~n>! z2iWG?R77ljj>`p>GZtT%`n4 zMT(x~;;93aT$ZIMs(uvPv#g}%U#0;87@*_v*fDo=PQJ{s5~SEZwUziju1r>lZ`c#b zc{!j>`wFcClW^kumAs}qud0ubc}O$~`Iy6fDdFpj1egaz*1n?F1)^KLMUfA^M-?J4 zjBESxC`f?`u@UD&-^zsLiw+w(8gZNSSe-8rmi`?V=bXCT<$)!qRAc7w@Tyo2lU2yw zAO7AznV@sj+ua)`PV_Mql)W7ZZ2Bn8a~i6?x28WdAa}qFX9u}CTQhP8xfjFA2WGwa zesQv`|GK@|JJAU)fB0;q#2=BTIykCn08CpmMV29TJut8tD$YYmJshAB(yTZlHW;!o z*0*wjt!b+1o3$@=d1eYx;t}jFPe7BjlvIOz&Eo~zI^lgGGOd8Ts!`dX5_2a{Q~}vz zA&2OP%J`}f83Pa5@&QUqJiKqr_t%AZwCi?TAgL9(;ET3OP2jZ%3SlQV`=Gri zT}k}jUbtH0sb=^)&x8BOeX8<8H$P_TJ9oFnjtQJW1Zhk?UPx+pbbjacg^WF}9uei^ zluGe8!(*#w5=X1>ndLy9RZHWOO(a|dB@M)dH=L;IBNK3|abGc;9IZ8eV>xdE=2zjI ztn$7K@rxbPlujdz(RnP>Fk(e5&!*MXRhN;?IdpE^xX`BlNK{;R$<_SZmkCv#k7!Ae zn~xH`;{tLKG<~i3neS8Ie&(rRl{wRi{L{tnZ!L``NfXr^9sQdJ+=d7CK*Fr6T~zJO z>%;%>fs5o4PYV*8-~j(vQ#ZxH7~1A#&gk%|)`Ue4>}t#+uV-rUDsDwY=Ii zq8m67e%=LysP_R6sVvsnoo-K-l#|Ix8D5*=I?-UmAkEJE72aA!%r`hw-G(C*>|aED z0^fM^^|LJAAk}8PQx!|p7Z}b~Azw1J3V?kx^cqB&vlTNo77DJtn>^1FD;Bhs&Rp~s z@Vui%bqg7Dnx3js%Z1!Q;6+&2&QCK1z*0y#3#=J4SC5NAi=||w?BMe55_UUJ{UO`b z&jAn6GmCJr!cHJh^Zpo4FpBrXWmhQ%%5Ml*9SRvdLYtPyX=5K=qKECVK2iFSI;fiD zT5{z*R=}I0l9BeT%hmAY3PRym-3SWXI-$d7ECiaZvwAEUmN;I=19W}6VYeJqxtotY_}sZb zf8GA2ulhNzE0LBjw`6k2Q67F3leYwr0mUXHzz89M3Oe!x@P$~d-!*~vGWI(TgzlE9 zhMre!+ym_T@i&f*Q));uj4=JniRaQ*GOaZdKZyCQ5ppXI+LRh>vzM-pP#BUM7#!M) zcQkC)qSezYo^n*^izsGEEMPnbJ#7o5>gf@f4biT(Fg5SI#;XkIg8||+lV?vdfyXB` z@zuZ#Ac46Yio7a``1E)ktIL^=l2iOitjIIuRu@^nq;C1{q&*t(D4rAZ4#yVJ_437q zWkHbh14s`eIV4uXbC9DAbl~}IcsCpHC_Z-;umdmd8pz5GGK|og=MA9_ty!;!KRL7G z)`ij949P#f>iqd7Xe*8xat7@D7Rt(!)#MTFY4Z~9iW08Ta_1J;J?iKP5h%*m4@SS8MOkl|Qfg9*k2|v;lK&VWc$5rjs-AyiQJ} ze~dVedzD>nV51f5f}v0%)J`vI#ptxkAk8R$B{OKz#FWSVJm*zK0@5J4r%n+l141DphZa+y@mQj0i%!y{FmuZ-!-jGAK^6;Xzi>URWS9iO>11*zQfs*rNCoDpH2 zeKxo{YDJ@V#_WBwMYb${?o<^$cK3qh;$wI*v;-p5G9k=%y2=CF0vl z59npF`;G$*H$RF8EgiUDUwk-amgbvu1#QIIJJJL-Sm7_k zF$pnf33#>)5#VUrOFt5wfH{zi_p^4~PzqSid zi-xN5TyjLF;5jV_q1u1YL&JU_u#;3GbOo&LL4XC&3T06CEPt9;t!t*{c?TL>nFpg_ zv_OX1%z$r^R+dIKSZb43EBd$|iX}0-zCi#!Y%GiAijeG^XFD=-S6aD@@!p5O6C27vrD=?d}F8`({^KLv? z%`hZWeaA2$eaFCKyvtBdM4zElH`p5X)eJ*8iA;xa;xrs3t{H7y%KeUUyy)-QQm+50 zpDFgQ86<8yR{Vm+RnnVQ?E!L!f^fyMk2sOnJ5*}TvzMvOJENG73har;9Puq9{f*b| zDk?;l3z5niLm!oHLeq6)%lsX~Y)3YXhq(U=mB()aitA|nGJUqw>Y(~ry*MRgIOnO@ zB5atu9WzUd;j;-oXYwoFBdNQ65{-i`9d$2c77m{S*#&Mb-R#Orr7k)l0eQ)nhBteU z4kLD)sh}sGb1^iR0aVxJsoDh!e45o%SD=zgmF}`ew?<*E$z$5?H9S)plw6Od_}aYA z33z%@-4ONPKfWM2w2#MmKBPi!HLE|3kTRsiweiiAQZ)c_QbV+>3>G&}7&Bo-iR@~) zdpEc!EH98-fQ^KhtCTNLsl85*YBo&Tn-@RZP^{}6APW_eiS7dI_>RYAVB4<^QX#2T zKZ1Hx32SM?@uAvnMs1J#D>b-wL`&UnVZTZtggd%{#Pyi=i1(F@DCnf=*Rx#?6b()1 zQ1B^nD1k-720D(blg(VKfnOlw zga8MGs}0$*%dIDpg{Jo_ZNv}&!tdv9ye+)qH<7{1NnAvdp_Cc|ImW7bKi?t^Qu5vm z8`%O5=U51?S?&(DWms>IJFCNRecXWmt>-62K6j8fL-j>WoQzo z5bwG+*V;-@1kkzij7WbF9|Cg5kRKW2ZdW~@Lr%(KxfO-`O?Zr{O0z<`s)R(3?E2UZQ|8Xa)$` zsvJcITy6k22p#YO8VfBvF6XBR0WwmcEBoMY_|$^Vu)U!ahkDd;F@{BKLT7+P|Ac`Y zvmLL6*%$8I)nAM>LzlnncHYb3-?T(#RIyn~%$)Ygr{Wxa;+8u$ti==GH}alNfYsmA z!r|rY$+Y;OB&i2sqU#v8So4Glg8%3>Ii!5&K!td12?n}q3b=-G35}act z2XiTU1i=?;7GuRIG$1$Tv=x;vgY=(#INl;_D4vVAeq<`V#EF%I;B!a5;H{|n)Kgf?F%VC>%l z)vKx;+N|p7^9*G`WuHCKjdyMIV?|3~NTLu;$N7+la<%v4>C9T=&D-(yVX-Dih6ugF z2>WaG6P616?%kiF}y8kGh7P3)n~Up=fl1ieA&?Ee9_c#21i`Sm$`x<6kZqf z7yqXf825)6ij*_Q-Fk%3XaN07$qGr{F&APb+|0N?{Oy(PaMYD;j3viiHB*p=)t13~ zmJD9n#wQaMwG=`{H$M$2@9}2?S>lkLa|oU}vuI9FYGdTV=6sxaN!*ax0%VBN?-^(; zIS7(l*FoKb(9WzU;78KLl(k-Q2~DNd1p<>?%3Tm$CM3O z;WC^?Iff!Y^7@x0uEw;QrE!J$Csp~et664-V4*$J2k4EKL!NQ}>XfXAHN|A2)9qHc zM5d-VKw>UVQZ|;Uz9`bHXAWSINT95|OjXq-Y^|OsXG?=cJ6xZbq1KTcw2hwr{_oDO zWFTp3bEv?D7Ul%Wh}!X9G*`6B*PZTbdlGKi*yp&G+mGD^?cwDww1z!K_Dtlh&sK?vzU zvy<%eUB^J`87X7FQv337Iao_`C7Zp95 zlLvb^+Z!Ud`9AO;b0{oKPLYW&Wcv+l5kn^n7-P$gz4OIb#w(Nqh9ttN5sZ}}5 zUh%(!Gn8Kbg4ke`Kt<$*(q({sU(8viJM z*6UfAMx6&m@fYpgfdPYc z{1e%yBWj4emWJwX)hiM72qCHzth#GbE0oMd<$@~0y`RXO(6<(ps71bTOB zPT<8|Kt8yC3QhVaXg^0OT;|ik4}$OmKdsg2g?t+uN$7)`OJu8UFo&_z6J?hO9`pO* zYrdqsC0N1Qk6E~&CEDCDpNM-DDwq(Bn7>sT%6M|vTsIr7MA{EAvXSq| z2rLlGywxd>%SYRYM;i}$EU;tB7$qqE%<)g~@J!ymj~o+QKVW{^weUe}t@TX-wFFDS zr}d7|fyJH31A4?(I|!x&8vQeP@1gfliW$MAjHx7D29wWsV#B$rhRrt*@23ceQk`n& zDzFF0DAEVQ*Y>jcAH*lke0Vo9^X_C!R^oWjfcDWz>`%g6J6Ky3T+$7EOjN`X#MQoYJ}Ta#9*|^gokr9<%W+x5 zDY}zy2ro@4fR9#VS$1e|@DR}Q+W|f#PY80EVNNo>MM2#`Y{PQ`5e05tf%#C?n}WpF zrxkoT16m_{)A*pqq_H`ei+Zk|tLXoZXY_US3x17Cl8(>t;zMGSVR{|(bQ9F^Dt*zb}3RnfCktj_HRMd<<_P}>lF zvq!~rq|H)3oNVdqFTbw#OqL@k>B3wvzs?1RD?E-j-Gjs00 z#9mm3t2bCEpl1V`@e%q&!*n$fzkRmW?4eY9WD_T`{bZi@Hr*kU`2-7WNL;88oIfrO z8sO;3G=jC|_shQwe?;B*TGXTJ3M+@siT6SsgF(-0pjX=Oo(!Xoa5LuUwo7J7bOrD9 zvic>ros3TO+Drdl_W`8pd}cG7GxS*>z0X&G1B9>EMt-xJu6By+{11>OCa^;jyNzF0nm zu^I-(;xSP|ZEPsiBavIF=z1YNd6=*bz;ca#AUqRKipqT1E6uEH{6TT4o1=H{-oex) ztFl28LWZ_wThJcz&z90wQw2YiN-AYB49t`I_U-8>m`r+yh1qol8gGgEz5_lWES8+h0aEpV}nNhP>hoV7m}EYsUF9#)8- zc{sDwCa29@pTOQW#!=lp1}%Bo@WA;VB3!p~h2h!=Oc47TSxd#Q2=SZD{H1|Q5tYuY z?G#=5I8G4YXYlC%G?scc)|?vI+SG2lq$f(`P{p+!p{yO4ao3LwKffOp6? zshRVHB==~+{4W8SW?qs1>Pu*St6p-32vghF5lrherk93b`1=2A1(xIr_Rq~08MN9E zX(5Ap?m1kV5H5Q-LV?pPL7q5RXuc6-rkCB}~O&yj3r!#drEI@vW3gdrdKQMc^l|7daZ)!oG{ z(-kl?#<1HU0(OhjZN4OTe2jO%B3E2cGV)4@olPrOVqZp1#wS3bdXA=@jbRgxBpr@hPYhY1WD0Pi%0?=GxKYQt3**q# zP<%4}IGFtRMJ~|>#GxUG+at9DcWmq0vl5Jdon28PYMl1Z!6We~uELMSmz*x4j0}if z+feLHg&%2RJpdsZ1^cq|y{@mpJLQ;g2bh|$W`Djlc0KOiS|VkGYYN<#q#Z4Up6+k;K#hIY7q0Y?XTynnul)g)PRu2^BE1 z=Y}m_-L)kp+G#jz0YJf;u&LLi(Wq{Hr-jg6T^&6^8G}38__+n+uei`M+!Gp|GuA3!$Go8|Z3vs!Kr;*HCy48zJ8(fd zvT@|@&uY{eGK7KGT?9&;mPJjf0ndSam7Ah8dJVg@MCk-`KxaNY)g42FM|3$kiuP!` znB;pVgLz(|s3_ID#0hUzXaYv{cWXGZY*N#yS2x&}H#a2%>^`Qz5t%1x@_n6vkMP zIu1d zaw_IMEo4z{MA(<^hn>C6vHXr0t(EPXr3DtVTK+y2*QkPf-{FfuyQ9Rcz)$T4aHGUG z@R2sOk$Vh}iA@XMj^qs%28}@(#FKn2Q+LIc6@-R!7VU0|mz3<=Y75374IjC1Vcp{u_B`MU{(O!MWt7^vjejNzF% zG6)F*P~xRG_CA5(a~Hr$T9r4X<83d^ zZXYiOUo&tvKMC##Y(FhYEq+0B0x4Asm8c}eL&LWar10ICA-rC{+QA#RO{Elq6q;jN(6ffx*;CCfWgC7rKc&zEMa$go|&GR zUlks4o}!N152BKi3}6`g?3k8rc!?Pw`13yUiAM<)^m)Q*MMpU=?&H>&D+aB;W%iNX z@PISi@;KclL-%DcrX!S3v;63{F){`sI6IU>d(|fOweau}v?sZ;c8M6-cVd%#`?Q!2 zFL;w`R7H;GQ{U=D$g9tbAo>av(oY+?G?{ukH<2~&5GadS@1#w7>!8hV3!JStqIhu4 zOv5Flcnrl^|Lv+^5yCSum38i);(?WJ_tusVedt-RlMbJ;kRDtO&b!_@4-?6JmQ=xr zb`$TI!mc2=%mcu|Gb8xZU~Zvt0ok+?F0{9f(Nvh!)%zP9OHsBeJLWp(6x$2l1H&WF0B$DIBaFlwCJfc2ja!>)QNtTgi+qjUFD2B*j zVClD{o`ng3#w*=`*wQ2n;g*4b2FS`~F1J?om5|KXI?8o^(Aivl2@#&o{yn=PM_o0M z(@`%*Y)+tU_oe|9cJQI?0O10o2h-<)zD08WFxpK!APTUd3e4Bvl?QN-I_TzH;>NdKRmw!(k4j>ekf3py+SAct$e-e^;n|eB9PXGu*dEG=x|@ z)Kt3|J}~<9I5PF?$1u4d$3gQo;i&5M!p)M`-j~mf?HFjio}O^0h5tWC*)ui@I{e@s z>*k-F!@C;UJ8eU73(bzeFng69SeW`D|*#enSax*mBwX3wxhc^i{ILowo zEHLtI$a}J=ZiaQ?ypUFDUi6BNB%_)$d9W$R8< zDyS{(8o&Nq7aT}Qu6vb^yjtRtsm7fC(?Tu9%f-w?k{q&C^VrNc{9LfCnS_b79CDx=T^ZbOzFw!tXyxG&@u#NRg2O_FM;Av@Hn4&JQa zZf42yk-Eo+y|BiZuC?cA`)iODv2u{UQPBh5*Oa-dHi;06v&>F64==`r8pYQ!_9uW< zip%CS$(Nz~5?0GZH~#9w^tZ=ll_b{& z6I-moe^i3>tnx=(Hv&^P*v*ON5=y(U_yHwHT!-cVh#7t#^SNMu0{Vqx9G3~P&U;>f zYzro0ZA0%Cq5Oa-XdviH0`Yx-gj z(da@98+nXFw>>e{(x3`N1!s4Az|pL|E!X>-Z;zOnYVJ1OsY`5%Y7DS!HfeeZ^xoC1 z64_Rtg>C6TcT9m>v{L|Zq`2*RQv2O)_gSwD`Ap<%>_)ye|MGjb4|<2+W#O}a-5htu zibBU$y^Uu(i%AwdwJLUTc7GR6n%JeLquWpNyOw~gt+QH!J^qDBu?y`VxPK;FF7t2l zm(TX|W~&2T{&|?DDxFoSeD`4)AvDwf3@jz>;?cxSewgDTG9FLhUgoJq9<~H48ga8Q zEhOu(*6@R!`g!NAi;`6rWva=Wf2$7SVfY>mT(|`>8HMpCzJ!x31q2X;U^1Yt+}PGx z|N5m)ZdD&SMZ)Mxm<+!p^o7dgYJvGG-4#V}EbtbDPAyq^2AVktGmt0xqsx6JA=pIp z0OC={)kln^zt5rhidxIEuokfB1Jw)RI%|I%xQ@?v+=D+pFnnEDtF2jwt_fRfNhJkI z@5&M-f$p%11+yqswZJ2e3>9QHc3a<|`iT*HoDUjsf(zsy`AL!Yk&?udT7GKlGby}i zM8y2od&6SGydE>{FxLo(#ase@+{a$rf@1IO&G`{L`!V!YqHB@rz1SOj%Z0z|yUn+6HdSdQA`4%~yNAAeDS$6pN^u?qyj`svzdBjb|Nn#$u~KKNqd43m z)-Fi+>KR$|a<+8>EL=qnW;LSeo=E*PzYi}?X1fBv4QLtb6Sd%dsG53;t6q%ST^MMP z8kddLEb_`H>QR+?&8HB@(_Xz(H>|?)Wq**x@r(u&)ey+eQC*%td0 zD(SG~&+DqV%@k{)jb0V>gJP;z*(Af7DD9JtceD0W9VrvF@N_^ud)XU;qR|=M! zc@7ih5bpw)Ahbgx)utcVNJ(Zs+@F1TD#yCdE3YFH7diyKfYU7F(m)!bS z4vZYzfF#u`8OygdbAq=dg8$BsJ@4xUVxE#)6N|))O)+yKonxR_qFUEhMl%fvenjJV5R!oKH{}*F(J}X!ZW!AMIxaxOgIS!B35_xov+uV}5_*%}D}*{h0{Z@Bpxj z5(Fw!+0gHxoNi;y|9X`%Cf&7v-VC?nnBy)IUFD>eGwJ!W7NH08OZG7nkaL?=_gSq8 zc;l+DjrxKd&y!wJ8b%bPrcL{{&KPjyn83JE05gd9+cdOh;6VLfG16eSmTEd*=aBXN z-97UPr{xki&8GKJsfgT4OtbzkFRd3L!~!o>QApsMgXZ}gjsVI zRZ$}8KX=k78-4LuL^_ANTg>uV00X1m4(uHdygy6~WE14EYA;mEJ{IxdXZSl%(7{IY z2ePVNxci?!3GW?6UO;Lwz?FLH4;#>ZOtDf|l`O)T81$rjy^$bH?$aLDFG3h0pVCm2 zCCvp7!oeK}GPd`~=E@=otG?84p8vi%WG-MtX7rO1N9IBo9bkUU|98oWXLBh378oJS+qVp zzUaM?MlkF><+U7a#+M!2dqK}1lfM3n6J{#?0akrDbcQL;1R>CqjDsL7DKe`zd0m%q z?w;&px-lYojVz@c+#Tz!k{Qa!D;eoPSCoO6rC!E<3mpvnI1gyYH@o)}K6E43Y(P&} zcQ4G>WlCbi4fjKQ@H>8WFQPRXiSJRv z$9bK{>&qOw!0+~l-BV3 z4^AZ8Z0lP%$H-3)3>`OF{SQ?J`79wdr|^cSg!OX8ECfEOew0ea2bIsB zgg|RmP$@!d%=22`sY`ThvOD{jY2I4;Y8ql2ZyQC#)2X_BM~nF>8%!cw!^gM~zr_18 zf4^Kex6a@>k$Yu-T|MkRjHeEm zFwW4^FbupUL`Kd9yVdp&5tsJ7=1-y3fft~(f>&CtnL{Hr&kCCVjwsEn-3Y)_5i($4 zlEMGj+eI2>Z0|?&XX9ny`g?Hi`LAhuN?W6Xm`Mln5v8_wvr?m~#E26-SFwt~cctHD z^nxDl0*@jfDhQ_5eQ8`Opc$_hsDjo#sd`b7avOmepkfjvAcYyp$-U6xATIz2X!Oi z)0qV{GUu&xrSSjock`ZF>)i*;cOrxdc_oP+KSp+4L!ALBoegrMS@!V}xd3&2 zt+vgcIZ{*(r!fEES~}pnGt3k2yv&rT7x}k!!S);bl;|>ffRZo8dyLC^BK=R0h_yva z7MhvLze|(_O1Z_#jM7|f^SR64PdV)Xhy+xA_-avs)`1cTqcM6KZZ^?R#yt3ksgSeo z#MAt}6_&YUz%CX*%ua7DIevS6vSoMNqY50BMn~2W0BRdrk084k@IDF@0ABEm56t|o z_lG*;Y4*NUO3z6GSRXXPo5P&*$EN)leyvHjz^e>Ny5x?40*aEr4VXm6YaiM`aGF(` zOw!tg{JCFZYfdk!+c$m|&57J1o}=n%m>fjYkO{Yz;#F7LjzA+07| zXI0Hngm8Rb9*rA#5syY$k@IgZH;on+_!5A?-`z6rJpaTLAM44~5;wT=<8-?NSZ)5Spa*6HuR4G4>K02YSu4oWvvWOAxr& zEls$>u1wzD2An(A(N+{vOMsI{=SM|tX0_U~3Fc6SCSHfXf7rHRWL4Rh6ho}W!&(kS zk0+0Q2J~I>-UQ;Q7-`>H=sO94HB%W1<)6Cg6Ia?vLhi$DM#`YeADXkx6|TROXZ@3V zNEjKGtkk5(0vw&07BJ}T{2rD9%?da?d9=d1Hrasa_9Pu()cTRhK#^QQ%GN z+fbA|kjL7fJx!3$XMpEeFnDw{aCe9q)P@3&W&<(Zl;KtjxRU?SOq70Ddy3&}3*x3R z2pC(T0`PBnnxZ;@I_daW3FmA255gXx=)vHx_p^(*UTHadg}M2LyC0~hFui0x>cxz` zy&p;9FH(fYMV)%o1EUWl+Eg!G;Gu|o-+dYfZ?@>9F?*`k;_2z?>D3P#p3|{DT*m0( z0s}q%m1mw?=iS&_xSyHfY-D?5*8>U*4e}oON{lwH!5t_=LFxdk;RIDWYvIbLOrGDz zscNx5@;-lgq%D$2U)*o9na{Z=jCaeu%Z_-^Jgh!ar31duu`&FTtKI>S@zDp8=*>?B z{xF>~2$x=3;iBH`!XfDx3WNU&viPmo#QyP2Y~$2mUpt8>s=?4)iz0Rgh|rr4%5f_I%kVxSFBv|*<3zw&Q{~le$cC0#$;i!XDw{yyLNV@ zr3-wr7~!c0>H0oYS`8b3DJZ&%sYuB$8sSt#A7|@p$yg}_CdU_bT5Rh_Ml=@XfV3=c zU#ufhLQ6p+QRe}3I`w{%jo#iVNfhpnuBp-gp4zhF9q=@F8ecw-zIk}_viSYS)H8_~ zKP?@zDBUd<{!AwYRWq7^(AgdpwKPtE1Ng{F=9`kY!eKmhfIgueILoSeJ?$5{KyA+( z|L;Kfw=z&abEN(I0Od+P)(v#4X;fX84yxiDn67FZZ$i12K5<%iP&F z?&koQ$&|yTE2mi;3hUt}#VbBomo+&DJIX)Rtlv^ZdsubkX1Q8yZs*YSzC!x3qjP++ zmpwAHZGP09;%C%kNUb+qI}&u&1N`V2>_>rfMZNO{WJrYwI*(Tr(^FsChoKAJp~w0| z7upmp+X$Sz&DxRxnkEP~SvE3UEI(8Qj`cOBZX;IXhkNi*mEpG4!s%3oOp!19CWc_( zL{PFEoar+XrQMD8ceSe;4>#}0)?7JIv@~!ufMVf;bHt^Uf}PX>$#MEi!~i!Wt8GQ` z`)iB;u8nZiS7S*n4R<$5&5)Le%ceoA@{i#Hi$>`~HXR|}Nyp-^2u>UydT0(u=y%VA z2|Q*KAy4wYvy?m$eY*ylU7$WY0=A+)+fcZ6l(8SRi%@E-LEFYOK4ae6-t~x|i z8`~S;>w!{(HRIsYLU6zWL@B6!dc3h|x?-Q0%2f9%oV6wG&XblK0BOvp{9p*)Lap7f zE14uxb7ckRaO8xk7IWJ@XH* zwM-P=I-Fz^Ms{O|@X!+ovg$&6bI7!N$=&sRAUDeecxtZZ=MRXD}SD&}Hpb+WGqKJ^BXBv*Xdci1V!0qHJGdb^F(^d$? zM8(!Zlg)zDON{IHL#v2^dR_1iw!m8(Cl(2Lxhl^{z7p$9q9m54ex5}TAjM%ZX{5K|*3WTUye;>I2N4r|t z+pSGL!$|JtWLdqjfj!xn!y&qIKi1Pi_#G^mXK^X>K)V%*J-&DNmFRujXy^!rel!{x zX+tFv5w_nh(ob<5*>nYQnX)~>FfL)IL&8|8z-z&i#HP zce+wkI=mEjK)f)JGGYB3k4iP1TL`@c@3Iv2sEi($$FLd z4VK`2b&xSI1kGG}7>a=E*KvfHMXg?TErX7Ze|N4RjZqxyMHjGITPSPA0A;RX1=enN zt+u?8dY*9$7c~W>H`T;>WL{%7Mf?QuhVfn)0e~=JLY$n{6Gl4qi$<|kx-w{TlGN#5DmySILn zM)GL;1=A+m3GrYt?_zJmq?5abeuOsFVH{)*4bYKxnySc3}f6hLGxkIB@kNt$P#)`<)7xdNU3wP!Png9I* z=ELO;2?XfJZ8m>OF7_qH*30dmtRtHkw2^T%W8&mPkLTlNZH(F~eN8XU0si-t$5~{G zwKY>@qK#1#zJwH6NC69G85#vl%TxSU9`XZAI9<-R=M0oH zvGQZ*-GaN!LqD<2y|6S}IVVLiYm>K?2kB?ZluoRkj~LIZj2z?Y*~VVuw8&gJr((lE!b!h3PYfcAA{RMCFu`GZYXat= z;bHg%x#>qmtQk+IrDz$X%j=wfbyNpuHlWPiL9w4f;2CE6FLI(g)}bE!N_3cadAVm z)^P>6;TX6ru*fmHhx|liBT^9`deIJ?qA`)fb|M%DPH>^ZF3{S@ zRk1GK$@p2jl#pm4)fgwqMFsTNfZYSUwO={1)vO}5-dT5c$@!^hA(avh9=TkXAqKI# zL3JMeRLn-O4Cbp*iFc$$9qHgm3kN;BQQXh2_>iJDcCV2%m0VbFno@m8TFsb;XM?HXKF1sfw z{7**e-jJwDSDv>FyPGI>#Q(5p^_4nqsRB*Xo-9ievkRH^cRZ*=j-lqW1ehZm7TYK= z6%pq>(g=)1J{4(M`G#~R$u=tPgEF&O*K`?=s0rNg<%U|_q9QF=(|y~zE&{0z9TRg# zdeMp}6~=Od(;;TQOp3#n=g=ZV_c*XESl(r6j~E(zbdC=Vrb+W#uz$AH^{0vk26?8d5>Wv_`8^r zp;d0OxC?zyLk-~qcSJyc-Fp1Qnr*>{Qrnv{&SbPdRSrs=C_rui8b=~pzW)vNur|@r zKN{8svSkf@lyYGt8^?rJ4u4qv8NMpr8oAx5$KHZ)O>CQd>4FCKeS;x)oxHC|&*1Qq za8zYW(aSd7vkr9rt{9)-tYH;x*YaqadZ)J6uRGfAt{SHkVWs&nMJ0nVBq_)~;));` z2k}43JDidk&QnGalsLU$m#49nYz?81^8Pr@Wl5DB-n#6EFXZOFWSfOuQ8>dkh)Ru0 z+Xy%q+74A7q_Gy^Yyi;AVDG9sTzHD)=oCpPj~DKO=Elf=U48LI$n$Ca$zXeYt{m9E zVm!pN-A%_r;jOJ<&x^jIVA4%5TBaR4+a5ZB`V@S^*8bLwXJ8y;BPco-%ED(=#L1evPFV@_6ns7Pi@&Kh^ zy27UbM8yxg;Nm@d0nx?IHf8>J3{Is^VOhol-DzFs zvH^21$$XGE2?H!m^t-@`?s7khx2W_-h(xb@a%Ho_5gxy%yd6hM?u9P~A=4&xBLL(z z3gbc4){yIgB0UIAO598Yzslw1#ESMH)qW9iU{PwUTf#ntwiZJ3od{t{&2t+RhacmSc@>BHp zq&k}!yl2iH`Vp~<;^1l{kUiFNc624J)Gy1=)z3_=JkCJxI8#;+jFnby6niHW;mAY2 zmfR2y*L1KGr|%j1fQ7FL?I>JXzmsm^%XbZrOb%ZOk^*TaP76l^24({oE+^cKh4fCL zA*{gG0coxOgj+Gj^7S*A^w- zq@I3jV>XhKPl<7!bNla~aNV@3p&osF6Jat-BgIJk?#_;e`?f=&45@oMOjBkOjX@Tn zLfqxWIt)A_m)(&0C|8x$Qi>1_#43IMLn<|0U=mBhg5w z<(s6NFcNqqFf`nK8rv#Q6wKr<`kY?1{Me}g!W)0@c?S2QPtuZ9#z#@BYRb@VZpm( zNHvPDvGD3Ew)m?V)GyPPCg$GPOLKCRB0>&B;P0-D`=O(_rY0o4e;>HL3I*i6g+#Z3 zxH-hpv2OIk62UDv*3NPk($#>Ce48%0QUbyo7&;vXkml5&<^fls#QZ|s#vSzCVH=sK zC?g~jfiEK$sg;dw1X~LNCNP_G1O;g;cq?LI|CKv zH$5xqti)M!uA71Y!DCIHT;XBT@n673gS(E11m!z?~2&gioEJcv`2cwXmOjU zmaQTH&9lK-8Xpl^thc^87?ft-I4oXBDy8oLCm~K9>%)LvZ;_m?zUQ8AFjF@X`OsUq z(WT*-?}9nl7;n&;)0Z(6O6gyn#=h$=InT6dpFOwrfRb>uVB1G)?RyvT-R!$b6BjVydu(*#{@rikGPY3&i;-Wkp9 z(>GP;64}XNr8~0E*KBokalaGhxM1+GD+V2r2@(J&+wt~R*aflMPkZYKT*#`_=78`z zaOhHkxfY{ngjlhgFv12Iw?A2y{7)L zZw%S%#i=RW6qNu=Vc*~;8n}_KqEo~WYIBtz%FA;)+;+ns8d|KcFnnO8E zzrWEKlr{7wy{3y4(+BS0jSAR$iJ_J;B$xCDWA2ax{GB;YZbUX5~0Ffm@pAbT+DoIU+>!B8!gH48b$ zk6mUjd5RN@GwS)G0~zD-^ObyZdeG=-l!ELPx4bf6S}|6@OTQ`xURsZ3El|bSh&o6d zq6n@r#g8*l{<9GsLIm{J4ZYixUL#MqDjqY#2>V*TD;SO)-%YQ0o((+a&v zAGaqe^7fQy=h-6HGcQwn6Uk<(1$*sNsY<_qA?M;1a~iKm==P6=4Z)uMp`6j#=cw)M z-r1M^Slv~;8=~_D-(-s_sv~=X|EmH|!+g^*jmUd40zF2HPI^0%2-w5FvDu#Nsr4wv z2vHuPD>|$uVS(^W{|uq<};zVZGji!YqoC}LhT zJX>1V#(VYe0Z5ZLJt~LzBatiU0I|c1ca&g%yQ)R7~{%r2IpU0MD}ylCkk3MCj;vA)O4K!ZT%=D>-C zRu>#~4S!8!|7$W>F&jJ=*iR#&k1#F78asd9ul_%i`nxNP2VZwJUf2X#_Hf3A4zN{! zZ8ME`liHTKTp9;n_G-0N)RLt25+ zS=Eq#pxr$Z%y}ronh*XL?rF(ru>IEyc}s6s$+uhon`CzS$GXw$$-k5Gja`e(s8#o% z+3^N2ti9E1K-obJ;m6?$C`n^i(ck_%+J>rx_?JrpR!7|d!w=J!OY2^Ol)iRjZ>jRBjR2yC6mDBu&gSzym3;5Xt$ z#&nOExXUYi-|&oTNv#?h9Y|_td^o_U(hduLUr(9BlZ0N&)#Rc))M_1nM3gccFHwyZ zLIq2PV2O%SzY;|9Cr@fTO`^koynxces&Yj;SBbvt z0TG@JD}z6EB-uDKF^8#rzWHv~lId<(QaGxpHeG@#@D&-0j=-ogWuj8Q#aUtRcW8Be z8k<@q$HG->`$_#Zw3#WdB!UrQtZY}E_~kRFxCS-Ie#7cnj{Xm#c{8p}fUQ?xArCX# z6Xlc}PBLb2lEQ$pbTY>-uS-De7J=ce{@x=%zJ|E281e2_%cVIm$|M9soV|V~ySyqo zLHqC&+P?#t75R?)WE8>={lYjxZw4n0YX-|~+oLZ%@GnM%6zKRZB`dJvXA6rUy@otT zPXxj#F}<#$1}-wH-YIIWdehL2*w3TnL%v!PHCu3+JUYpNfI^r_%S zCd)(FKWsNrRBIn50_EZ_hjTH&9W8s+@w&DaM#?+;$VM2aH&tXyFNV!XVver!&Cz5@_lX!S9g;NrPfSDDrJkpj^v$35tVgEYFvBhm{sFQEyvsS=?Lyu5Up+0 zMe$3xf8&KAAHj0-H*=fU#Vf6ikDFUTk^)y$cbXFnO&A+mD;ehMtOHuhcVWjx>{!$0 zK9&ZkS9vNMI}^hkvTxofy(ZaE)?1P7xqC`T2ltwE*E6<_yn=cg>@4&5xW`Bg_`Hux zu*kS*+2!1(;G-|Kx{lEe%MjR8_L#KwiM*LAWlvkaR#sQ&?p(&D0S_Qa0o{T__d;B{e?6H0>N`&}hZ@`naR4vHru@p)P^ zpE{X*23i6%J@59`U7`sHOTc5H4eH7qi9p>YolcUdfgaq<*#w%K>qS0I;81Q{g2H>Q zQiy#1M1vn3lu=&C{KK{#i)3SiSllj9_EaR7YRtHInCb0QggIb>-Yu<&$qOH7f-gZA z`>?zdgPjD-AC#;&Kcd&2+Vs$<#xk-7F^BT+*jPThJuiNEx*^bZz-mPvnB`;0_g~jX zx+Me!C5TBcefl9lDCLC$(&@r0KqR|>~>6|a+|n-M4c&`*fb$La|sBiG@YZB+2hn;l07O+)isUS8iM zD(&y|qtOno5X?6wns1G~IkhBALGJ*>J_YK(#@#-4>h6KT&_5l zVObUg*L`Fl`a;F>juj?Mxv*cYmc@F!6>$E8V{si;AdDpcSj3y2@~?o@+4$cy+Flz? zZVj>{@>M9632lm?aY$W!9BG!M#)Z96i%}BhB#u43I=NKc#^o5l3@UsksEK?lbfMb+ zD>FHpO;Cr?ux-}pdk+OFt{#>O0YUdNVJgjQm^%a~#s_BFKds|Icinre#NJ(#o#1#| z2RY&9v4j56zw39n?{5u^61xX7WIqF=y~9Q4ydec4>G01bqpu zXEH>G*J`L>#!7mv?I-RqvnvZ7z^|YPyEq}C^)&R3OPd)_yp(+UB9_tobmRDa9+23A z)r1JMtCUJ^x6+P=n>r)?}5kq|8zf);JPN4+9_Wl5{dZN(|wT>jtG|;{o2J z8ppqS%OVBH=GArj4)|Mz4($^ehuR6$sVv0_)W|So#p_e`;5`NO`=#rCbPkd(xBv5J zq5Ix2|Hw}18$y=2)&fc}MvKctnV`M!7` zUhNkT`y$gJUg78Mgv7}Mi!huCqMK>K0Da&@s-J1_^9q3qCJ!J?!hA(q9egl?cLDWq zzThu8{TNR_yzi1&=Hp@F>7ufz9!>piu-Hc$vs~39)0fT_+KVA^osZa9)e`9N&K(yj zZvL7GT=Y^vy+pk|CB<)#Bs}e%3D6)c@AB@WD^-_e0MkLg1bf}1X|DrUZny&1kB1ZF zTA!U+9Ke-sodc36$eud}6&dq$l}Vz#xpbVyTfgv16PQ(Bp9Q4r9*J1Cw_c$0D;HSA zcd7dDaVg(&F7L$Zu`U^6>lz_MziqT9z7jgj2tYqp)XnuX1*zG#7y8O zv1f-oPK9eTazsWmr?h_Im)A-L*BDe%ZqX3XRxf%uFZOl_R`N0_TsHtVNftRN78kOw z)&TVYJAkW=%+sz2eN;8vMYeD=o8Lu2=~vY92zH+Rb$?(1P%%)|7l4ky=!fb(>uoX6 zTsU$XHaQpA^sP`guO9F8rO``xAY}S(KsD6dd^5>7k@%=(it)vnL>fM7M^_)cDcR4D z?nrW1_}m*`?~+n7F=>poxg6w$uRC=(Q%}7kdee`^rn&xiXlTuML&Of>9tkV9phfK~Wu3Y2V7`OY{6zH-ZyzVDb z-n_~vyzv*l65-|5%r8*UATJP1xAWqTUK2Ueo~iEkMB47cG2Q(pD%Bk{Ka^ag^VKvVfFs z8cG~NlF}0n|7sVlU=P#1jn~r%vT6c9;2Mb8ofhbYJQ1<5b9G#fk5*sG3IjgrF6+f8 z$$QDv;*?4x0VHmOwsCf2?flO4c#jv3!$2-dcJ9O!SR=k|kJkcyb1ZjQSUw$)Xc&qn z+}wc7F#Z6XQwT7y=CJel>NoDNv$Pn=ge75t#I^DcjLHTOA_n!$hzUN(=Mz?wpq4BQ zTxYqMf^5Ve*}*@>PxqLr_4^D301E}0H<~Q`Ddd>xn8{x24VNirO;m*OTCcI#dP5Y7D*E|lS_c=%h1d}R}Fv{+{ z=zUgx*ZtCehi2Jp4$Rf>^0DK8&PFL+C{HVuO{;Reaj?}(LIi9ST8oBJy3wl79H?TR zn5H{&z31{sKa{8|QkUd9?1ns&-Y5K}p<994@e<_=v9<%ahk2NRX#fqmw>YKDAdxB} z9BDKN;q=`+svp8|rHc{-qa5;uIjrz!)5Bn)hnv)OD*4o=mNnuv#?xs?oVzAG6~6_Q&;!M;!vMJw|V`5HOXkv5+mkfJ+}Mls}aRahbC!Fkk`OpvVFPvZ7K0 z&KnvOCqZVl_ZvPY9;Ko6adfv96yf2AHf|YOJgnUoQ~7wuEQ`@DQs_mSe84=A)y+U+ zEP?D7M7IczCS61LBu`yPxkilY1G!C0?i;V29+qPnc)Ducq5SBF^#}yUl|7X>;+YFg zWOY=3n#eUJ;>z3@H_3&yK6y*$BE3QLmp5@qg2PQG!2FRf1RRk7a_v|0d7 zK(fEjcf>krZA%l|b<^+mms|8mStyqpN^ugP(K|yJK=w@NhQbebYKDCa3FC46nkueo__*LAK$3+g4_4Y1j2L)iX@c*3y|rRYNe115zzj!? zM<$_nuQIvP5P<`OR4FTrpLI;hab(j#kSMM0`{=6MI50PmS#g2Nw6#Q7pTdLiil1WA zHE`Sj{c6}#VFPrs2kZv4`^U!>S@!+22J6U)is>T=phCcVxA)ANO< zE^IgOR3Yf$ZZ2lX_=B`*4j2rlvM!>KMM6uQ1BHS?IE;>g;Mxq036}ifftT~zeqSRR`>CF$5y)V$c# z^X%Y(BlHr1AwWk7%`kN$9mRjme>B@B(`ICc=M%bPXG2;JyWq%PFQXF;N|-v^ZEvvz zd^)p?%)BQEWRI2A_4*SKVhw}w2HR`^HLnE9jdII&!WVoKBxPg}euOoSYkV1#1wmi9 z!lL?l_7UN)vGq~BNA~AFnd)v%^hm+m@MZ$nM=K0HYOcEoI5nosqQHmbr`LBN;G=|9 z3sRf!9kzz-e`BEG3qDR)*--3wQJ2`~dS<}zfHG?ccO>ruim;lk1*|h40~WN9ic%45 zw9T~E@;Uy$Upl(tJM5Kgm80zR(I7q#D@Tk@7!-z>F6~U?xa4m(Qy0*Ao=9L&dClC$ zoG$(6`s5a%m>ko^^Wi3*?TMRw{r64B%zBYblBc*tIiVH2NZ--C&sUI!5yQMVg9$$jrgHB(Dirj$Dt*R< zCbUbH4;){h;$-eA{Qg0$dFgSgV>^}9=SN1cde9x}9a0vK#u)ml^&z3fA49)UqDD6y zVjAuS1}WWP!JTQNF{H7hDh}yTrY7r(2;PM0$!BMkSbiw8o^&VDPqG3(v^ShE9lRfO2={WS5;T-} zR&-?u286_4$IT(1w603k?W`$d1)&*U28yo*~p; zO-hX07xx$e*hSfNgL?nBeinHU>guG(KGvl~{0ab6cZcfVv)crPnLkQDt8~xvNvEm% zJZz@^(O~(fhRKt$4!WePDGK`L&P5aM>oQb{nDXtVIUOWUPDMQu3Cf7Lvzqo;rTwI zv>n?$0oq4h2@rEBYmXrl$yj(|B?X^qmau1d#hgm8Pr99SCIHJ8q4b^J8u0+7!kxm8)Imy8d!HJ&}F-E$%sx4m97Ucel-a1Yd(l zvA{(1+!>w<38`|zsgdiT)3c4oCx&~U2}L)^(+E!RQ`+14gOF$mQMwO`c#X|DdZN#V z#mWoqzFY`VA343DLYy`hG#tXXHuVLx**@qjy1zBKeO8^pxRvi>BAgjD8_L}Rt`>#j zY7f8eC_mpp`)LqAr<_!X8oD|E-~hCLDsaxt z^F_3@ub~NXW@DQsm|F%FCw zo9|6ysl)r#6a*lK^V0!Ux`|)-Rblcr62R^PJ3X~MUvuK2;(vOGyOHu!)o^B1xji!H z2w^6}y@@mDnyF8MG2Ue8x zp!z^>88JZv-@q zfS|ib5u#liryPh;J66r~KANH998ElKN*%3KJKMnb5>)z+|G||wO_$@Uy##^tjiJvmET7I1pavHT z(ge=u!4vw>m;O?kVNRgPDSBRACZe#Ke$(IE%o$7wvJ9i3M|=TWI}1N6 z>;PyYX}!I$KcQh$0ejTeWgw3)YIr{?@Vk72Xjh(tWj2-aF|ou<@widSSMJ5!N$3ax zuECVjGBW$o+}7eLkhe+04oQ-j5ua)kV~{>o1;SP5v+S)&;9_hZns#mbcMQqR zIxjJuS}z=t8{4d>|0^Bn|RT8{%|R|)$Ua~1m@Z?31k zMlCYQx2jN`vD%P;ndr{=1$~ai45%~n9MCjSi6Ck#=ZJAM%=n_0?3Zc!Vw*J+NbE9v5G zh&bI))Px1+uOnZSwopJsigrJcP&6zYscLw_g>NCBg7B!aA znPee>Ng!DKinA@HGAakN-O+%$O9L0;`DQz73A6EW@s2}il5Zk9$M70E??V+*cghxA zEUi!s`5UL`ac!WoDRa1q&-0?@z+TTmCH;Eum%!MqOWN`VQG+dOYLwSA=55e&4 zv1nu9uxO0UYoth`KM!D>w`zo%xK!>YrBt13!%Z+@lVylM>W&8FM`EUlIEbjO=8W8Z zqWn}9;`Uqi4kd!!^8D&^+%{yeRh^t7)d;B~lWSWCr9c8Wv?0ZI*d;6PgRwZryG^!+ zuRke(Y{Q{mljsprn4Y^TotF-dHb}|8No!M)X9M z2$bEW21s+L4&Ny?Jgr=o|LKE5U!U(-V}z1GXj_Z#IL;*L0#C}{BY}cAjEO=7Z*4i; zQ4V{2yGRte4P1Zd8URoGCHz?Fa+o;C0Gg9XY3ivqIdYudF?tMT4d0@zE`cclkRywk zkJg&sS%|g-CjBF`vBhS{V)hbIR_ADEHX544WZS-4 zqTjnTP92JS{Q<7AnxVSWTSgXEW57kgex|SE>Z-F&d~8_nV81pEEp4@CJxgbBqwu^D z9EfK%h!@hPPnx6Mr4ikFQMBbm)GVG4`ZduF7EJgcu9J){bbp{s`~!|pN{$jCvCANy z$c%YkTSt7m7&S(%3wS1!-42a_{R7hWXM2|eguEUMVSu-V>aYb%%7|@E<-&)Qfrbx7 zepqu*x@UKoxq-BnIt6oquirxX6e$rdK`I4B7o72#)EDQq0ihZj631y>B@!(~JNyxP z3Q3Pv%q*gJKIXk>r(o6K8Lb5c>HY?4-s`7dEvn+z=AVdrVt*|7tZdZ@D$N%#Ovv_}vXAmz_mQunHL7Fh0BzdEgOxC4-nz=LA0GntforY(BvGA7 zX-uE$*hBz`9!guFdkE^691Y*sP2HXx05I zj~&l2uOaem%9cw%e8UfLdFjRz{!?|vk|I=lh~Z&iq^r6SmBr%Ye`{KC$xQDu-=Lk%Baw5m1iqo37dX8pdXh(+^R zIiGuyvfb)tnGNyVOr@hlYZ1mJI}SD>8%+vj4(%1^6mm`7UtG>`p@&No=tN~ZWQqk) zVBJT+%_T)x77;FBEh@m2>fyOoglvN6(Qv)#qR|9ew0hXDB#Cz-m^i1<{ za@Phh2k#x{z7r&EBI!2sMCV_aZTzaxLB2n8kvh|E*cBPzF5S13@at>$BjRwz=!tZ^ zXjE9&uW8Ywjj<00Vb#BniVbb0J>UeO`MH*ZyhMo`1X}{iUue zZ|e%^GPj!%7E#-uQb>_JbE3F)r`TZNs*h3LV#PF%cXa6H$pmc&a`DitKVRj-39sqp z%qW;1mz58>8W|!0GDEpCdJvP508P^k%f&~CfzRHEJ&R`Xp zZ#eaN82;p>aI{DBZ?eiFh$IFq{x1AflGk3wJ;2FYbhI_m4K0Ls(j*FrXM2so&(%&*744xln=FdW~qnKIcGCC3```Ib+eu5k3;ODtd93hvbDrt zRZ$q=0Qb}~c9WI|mWw5JKZq-3WxVY3mvDGI4^&@+l`MwaLzZ{0-eHgcq6NMQFV8Lh zT_qhvC6ui6xMN%fP z$qsOxLPe@-3(fsr(Xhdhd;xwqv6vjfBSr$9mN80{Yj+?;o*@h65RKT0d$*z=LKkV&!+j%JS6V_>^tQ^+|VJYr@m*=jNxnI%n)1u#sH8#V<_>TtI0{KgA#B6^@F+6>afbwz|5=(LE=cvL>|UG?mqgf< zZ{+__eO-eNlJ!^&DuXX(#pFQsbq@Fi!LbADvY9l*YH*aw+c=T^$sUj~siHPuLOtJ;g3pW&M_Mk)q?5x+reF^WGVqFbtqE53dJ$ zP#kNBIi$-1`eD;{>o*(W5OFv(n*h&d6B;-YO;gCKlun5IB&K>NAobFC!5k3@uBYc``n82vIh z5TRYviE3n?_0M(PU}yt08>u~1^pCoNf@l}tB5oBk$z#MV*V2HF{|w$BadPe zknUu3W2{aB@t#}k%_eN6ELFmD+e9y8tAuWs*p0KcUDbq+ zaKFgYg9|@H{~2|Ab&a(T_<(oR?bFJKwef9Opeml8JkLMeHl6s*Whd13Bk(cY9Tc0Ujx} z-#BYu+%lMT*ioCQqp0`SGV$U7wt-&vo}ZX4v13OI=C+`z8v*8L`t1&=hU$1beCwD^X7$ zktVV`EE%(>7HeT4rh^1LC#Xp3ZIE^<3Z#LyiLS8kHW6E8jLG27b@|D_O&jtmD0}-5 z0iBD2#J5tR8Yvoq-_k5gL{FXc-w&Ov<{8H6an*URVrc8Xa|=?yLsRgzv~&tVUJp%J zryd(%^})NR94fu;*D4jR+jY~21D~iBQPWgZq_zk`qkCX%-t0t%x>D()B4&FuhlISx z6Nl}YrgOHPS_fa?VD-QK!UG$y9lvNV`h$3Uj7g7dYG@e537(uiC}D*vb;qEK*nO zho-_sG~nhrk^c-I_^YPW1yb7LiIERT35(=5gfQFg^uVz@kvZwpGsjuXSme;F(P%ZZ z)z`uRK3nw6cuT!*`)0derV}D}fhtSq^o?wmJjq9!v=5O|`)2JASbRo#MHDI7P9VxN z5s0ZQ4npyPm6=>=V<5+n3 zL7S+;N|js6+>{BdI(lV5_r1vbnIqDM`IWyemVldoByiv_ajdCKSL&1qqLcQhu)wWdc-^Xe6sWsp;#7kYDSM^M0Heay>xI?xE3O~Bh>}2(=0=@W zP-tV9+b5)JQYK$qL!^5g-?`hoHMqF$b=2sS1_Oxb4vlq6LSbia5@9Y$=1eL(FV;2p zdj#F69LBdy1|7z1PV!;$YW3d~hNn8)eBWt}I7L!%pd2{4-9ZQKRj4wzI|zlVb9cI? z9fV_2-IZl;hF8zo%-3@PEoSe0Y7{z@_t?TVZ<7MQv`H-HFfL`93o*o#RYmCKYKi+d zC}7g?GFcMbGUZj-zsY>8oMDEg+f)w!x38HOm@Z4B>halY9r5W{cbt6TDQiQiI-0Xt zlteS(I%LX}+LlsNGw493c5Wfog$^`(6{9Az?w>gkdna#PWXD_1sU1Dp3%Tj^LhSw~ zmPIfR?BvwIJFhQ0@dT8-AxQ^u`*nZK-|B(aQo7z34k4R7=09Tjyr@*G2i!U3*f$)} z)CI+UO6qOi`>ldvP8j5E?431#C&jgW)k&#awwoW5Ebh7N%UT?2@J@}%wSE^nv|i9F z3DOQFhbU0IU4N=Twmc?E@R3F)y-ioJ<95AxprqmQ&ENku)WTWVvdeZPanpya-Jb9g zD(@_{nxDpuc0CfYSJZRj=3^d`1scFHW0k3&Z&cB-BH%UV+ikroH_mIYT~p$D`dUf* za$GRFeN3mtA(!l_GTkxgFTg|ZLfEJYd34$@?8u1bP*hCYYjL|s8KFctyrBEF76@?w zyw`Qg#T6t;4*$T)N1&!y4pXL7p3rg$#(RTrWzvfusJ2qrWI2V=@-)h}5*KWn7PA?q z0>zkkHI8x*k#E*WEeB(|{;iFS(u3* zGhP0ek^Zqk2&&C^z4czS7@*0>C^J%7fq~`uU~vDWP`2ooxI3$xA0HhxvzfLuTf;ko zj_yt_J)kRw^V(#~b*Ilw>mzOkWcv5D}cs=qB+&nd9 zt2DUKE~xpAylc8mdCHnCpG?9)PPF^C`CP4z2F=&6`{WlJig*w7HiPRA_L+Tv^N{(b|8rwdlnQv4W8r-MG<#N zdP&t_J;?Z%$O34(4L(p%);TK04!JbUzquG2p4Z{QioQyB9ytpp`XH5ZD){NgG@GE3b=LL0LyO z$(kZPoAeI5`{0Cp!S9J?qfp7v&x4=r6~m)fF&Ig`6BA>jx0C?O1hpIc^91ac%cs!L zm<|Ud@2E&CMEBK$CAe@!(ZH|c%SSedU%{|;5^(Q7y7mdT@bAq(6bBD#)2o>7dT7y2 zg{8%C@O!nxB_dd46FlZc2gcb-4e-Vd@6m2_qon8=8g`?~%$)^B9W+ul5tl;~zO`ru*t;7#M8(RTFxiej0kn+B* zL~6W>2KxkhA6kc^k4?oxhQ}$b9hb(t3@w3cUHRxwpWkl+SxKaB)BE2Vc6IccBziJ-t2J|XQDhajze?9uI01X}09826OeV44B5EdZcRgF_kn!r}WxrJ= z)MKY}PjL^I=1egj6XY&m+bYN2;2(qb&N0F2P&{Dp08lVc5tx(Jan!vKswUeb6_Xob zcXacdQDb&b-@DJFMT^nN+hG>{R<+#b7C9Eo_2;te@lR^mRnvCY#kNOuAR0+YE8=H$ zuo)}{dRPJp6mlC7(FfiaGC>Qd<>5r{lhJ$<`0ApE2(Lt%)8t_@9mZ4Zcb`<6Df<&U zJZ0K5APu#9DI7~+-PY0y9LsvFa+4&LEmFmjeYCu zg;&Lf7B}C3Dr&H&zXxrKHZ=ex2Saw1!sy~VT_d>PMZQ%}O=^QmL(Iev$v zslV~+{wxExQlI%3ZR|1uz1-S4srE)kLcqj+AI&UHTzkOC)tK!2#>or4!2h<{Ay!zK z6++BAlo~gw@?qfXhJAD!B|ZFfKkgOak{fgZLvE^7^zUSBjfL#cR>Hj2Kc{e@&}2m; zl00*^tSxerg4MZC?Mi(~P0;Jg(r?Fr$XBjuFt7GflQ$gb7>6c!u-^gKN$3 zek}K05kIpSOpZax&l+Ycv_w$)S~0ZUxdn?3+vy0h-J>}{@URkoj!1w|c6?#S?9uVx zI!ps`rWNKnX@_l}-Qr&~w<0Edt?*-#M zXcZq)XvTzvM{bA`t}}|1<1?}YYo&*LSi?r;h*?I4=bHD71)x2&7^v(#L&}-orzR4% z+T|z+O~yJ7Qc-tQ8o_3Mho{bjrvGRW-O6g(2q3Gt$owxfuP!KA{uB5%c{4o@;&Ga* z_J>ECV&Kl@o9hM|p&hOkA}6Q4VZVD?zMP#XXVV;tym2jJ;%;c*Dc%yxk-w5%y~rhz z)e3xA$vnvdFv7BAcxHt&eH z$Ei0?mLM#-<`G$dpBvFxwuajz$Y=TS_YGv_7zsdG9ncEpxsorQNCcA>-&%Q+t3ue0t3ZlAH7}BMutmdI475Sh?!Ko)5xO7ue5k!;aRQ6*&f7IqZcn-@e&2BY4_b~tUxPVS9P&_fdvpQkI{-ffFaXu#$3_umZ9;Zmql=`U3b!->q7 zxp|Ina&;MOr|0(a0-mP#oj&fV4yS~}>yB8KS zxJJ=&fQoz7n_H{z9Wv4Bju4QMUxvx}$+mAj8jvs*SmL?HYHXT=FpnOMoV@=->6W zDfSj2#aDYtRm~=KxABJn^^74=P>}JLu82`!QXJEcaN|r5qRO@6C}ME+;!jNm?3u$u zq%DGa&)sV!0}3sl{B{}-wopr^)F zLNaI7Df8lK@Cid5*I+GF00dcJbeq2jTh8f`xsA`V_l6uiafQr{IPe-zqyW*nL&lR~ zt73H?jbj4O#SP9|w9ssl2XM!J*hL$2X+$?RmF{5#@o?MqgL^@pl`uX|SKcHhNk`Qg zM#vD2!&gP3KY;r?BW{8&o3HNUzBim#=lupZ9Da>ffY%*&Sn#U)RVeUR|8_dSwj_Mk zV^Z+onUp6%&HvHrW2l9Fx)U&dq)%1#LtkPZpAN0!0QyhF&=!QpnAm&jQcEX%QpKuC zr#;XkPf%*3=58hfTlsSxS(kwv;_*oq;^KPB{w2YE%%OW?^k?j2%}R}y2rr} zup*8v=(D>|h`etU`m37d_@Dh^hXcgCC|)iPj@QBh36}cJ3sBc-x$WJD)HH2$VeR#O zfs<&-l%a(Dpze9vbMuTVl>nH)7@GC*G@{8v&1~xiRJj5A0TYU*Vd2OT`$vJnI34N86okB%xtXYdBTyV{tU{1J^@TicQ`u=cNRj zd+o0JH?gxiPPhZfNFKI@J|UXQyjD!iPUw~u(Pp7-U7MxWH}EIscg;pv$Dz{a{~<^F zw&g8sL6GFgda+M2wno;{p180Ej?#t(k?SEmOgC2gGAf% zuEV53aiGb1EX%g}2p8YMhmo zhf^3Y*mTH|%3e{-3M_c-2CdZtHXNPtZ-b--;a<6#a#8|>~a|myYa79zG_zI z#l__jM2vQlDe=)owYkSATJlmFffHjXwn3Mnq_`KgizinFS8H`#LF zeGaTD;u%GFjypYqqb50{)FDDUx1-;f*zgUoSY1J6CoJg;vkhJGq9*!E6=yFR+Jg)) zG2#9qZe2lUHt}bzJx2P7zcvp8x86`tL`K&iDPLv%woL#Jr@!0uP+%QSr#rtnLwHQ1 z`Pt+Fld4lNbFFa(&f+}aQ=Gw^=Uj?AezU4fjV1rKys{xHmuG$CSIL9fkE~o-)0RC- z>c}j~gIEc(|vr1ycn*GGbYN908al&{xaRxE86K| zju~1ITQDtl<1-Oj=J#_uwI3M=Q=zj!?cN@bUvqq11t7~|Iz@|JlhYuzJ>eZo| zBNqf5bp*pBlkkNxFbkZ=_^BsFXYXX7Jj|>;lQ}ioy#{c&C__g8Zzr#DQ*V!20&rrp z7RG8qb_7OD&Rn4ZG?|;odHhE~{{e5UI@GzgY~y{~0b90h7FE$c{1QaX0+&;`thRwr<(^bV`TB7c_SsI3zrIzQ6Cs5q z0_Q-f7b7QVJ{VOMeq!Ikr+V(O?IqWfyXq+>d_zhVXb|lh@@Xw*24!NlmU&!bd+P-Y zT@9B#9$0Tj&lda@M4?`rVB+cWh?phI+X8P;e@eQXst|97kc21Z12T&1mE3nd|Hib2 zh!7=^hEb{}XV3_zl8i<%AyosOh}CT^ZoYoJe;MxE{?rGV&1$xjc1kV^31sDbkDJj4 zv1$_gAT5~YtAa|&qQTwKV) z!gWBL+njXfl)y_Z~FxlUigcKsO6CfqO;tQ3cprnII9vAFT^&Hi@7(4c+Yg(u#XZ+P{^qEt=_y0k+badl9sSNg(_@7ht>mazwA@mtUMSy|0DSkf#b^}^rMxbqUYCcG5LOu=>{k?^}s@A(vlD9jd^O40C z*0x-=G=sn)o&OT`eLQfS0x~L}gi}cDTj_y7rmYXD1`b*O_7(&;mX!26_8wFV+y@C- z?>s4TECi0U$!INLT?%k(`XYLj^eg>7mssePCfBBjxSt?1f-s&h6kNlbtN!VB z@G8uH-HU6}6L{k?FR?bIJe*an(?5o+UNb{a&}xc-EPW1ab)0=s$8!k>PUL}l{dE=N z5^Pe6UW^DM-&T&b@2l;W+<&bu@PbAQa^vNX;^U#cn1;7iLhycbZc?}>O_$5VdvlQd zWUXu##Nli*`6&b>^o2}pxf!J?s>*fm15Zx~SCqpfsl40wS{7*OVw7r6Ue;<6t#qE4 zMDn3CXhSXgg4K1V80#8-*RVSP+bBf`%6)4(JPleBQ1y534V&`1ido))fB84S%QDq@ zN`DM>el%(Ktn9GjJwOtY1NPEuz7efJBMd};5GfV4+nPeK=pN||TAo*AQJ=Xr%UiGz z0ysLagT%voXWf}@o05>h#(L#><-l^3;uYT^jw5N7EX=t?NIZE4MjcF#DgkyG5cd76 zdtkFgQf%x0pcU#Oor~PtC~5e%L~ZKySInIp3e%zdTC*tc5O)rNq0R6RC3M2=@5{e; zCX5my%9kneTl*LU0icV+b8#yr%q@@-JW814UJ3!GpD$V3^oKlHJl%!V#^}gsI(>jG z$ux_@ObXC;lD#YR4b=_3mM4z$C1bHpT==er?d7?usI|fH{`3ud5!_i0!TYaOmkI^1 zoNXEl4D~%M_wz&aQK2mUY@;#)ns(Zk(j4X%o%F6$aV}qWaUgX5gnml$=!dlSckgDx zupR{%SWF&JXsi)3a|W4#o{=N+-zSIKs?1HZOam2;K5b~y_}>@O=S`KV>fm1GSA|W6 zYd?pe+@XjtwKb>wgB8*p%&>cU;<*8WK|R@@Uc;KKSF1W#yN~!&I-MN z#q|-dLT}%@q?jd@0V+@(Nb3X1UIgc>P=g!15vN}ET49#%pUZvjU{;27vrbr{fVW}l z5K}dz%q}}($sk2M7UdReFOq5g3>zZZ@~v}VpV}+4dbP3PPmI}C0-B*;-FjyI zRVwciP2wCs*k$m7Qh2hjwaALecqoaCs?%8zY`+a&@tnv*#oWOu^LRq}kw_aas2+UL z%kVKDa`Sk~KNe&l6cwQHs_i{keN`9rUNl$4@J53=TPQRjC%uMf(fmP8Gt&LXy6IkkV`p?+aexXz;hpnbNoMI2+&aB>=dE!%w7LJyzvW&h{u5GB6Ah6`DXFl60eN4Aw_%;LI8zkTR zFFk;Qr;{)BD%utv2}{EOf=Jm(xDEM`&uh#3O4Yxl9IFF^3TPVA4I>4Ta&Mo*{*2?z zdb>%ngOzRGM&Z(aCgYLX+&3#2Ew>$(FTWnq1F89?iL2`cYF;bT-aykpKtWT zAs%@(UMn?1Sels&)lJ~N0aHN53?0ox`rh>{A|Htap^rjrKZ(`HK#g} z&`}n26gzWVVvyo93e*bG*Z@P0-jbtfShmQL}A43S6l?_bF_+8*@)(1jXM#yuz` zCA z@2ox0@#Qy+QXmJwj}SH1dc(&A>tPlwY;#JQ)y9M23cO^F^1M|%5oAbZ)kvnEE&MvY z(E#EG3a7$9avC{Lb(ZOj{OB2t-6bKnzD6IP*b6M5mZ?sv;C<@%XtSbv2bpHh>`5)i z6eSpE_5Pq%_>(Q32m4?*Il}4JY*CQT{KxJ#5L!u8C%dpMi;wKSbk7$wq#-7URa%#< z)Q`_9*|w?_HWYJ1bX0nGg|mye4SR}ntW>T{aQ%PoMpySVeA#$1ggSJSJDLapcNi&$ zGPPJ^%<0p%j>WV2q)3KgVL*TG{27HJkCaNaXJbdofoVK3l?cI`D{b8*Fx}gCqEdZ} z5ibJKC`OHy0#96~`;%L20KBK-^*#tz+-KSUCeF!X`G1KT13PH;lA>9^NF<8hpmDjD z6H_4QI_W0hzWo%*f&qF<&Vu(I^4;wnaea%f$K5IW>{ zdA`Q_blNZITXo_`9QGF3#hnDmOdMHg*M3wriZrL3H=W6zxGrdLex8k{OSvoAlz;oG z(=Tb5?cV%Jdf%jPoxY_1N7rSM?V}2$RM|Rk7KW-a!2%!_)V4$YHUxU6sXF!Bs3Wg0 z72=<5CVbNqGPfboqP>P^qCD&~ZZ``4&3jJlFQXlc5b^6nyTI4$N0X|}V4}yBi_>A! z&5GJyTE-8(zmZbbaqiS5wbVQ2pfJkQL$&M`BNCMtK&R+!bdO{~@L|0wr7z)1`q@HQ z{(4?0xe_1b=DUDsX70udZ*cx%ftJ*3(ma@1Dx>mO#K zm^|6^aRE6Shqv7xt!TtUBN@iTr-^zlGf}sVD47nt(@V+UXIhvA#z#)E@O;b z7DB+l>{@rKh-(%mo7}v14palHd^U~Wn&M%DNtd0vQQA}~aj$pWKX|9vC*&Hbp1+bJ zypA5nZzx@yoMb0!zAUu{RG0@~_T8(SH5Rk=x0j8geJ5ThLg+b(Ksi4zyBw-=@SH&z zWoR$|!V>?VVDMD4`oZ@6m5!7Ik$L>LH!ym>{sws0j?hqIyCzrZEs|AKu(C=i&mP&7jQ;NUu%{3p4Y)KvTW|JZQ+=fQ!%}42oHz zvr83;2K#LYz{oyilIvkA*k%HhSA3Um%QAI3lr(86|$170s{ZvpCzWG2F#f(BL zE0@gX>UIsJY}X9aK?ZcNkGyDXGHAl}f=_1anOu}cnMbX}oNWIZ=SI@TAjTOCBr`MD zyi6!7mQ-k+fpCiTxsPEnF(B?vdQ$2P&*6VkpHn9g}s4Qo@!GwTrEvREv>AXi*(dfMVF2A6;)$gd5?K^9KgWel zbI8!d_+f6JFb%d%m}NT7J9e!~bp(^7LBKhsvuBC4M-%&8)|=u=Ruz;i7p@|auzqUe zXn+1=dOdm@p*&b;bBP^dhx2k4i!!A;tqohufA?h>57YG-IrGq9WqWHCP>FA31z507 z9V!~bEn_-1`ry#hw+|7)j=hrcLx*hG(yK3jJO(AK)T<^l-Ofs|U^0D-3;}hm5Qc9K z|G(JikDNSYrm#GfZt)}iR~$#1%nUR82f7-G+n16vJ1V0L0nH1}vI8ebwFjR#VFZ|3 zl-zI+V*@Mm6KU+_xtM_p#{&KGonWewkRP_{V$x|~ognA?FLK@H|L`H#@{S%~zl}FO zB6hSMBkRTs8J0x*-bmFDQME4{HNrTnyKJPjZAS;~L1qfIAuo0u_on8T{*-loG zviXnmLFwPdmS2}fjvy#1a-G=^|3pi~b)qhCy0`<8jtDo16=DpwP+-NRV&PKUCp)AQ z{Z^{86M~O(AD%EAiSlq_VfDlK?pd&}Zr@7wd=%=9#YY|m4ErEtP97lgem8(@dfBvK zEB>Z=A_RQXcQ?&WrvS!@|DI=m`ZCnRX=U#F1aE{^_onx%VZJ~dFYRk$$XlYvEk@G{ z6YWj$oKO@Jm1MbxVA_qR_XIDdw3t;r5Y-za)W89Wa7@4GZ(EYo7Blbr_z;UUWyW z_}ae=EZD%pu36*U=(n5xppFt_)MAMd+PxY;uyvGD;3crKP%2yP-x?CPB;kGoMHSpS z%dmEysh}i_;yS$<^*@oPm0OPWRQ9<(VNyQb?jKt-DaQVtXW!{e_=49ff#(JG*mmt{ z%?23$svUhS9T%PB4g7kAera@z4w$gnN;l%u4N|yml%Nn4&%%m_miIi=>6>Dx;bVAH zrBZ`TjRlMZqBk}`6U?&9gd+>LO8`RY++m9;M9vgia7zNPclpqe=3i9i>18n2Vh(PX zfIwPX0my27-fzKlxoHLCSPeHF-$0dVkv@2%ETVVyP-Od8XMhS}xN8%O!@CES z-ZX;U6_-eM`tq!8n{a^_3cwWJEs*2J#=w_1wg5bYrmH_m@Iu~6|w8vW?Wwu!<%?rJDI>KC?Ve1cg<)jqM9Tz`eJVsTQ&!Q!HO73;{tQ)H3xBK zVbvUo0$yoLZMzhkXFpL+NJI2 zVWLCI`3g3|q|EnqQc9d29mfDT)#01bO;7{*lbXa!OvYpdE2B0ee~X8KF%jb0O7}1U zWDT40TSs#rC+%inO5T@t`{2n{ean9tw0&AK)&9`Aeod~`?5{%uHvksUSW&HkiWD^Ej9=R`;nRXIa&3G*#;5+fOqr9f^2`>h z`iw%8X;eQ?aB2bZ~t>dF5tF)ADQq=5Jc) zZS1I0U*szW;QLL!#0AsY))bTG*}nJgew(E8Ha0REB}nWmd;w+*ne#Dv)La8xN3LHb z&+wF^s7)Qn>~w9atWNY6Ij96C1mIJ3xJqZVRSKUec5l+5$t|&Fjptt)D9{oGav_<- zh=E9A5%M|itR4aBb~y!)aXG7)>w9zbREdF#P2&q(1>(T^Sl3o|ELO!4tsNIv45%b>++|w=W1ah}sI7$Nqm|3=m(Smjk$STJ0Ca&9ro=gA){$D%N557(v*qRZR$z}ot9!`vR zeG-W}&3^*SALnNV%InT zt79Z^%}GeTwjWy9H&Sgw+(kkvTuv@27fI&=tESKyfMn#$)E%p2-(u&+E5Jgp%j|;> z1;AkTwa#)RQ5A1KnEjuGCN-yKTQ$;A;*3~p=V#h>D-f`*Re>$bfQcix@rwQ; z`V{DQ-J1w5Z)9lB9HojIRnqJ&9ERzKgkA6Vc_<>cN@BQtG{oLhw1=%3K7xzcTQ$eb zr$7kOQkig)Y0kB{knZ&6%l%=HtmZt#FHKUf0!cQTkwq#2N-KbFYo03O?LQa~DYtLex-6X-$zfxRX<8{HiLuB5l>|oaEm6VVjQoZdxAmW0V)x z)n)G@c>%XIY_v3dX9rTOJ=IDD%#O_2+u}~W*Oq@n_NIGm0lS(e*gR?JTZczc zBC0edj`HbP7%;L87u78n>^hWM&MJ7c2l*$Y^WfzVrH zo~Pnr^cDIRQl;0LraN!dUpz|U^s2rfn85)MfSEDy>hzgG6wnGOms<6;28Ot>>w_RV7)=UWnPrcQ@Yj7f;FJjECte7vNhULsY*79i1z@z@>*Sd z6j+pWMWU=Cm!%;sxigemUjEw?H?-42FkWBcl~x{_vDt=t&hE|>Grc{11sJrio^AXFFaH>f^%@q4&`h{J;ZrvZyg@0O3{Ku9OCuOfJi~q4SAOIMQM?%z?sR^w1yuUXn;XrRoMDJrz}(>C#m z^b>3@Gl?vJ5|PoR_%sYU_n`2ra)Hf=cu<*Tc%iNXPXg^oanan|?#WcxtA_r#zLvEx?Nq8@5@ZD&j83*-<<4wFcS=pr;d=vh2ArivXAHkw;);yNaHa>m;Q9U(G?W@1X2XDe-iWSp zjg}-F7{#ukM+^K=BwFNFZX-q377fa);o(uikcX# z+95Ex5IWd!{@l&#;HkJnLJ((2xw0!u0l5#7tcFhUbU=+isQ`2t6N*vpmlyll#F> zJYR8Q7+FQ!^e8el74+COs+y>ndJtve-fkraj)4)Fv>6Vl65HB^-ql5xt+Ct0L_q+ zquN4`=;(m_Mx#Dt&q5LN13VHg;+4Y)a-20PId22_+6&Um@(jHuD!k0nL8&aVh=;k5 zaH5gp+$Z1YXVjaefHpj@ZTd+N#b}tnm>=@@$1RYop;`Imkv#`K+J-DI>-$_eLKmms zAGNRqXBabjB|qOz3xmCCFu_8S6#UKKh|r?)6aNxV=Oc3auJFsLDuEqtb?p7$r zF5hYGg(&URwXL6Fm?pWjuYC=*TEnR({BJK;?*=chg`PIvjQC9eQoh+x0_!(uLE73J z<@Qs?fOd2ebs?Hyznhr&+hbiIE!8uJEa(5l$!2C!Xmjsi%C7V*8QUiodG8$YMrW53 zPpQAEhTlHZeDD$#bP{dZhi;oV6;95gp!bI(cqwAX?3IO}@qa?_Oc9<;kjjV(98rcO zu2<+>I}b;{u!H&rC$PLOLJ)HxK=@;W4jrg5vyR&AWnRR_bOo0+q<8pnENUM2b?TFgPdql z41k@_29z*!&@PvBHFWGJ9e3Pr^AwJrQoD6;64>a;>KffO&fhtHqwy+%UQHyR?1&yO zuB6a9KRK;Osl^5MDh%|S5ICCHWH>+PEjisNZhZrEhG&iNM}oDnnx0*Bu-}YwR(U?k zB`Yc})_o>pX41Fm*HQOfKW_h=J;q5+(%~+t8%#ytF=sU;^4y}{Yzu_ut+OI&{(YuN zvFjLx(I%w~4{KHfJBq%VCdbg2YN%_EZqjAzU0xxc7*))C2N&Cdn{gPTxD%ugL$IDs zNTRM-{>`TcBlYN*LNMK1yUo)ts%)CGC*T&Yh1Nt4p#|kRa?3iUl_m?0tKT3f5D)J+ zF59`u3QgN%7H<>>2B=-QtyB}%6S|7gG~!Z>qaGRV1j&4+l-q3Dxl->BwuLAr$`CvO z(RLWEb3Gb0(136;!i5Jkf>w(9!fu^av_n zAbIn*lx?{AY1%j)RG1#RI&LXcF}K}Lc}G*a-?{yCe~b85fC@3{^9VNmi@}y~7i^dd zi6(sbr{{C+m3lMJDjIkAQPK>vpX08S)fO0@!pKVyK&iancJ{$#cAD)51C=Nay4S34 zS1|YUT=)KLd=p2gPL>Dp_rq*;1h2(Hfc)fYbGp5(WPU3hWyQM)?mb!K4Av{J90%mR z8b^nl$-L|@vmtc_L3Y#0o~osNBV8cI)Wi)Ld@4>cR+FT40Eb|m`>_12FQD%{1Quoe z!MndISJ%U;|DIUz*D)(|TM!(qO4nM~@U@q3b; zqV=lE9Mz}5T8HXFoQxHcjQSf_`Zr96gP19t6+N|QEh`{`nPE^}*@y9Vz3(}g!VEK_ zCS?5-bpQu?@rf!NGmlWhg>QCZik55cew?8{epGI z=COX>hj@&haXv8)=eu5{`fJ){B7jvwzsPY}ODPC0q_iXwXgZ!KW7&~gcc zrz1mj`};oWa@sNyL`8}(SxKeV^EK0{1lngd*ACehhqjS3FMp1-vRvgAR~XXh;~ z7*lu0orcc{*Q{>P4&7~b7*cUlF>AL_n;zSB-Iz6eK09XtF D?*{dP diff --git a/x/wasm/keeper/testdata/burner.wasm b/x/wasm/keeper/testdata/burner.wasm deleted file mode 100644 index 6639075611b73e21f3d0045d772ce84b406718e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127528 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 OE3W{~0~P?eROl~tF*(Hm diff --git a/x/wasm/keeper/testdata/contracts.go b/x/wasm/keeper/testdata/contracts.go deleted file mode 100644 index 6073e4d..0000000 --- a/x/wasm/keeper/testdata/contracts.go +++ /dev/null @@ -1,94 +0,0 @@ -package testdata - -import ( - _ "embed" - - typwasmvmtypes "github.com/CosmWasm/wasmvm/types" - "github.com/cosmos/cosmos-sdk/types" -) - -var ( - //go:embed reflect.wasm - reflectContract []byte - //go:embed reflect_1_1.wasm - migrateReflectContract []byte - //go:embed cyberpunk.wasm - cyberpunkContract []byte - //go:embed ibc_reflect.wasm - ibcReflectContract []byte - //go:embed burner.wasm - burnerContract []byte - //go:embed hackatom.wasm - hackatomContract []byte -) - -func ReflectContractWasm() []byte { - return reflectContract -} - -func MigrateReflectContractWasm() []byte { - return migrateReflectContract -} - -func CyberpunkContractWasm() []byte { - return cyberpunkContract -} - -func IBCReflectContractWasm() []byte { - return ibcReflectContract -} - -func BurnerContractWasm() []byte { - return burnerContract -} - -func HackatomContractWasm() []byte { - return hackatomContract -} - -// ReflectHandleMsg is used to encode handle messages -type ReflectHandleMsg struct { - Reflect *ReflectPayload `json:"reflect_msg,omitempty"` - ReflectSubMsg *ReflectSubPayload `json:"reflect_sub_msg,omitempty"` - ChangeOwner *OwnerPayload `json:"change_owner,omitempty"` -} - -type OwnerPayload struct { - Owner types.Address `json:"owner"` -} - -type ReflectPayload struct { - Msgs []typwasmvmtypes.CosmosMsg `json:"msgs"` -} - -type ReflectSubPayload struct { - Msgs []typwasmvmtypes.SubMsg `json:"msgs"` -} - -// ReflectQueryMsg is used to encode query messages -type ReflectQueryMsg struct { - Owner *struct{} `json:"owner,omitempty"` - Capitalized *Text `json:"capitalized,omitempty"` - Chain *ChainQuery `json:"chain,omitempty"` - SubMsgResult *SubCall `json:"sub_msg_result,omitempty"` -} - -type ChainQuery struct { - Request *typwasmvmtypes.QueryRequest `json:"request,omitempty"` -} - -type Text struct { - Text string `json:"text"` -} - -type SubCall struct { - ID uint64 `json:"id"` -} - -type OwnerResponse struct { - Owner string `json:"owner,omitempty"` -} - -type ChainResponse struct { - Data []byte `json:"data,omitempty"` -} diff --git a/x/wasm/keeper/testdata/cyberpunk.wasm b/x/wasm/keeper/testdata/cyberpunk.wasm deleted file mode 100644 index 355804ad13f49c83f56c0cf52c18dedfc962476e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 170223 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

Ec=)OE9QK&hLVY7q<3DG{S@*9P@=U*{gaLUn7$! zuBb*~%q44@4}4A8_Vs3HZosM)&T8pXaZ&L_pIi~V0>Ou5%7Rom5@e0X-~u3jx{ z2UVw<*nF;%7=3|9eVz6vPm4XtAS@8>m8?2g5z`Be~a?~RWSB^uT?*u$T#pQMIH zxn!-P#f4Ja-EoShf#VuLu+b>fZR6TfV{JaJVbvtt$B%I6H%RE@9mv&dX^M`}799e? zC2Z02Fi2Tnoc&dhVmn~0DAsPxSKTqz5H!pavmBU~z^}b@>$UvW*mdc<8RH`JXi`7D zu5>CuCvNR_L>%SFDmfZuj;xZSR>?6`=BQP2)GIlL%N+Gejz%R%v&_+`x@tHo%CR2ImVQnmhFhB|sA-|RI{rc>6?_el*<2&wUfbdFw zhi0ew9iHv++nim`@5t;ben+)G6}Y=W*{9!Ohi=mqzmeQ?F9}m2;pBJR!&B3rPH(v9 zUcGGYRqitrdNrl{v%~h$H242pQ|b+S@4fflEl8B6cN4>W)7sOn(hQZD+Pjg;JEI$G z0!PEU(aJlc81Gu)U90lWXvI4@phW3*<(*N9cVpq*SmmA3hj-)Q-FW4lQHFOD;oU^# zU7!h}c18?{v$aBx)di?3#D;h4EAN~VS#d>pcSYr$Q-b{w!@Dah z@0=1@u_3(MPSEcq8qNtEn$+I0sK}Xo1)jMsj&}AdY_WA=D_?uZ*eGfO zudh^4w>bQ3Q3XwmH8knCxT9mVl_T{hKfzb8EY5IoZSIQ8wc1H9^0`v05l%1l9Mn@yN%d=_O&)|%B!0LuP5@o<~!pO}@yfDS!gG`ICSs99RkxpN2hNL7R5 zB|>GhNlf0-cH-k`W>m=v1QdFDg<@e+)FNxJn3o>{MI^D;VO8a}q;e-02@F_tGN@b^ zBvIvJd2D5@XexIys9etxRW4>qC5NkA&k5r%Jm#k<+ch` zZdrz_T+b0zZVN2*bGXX&98u*m+9n4FqlpmC0#dn{`%<~zVnRjss@SS62G=1S<6Sru zE+2<4#zv~#egDTJcf$|^rq^SU?~6IN#ZJTonmp;dk-;7V-#k}l$+~U&(|C^Hr^ghj z{@t;zn_(J`Z|ino%ra!F%B1JJhk>nC7T{v+J1H*K5IE_sfhY}(jd2q>y;T}2GQ7qyUON=a-&w`u zd?ih=SW)?$p1|&N%PU>MKXhrW)^i+JX0N@k`)e22HC}U^8rzuCk-QW5YnRy#Ue{kw zpVVvb0sh*>Ho>q3yZsYF%4=tTf9;Z+Sn6Wv^&AMS*UtO?+J(1>GyHnIaeD2H@2_2c zV`n2&kc;e@4!)@V(pmkSRwQGaZ%-JYT(mLyI&zC}sGD&~JA~Qm8LdYKeX7sPNFIhl z<9Cj6!i010gO3JAFEaiP>&d7$E}>^xM`PB<%B;1bRy?dvLfqTP#M1G+wGIi9v1#y1 zWYY@0WV4-H@y1SeSISiYfjwW5{97Hqra|IB;gcoZpNn*lNIAPO;r^q6_?RQ3w{RH5 z7!ALchD24R+9pCRE8UQf8duvfI<60wrPL`0h*uZ?_YW7Z+x{?vCz?x3IMtXI^;?4( zA-MxGC8IJSm4BsN8kBbV>hY1ugt2vE5sWw}7&Uva0bv*JHBzh(Zl2=EBOXz~`5}s^ zU={#-BS!dmLMZBL6e+T6tTo&5TOKFyZOu;gAB$88c{Kr0Q5#C0UYyJ; z-n3>nFa3(w9qC69>j8e{4f58t21ZkHf9x|6%O^^Xj%Z&<%?BZeIR&DL_FyDTk+e|K zLM!t<^>^4Y?mEdd%~u(7(q@Bbb>LTp0surX1+9bRke~gV8SUOXn~90>a0E;6knm~F z2$P1BxrVqmnM}gap_j8Uoa-#{!}oE>#)=Tf=kDh8e_xL`!93Bh%V!CuEa=#UoTTG0nRq zWT`>&*$uv}9h7sM9sB9rwb{A5?$L2z%o9WU-&Ij4jePIxvTL~0*yf(Re(&p;b2iu5 z4QzYG9Ohh8vDSl1625ItDw!H@&;+w2)mos<5 zyIiVHsX|YNLi=CP_RE3p!OMV7m1;6Y2fu{b`DIJZ&O^A{-`joI44ZLh)b{$h(Q)J7*stP0mtL}Gv-_UpR zrONT?)p=A8lXfJC&=v+D-(l$K{SL<3sp6=*n^Yvdc{!84;#(ZKHtQ-GC}_D*!E&D^ z4$v=`O#DtfzRTrlT@HnV^kg%{E!lUGa?0WtZ&McBsMlw2;<{NPVY%&oo^`%#RO4W? z&httnih<12*>ym`WwSB0K&jp4;@E|=gTkB=XcHQigGQa8y&NyJJnw(^ui|;Xr{a0P zXQAg^J8KDvM8w%dgxv3yG@FuuiOaNsN$Wx9yOkneJ*3TG3L1PWO(ct}f4myrHa?a$ z_%CfVYDuF}w|M-ID%QQz`E^N+wD@Fv!XQWrj^s>zW(06k59j{_>fN@zk2176+6*LAt`K)Gv!KDrLEH3Ih%0wB{o+MgB8-J=A#{?}Q|q7-l=I zlxHsGbJ9ApASeQ^iF5FIDY)Q7ms&6CG$^}}e8 zT}^MgFkpQ;v+irFN6K*6!-gbs#=~gN{(3M$1uQ7mIW2TK3;9ED#}7Ri%22D1S!{5x zqA96~*p~ibG+1GN7!8n_gjehAsvLVK5g(WjnRQ z8>_~M*RZM0h;>4-*piQr6>kLInHm%MXr!ao=Pv_lZ$_fAl8$4-fFgCnrAKpi70jA! zr<$o4Zo&{u>(%Kat1brSzCSIGb&?%9lUuZ{nym0>$CJW+L6aZwDnl?F)`$^|J)eDiemn-lRHj+jdQa8`zU24ZM!9gv`*h-4Se z4Y0=2hI%Ijd{Nc_6w)LV#C+C&>WV$0QLseAEEHotm_=!6XrNUiT#<5~mM#tT$I>#= zl_9Fgm;-NsoO7^j38O#<5Yfq^A?djlIw~$f128V4qe&jJbtJz4vtjT@Vw`tL#<6)P zhKZN6JQy*`_LVo+MF5Q3B8Pw^K$HwLNKI5BtUL4!Kxq9$JArhK{k-j5>(_wjUb?uUIbg`k;P@@m=US)w&J13=aY?z9kY^QLlzHKpW(L$ zs?YcL9~2xhdw^NlISh50AcNE_d_#=xG<1{TeXY(aL*GLkf-{JV%sjUd!L*a#1(bL< z5ci1H8lm?<)r|;!cFYv15Ncwz*ddE=592D0q0(~Uf_0#4NO%IU<3LLzuNz3iM$j3= zGQUfR9WFNR@5rQ9Xgql=1jWsN!y3|3y+uM(Br`r2TWh%p{+meYLije07Y+jJv)Evu zlLLsx34+mCpTQtN6=jG0L_K!cr5Iu{JD_NmV25H}qRRC}U>yV!^q5s33_aaDgoJ*B zQw~9E=-@=03G3t(){R#5aZsZVg4RJ2&qT&? zC#uhI+|$+Pr}_`ZaSy?8^XKF^$U)GciCYwwgRZbjIK^bs#WKwOpZZr|7$eevJ~515 zF6(@_HYiI#z8QE5J_ASHESu3^6eZ_S0L5oZ)?rnINmOacJe&P&g>^D%kE}CkCQLrvp7oqhPb$)FH9S`tMQD^wWG;@9hG56}m_F?HfbR1Xhux^Kh>|-oh;`uQ2}& z+n6PU_0j(sx~l+2NpT2JjQ5t@3qNhqECNzrap^=0uRwJ1fS*`sC$G!DbD1QQEOcG0T(;H)(C5TIs|%2z zrkKQSy2O>~;^X1-not7>zW2ZO5iAqdkqPQ>(fd6uWeAa)m(EZ)Tt%UwXd!!_hq^DG zrFu{8Tct~ke6zd6;V0apv@Y=|4;NwHAA_*sSo`L9g~~5#zd0L-a4fOWg4G6#*E;W_ zmrZAclk3v#Hlsu`k!99|M3QlPmN#K_K9$23>&-vS8ME@~RF_bFQz}7NnVF6e16Lxf z&rzQ6fp->*ej#Gp(VwfOUH{9 zb5G&y9Wdu^b93${V0NO)&}1Fw(-oqg?<1#b_~?o9BNL7i7_rkrWRvs3Sv4pPecUyj zT_d?hXGXnRm!8hnlOpq(0bEFFlj5f(r&`n{1GXbhlR3X2qcUL-7t|DlC)Q5K* zsK;zNkw;7hzF+RXqqrdCImLGZJ8VjhVxzN6j8&GKl26oF2lBN}NP|6j#(7Juhu2|?%RFO^OtOoVO;!;%k2JcfUB%Q`eITdMkw$OS z))DC8(FXm&KqA-ChE0iZg3bQARj>DCZtfDydMztXsWx^1)WJ97rgQ z?dlRZtOiL7=?K^HM!PjS(&WjK@j}v0fDTPl_R!=O0${b2D@tid1xb(D;r(A)hg*=E? zCE7xsM!P3W>5;&5t&{L0StD3)Fb>YugzpBjRDMC_!7e+c!MBw@K^|lt0G`#tF(^8WxWdL~>uG*pgEW$c($6W=({bF8o3z};ol_h1cl9man zVOQ=fuKZECMfFhI4l^<9!h}Mb)tLDNp2SFySjPWD3TN7>`v7`z)(?JGn!*bZ7~DU+%>j@!pO8UdSO0zl(FCP3o^fCgNK z5k8wU5}rndmQB{nK5_=>>QYe921HyWwCy$LE>!}6b4d{v9qH_P@dUS%eml=C)5S_f z!f{-RW_ox)_WANY4%)sRAwm**_=v6|N3HY0(5{G#lE_QrRI;kG%cb~(R0O{T+|`SR zsLKv6_1Tnqao7@U5t2i*e*l^X0yO({)e1jJCD#L5-Y0sqy3aZven{`fPXe?oIaP`; zjgD;z{W^Ryr#6p-+L%4i=uHLrH--FDKj+_&38P}4T4M6uG?qW6AqUGi%DxaNc%i-^ zY3uAO&D3s7o*Gkdw4HJHWOzMM<*Ue!r%QHR#?gO{L2)7xT7&H9T#O!xENm<5>cuJV zkRsd3vYSq4C+G_8mOmN;8zUp^?CBi^=8cDzBl6^ zIHB)-qx|!K=;NL8!_qfzhKo6F7Ha3b{;K3x4aSeNWPaz41-g7wf(x{j@`u->6s&|1>`5&)Z$57#P6k)*(j2kbjniU(>$)tXj(NZW0U;7W>INF8dw-G5IijFppVLaS5QmETt?2w=oTbHI|IsHtICLu=4n>|4z1S@3S`0Ywjt1O}G2lji z8@FxB*RB!xjT-5AU3$s@vAzl*w$TWHco;+c2x=7XdkQUE??@^jarvbKMo}o)bAp%~ zj5dPPy`J%^<{5U)!-9bDiF&z$dc)K+oWr>6q%)xC53;PwpG0S|y`>8ETAwxBSAGvk zyxK{ex713EfoD*WB~)G+uhFh%bynKJ&c0*A{v92e;L@cP3sayj$|daU#ZzWYTkR(8 z>&4S%EqER&2$tE$`U?iBlJ%U1iUmHk1@Y`)(4kxnVd9^|__Z!!BHDq+5*g4v_X5s< z?z!I*DtdwXo|lLLh|{|6wchJy@Lut&>zjAkMhM)8PQFmT6bm&_U*RlLHQI}n02Nat zs2b@OC9%*sCP~Ij>G2Hlg(e>o*@l~vuVgN&&SXHwdR48HKvx;zr0dO7;3T$@xNCYF z%h#pwAEnQxHxv7~LyKKCbEjIuNJ!!Z?tuz%Y#~E2j3WjH3=X|wA4}DIS`fuCgUiSN zku517pSABKA2%h>S$m3;B+`*>JQJIeFJSFLM;7x}L?BjS94BOb(FeQ| zcLSq<;8P5v0K}4sEk+=i@sd)_AbfyA^{d9?Ub~6z&ro}`|HFV*_J3-kZpiF^VrQ^b zD&Ifp-`m*u(^(XCwjwFiCOfMcMBTYfe~o(^H|N6x%lfUen}IgR*g;NEJG0)j=nFC5 zVnOPS#W5pOw7ia~J>C`mFp~}mU7<*jfo6b~hLUN=iW#*+96ChCrppm;_+kTx?Ph~W z7?IQqlP!o0FHowX>C!IP=;DoHJ|La)&3<`YtO5b&0|EG0=sBMm#JI_oI$F47c<+u! z2as^o$_(RT=kZT0&ErSxJLmC7QO!Vbh$MLYNY-n)!pEj{tM-tCYMlOIb%zfC7OC{BDS6{~du%z!9{?snBZM7rC`>tj04Snz56sIt zR7fJ_J_5(5E^hZ#zTfBHo0xh}$?=vk)FukHF09;5*P3$AtR9eL(Gx?(uqpTAP#nk> z$*mTHf(XS)S`3?hU+{WB#bfp4Yu;d^PMN8w>rmEiwP(#id*hgnZ!VM2(WBhZ>j2zQ%vpg4#R8kRS;#%>w^H^ofN|!W@FptX zp7n+(b@T0HeC-`O#_EJkg(6N9QO}yM+0ogg^YM)C3a4zWok`z-h?_;@DA<97(`j!C zjt;fxbi1iHRx%XNQcO!57g6)Ggl6D$wEq5=X{cGV^2LqpR@{;cqwM*rO`{%j%t=+c zQnZng`K)#^eZJ_W; zQCOrjGsldT3z|7tqB3(L-YbqzmFYg>rOg`rUkF;Q{3xxE+=SN5Bnaceqo<* z2at2o!@eVx;*NN6rdb{d2Glog*!P7e-}6($=NR_IcLO@%#ToW}R66PZ1H-=1j(>?^ z9}4haf7k~QCLE7i?{!P)sQA@|<0oX~M_U*Ee<8vVn@aAMC<)W9ygmc+QdX$kgTG6+Ni$7tIy1 z^A(ZEtoH2bFdP$ zy$0GC(^Z?2cPT{Fq@=8+^UoM9vk{{vzO?UPS=?Je#m!>~_)W?G)gLdC?eeCKCM#1$ z^=xdcKDKDisGel!nKVj^yKv$s$?V_%&hOpxPrvufC;mM7?sLx^wZZ0jXO50aKsL=U zHh0l8N6UTY2tRdo<_L4*(r1qNXhCbfXAIlDKYbJcyvPX|rjN$mi6blgMVLOq?C~i- z@kJj)3#N~vkLXgTkIX1LXQ{HhU-$$PcGnB`K$rz4|KJOrKmr8Y4y%|kF~MG z+ZRaoh5oxmqQ39YJI4%?3?&;4mYxvCr9UVweE8wKY0LeI*2o?rg~0KW#4}Xss=JW0Lbt^M8;rEH(`e8} zdntUB0QR!BIZNozALgSP$JNf@zf(ZegUECOOH2zF`|4X&R4kXtc=I12OK#35-LdKm zsoOW_BRnT2#NvnpZ=JkN7nvK!O>8aW7;|j`cys5DR)}K(CMm=H^{p)%usY*RJI@33 zXw-SqE7MXjE{@yjPYQs$GG-^Wu@B51@QE|UUb|zV8m$96ED^_|mFMPm zhPb7#dB}W97Nc|S*cI<%rj@lRWt~V>YG&XFPSu3mw5a(ax_vDQx(6Ut;DyHc3RPfO zBCoe@k--&TxQ`UgXGfG0O$(b>4-17@Z$dS5$G5Q+3=TWu_K6vyA|w|RN?PGHj0=T4 zLtX34MDO{q(vifv>cFULWMZ-oT-{@sgO?cYTNUc}+{;OC{z;4}TSOTfBgzlc-^IepAjg#G$@dd|z6aoKWh;<>mc<~f-QSy=5f_*lu zu@F40gT7EKo>!Ivc~*+D6aLY@y2K+sWy!B&l4HztJ*^(2&T6rCoe!jn|FTI|rr3Np zUS2UBRPC3{yol6IgI-}1LZi{>cnvR)Bq2A!T_&8V76ls~tFf7G>={}L_{2xC0r%R@ z?&eGYt#n#~2x_LP0Svtlc5%&yvsrjLM%iiSeLgdL#^(2scm3%^VQ3%Q7o1iX1&7qi zW@t2=8Dfix<>qMxa0<{v5YA#9eY#TL5@9j5rFKAigl5d{)$X>F9z=4Is(D9Q#j}FK zG?M}+4Uf1Bg;zyITy!(`s~L%e78|%G+^yf5+y3P`j3@7(zu;}M94w)#wl@iT%fR%0 zXBg_d%qgPw8L+TJ4?snKylb@>2uU;5e0=!~LoF4)WI05bIkaeb+xDADaYYalP3CaY z&CMTba9Y9E3V15C!jQA_W)Q!^aJ`&gRnoASSZ4%_gAdzX_yBJ3e3svDddNDvuCqt} zpni&a%&)nY!7Avn?Mp3&nUt~Q`yr6o}jaAHq z{&S!#^YM}eF4%$4&`T6`e_7P0F1{!Pw-ptb_0$oeavBy5@IC+Aj@z>LD!-j#H^Z8B zV`&$8k{MkPY0D)RE-FT5G!b`PB=uavwLXwi0+IMEdXhkz$x{@e=?`1uSuVup&@_Vz zAtR`?<3U`At!eB^Je-}aHPDjEH*xWgeN%?Q0gjd~2sjot*{WU1*31@ZwLZ~snaK_g zmn}2FQ9M%i!0*ko@k;UY$jpKm9Z88JBypCh$;3*|uPe~0Gmvv%$SIju$@yjF#N1Jt z+M0nf!}+M18LH;|3*{VJGN)t~WlB6r)MzQIpL+Fc`evf|Ok77A#rFvqEpXe{v{zIAZX;P z_bcbJfifTRoaBReY&fJ%R_K!a-_s8t+w?fr9*02wP3>ZBfox{*&kB`ou%c=kjEzrp zCYLQ=VK&0={F%v+q9+$`Ry-0O*C--3D}FjWu2(>CR($nOJ@Y!9{E-#UgvSkX%4Ef_ zhR2us=^8&49;f}>ia-4m|8|QXbMn#f_%b&z{^@CddxIuTv*MHGqpAA$gvVEET$~kO z`D4#KWn5hRRd}4x7%?k8A0Ev&lokIqJg!r4P*xl&A5E2f=8wFnKd>DJvf_Q=`6eTG zamo&Foi1K0o6>0tc_edXDRSHD%4%R0qZ?YqXLxzFa@ygaQ39T@V#Mhg3(&N&fbQbH zZR)64TBMk$=2SoIxf>G+UzOTgtpo~`P(~V;>8~`I&YC%V6}H=@oq=b0nrt3$^7y{t2Df>W)y61tv5$J933;J(Ml3I=b7yh?IQpj??wzR~%j@ZzYnhq&hNEdKH zF1nI2L+kG*)0WH{##x+h1^i*Gi6L?ut zTp&6wKHcgfgdH}A2zMfwh;;T?$}NUK9xD>x60)`;9}g>9ho&fI#?OS)u#+$)LoR+- zc`d29uC34sZ4-OjkzB;=5Gcr=OWGrg1>jRQx|Ta=W47LhrgU0{=q3i?b}MF>{a?K7 z_-myhBve#y)O}PmM)Ge|9=>5N^6(AYG9N0X1vQsWHF<}$)YjsOEV1M7w3&6rzb5~L zAH_nAAZ9pfDhIcuXxFv~LcF4my}9ya(N5tPPNnx-IS7B~rT8cd)fxqJh0gF9Djw%V zxR)mOU;CW@h4*rW*iKsbN^lquBh5Z-RG|?&;)Q5ZV)%v76$DW2jl* z5W7)qf#70FIqiu;gVC~C!=yZlVQgE4rb<%x$0)PnI=kp96xS?S zon&Kl=RXxUG+5k_6=#u)Ln=-V_*mT3E@x(&Jc!U$*jdF5urvN(D{e?K&Dtp`CX*4D z^G-Tyn{C$|xcbf=J%pU#+R=46iJ$f~GvaecYQ7L!r%}<7=8k-F&s&PQeUI(AU8h}I zAAu2RnH?F$K6d)(XpN(Mi>A|v-lxQGe4H*TAwVDPqTZMXgqH2_#gUrfU6tTEzIAN0 zm_RI0A1mlA3zHP1J9n^UoLr~^3n=EEpz|6m%E=Q)J!-3y{<*v?7w3r+gyZSH>=}y#eu%0U?n~A=HPsW?A>>>fNgW8nRK@Fjx zG^Z+|Zz>{FSI%2P2{lSF$wKc@TvUqEzaxNQ%RR&vV4$!$2z}AZ1R>wZZ7X{M4?fm5 zmVWSpV5r+#QMk0&_nG;m=oV+y{cUe;jl*L;zHr~MzObPII>ps5EszG41K8)B5G=N< zIR5F(Pp4R%!mysN(ImFFil07-T~?Ee7qyP%Q}`HTIUKskG59u&uUu&qG; z4~%qrn2iAz8R*(zlI>~CAeVtD1Nij?py$y9UMGt{n|8uE;C0XfH_Jt8B?WivY@4)R zZDWps+G@A!d^eB`kud?7^$p40>@HE^teEt@<6KJlH3(O^k7 z(lwYcEM@(70BAF5aGy{@@3q~$krXJ9MzMhtRJr^|j4c>-XeS#~TFxxy%*csQ8e;xS z`QI#ilhY$5RV`!9GB%54JL&VM6ZW~>l+?;^JP*^}#HE_R&1R~96xiH0zK)H9h8sf* zM^<`<6Ul>ZWzCT6!{iAXky#T*e?20ME@{{%jN^o`4f@im;Zdfd`Z5GLguBw|F`|}v0h&M^8cb=zBJ2=4*%cx z%a00@-q|Q}H(2I46-S*}`Vlea`jKtQxQ#k{+@4TirXSn(R8i?;=*Mpg=KsMUzZ6<+ zzSO?8=z~Tvp~cRlx4RX4K3)Ywrx~HvcNLp=Tw=Y5sm-$ewy=(5oxZj1abYCpY+N2P z!;4iKjYX==rvSqVM3Se~3={T*on>~%A|jSFjzk}mA;Jmtfg{o@sg#6(QkNhmgDx>g?+a0GpA!$Y?$Jbyfz1+R zDWjhHSucp`Fw(_$A&#T-VL z#rj|p*u6#0B(rmDGFLh&&!$j(|5 zNypBPvgnDg*5OAnKSVZzmBMbcvj1x8_#X9Q3#n&E=X%3s8Q2uFr;<13!`+PG1tv>r zwVi$@SZ&3Mytb`{J5g4y*#}(B_ugv+K7nn-C!VTs6=M}5M}~M|+gAr`M7ADo0BEmP z+Cc(uFj`d#c4B2=K#>jHAaN7EwVB9z( zK({4Kr7(}T&|=aIgCCd>>v+c4>y1#h%3Uw8(63pcAI#$Rp-dk(qDcmeI%bxzFhJm7 zC$fSNQyL`5RHXSCqMvb-9AdPg$*9x1gLVvEqC`@0?-VyK6mQDDs23m*K&@N%@f4&^K(6M_7@^LtCZS9S0#HyX(WZhn8_Qt&jWuyydK#O4H z_~MBL=k1d1y1fU{u>G7kmpo_n2(^iUgLwJsl?%Qiu86NVLTIosd~!$q4HEpG9+idYJD6MC@-b@WXksC`ZZO-7Ig zPi$U-gE)KDQF%!ST5ex0LJ*GKr4Y1A$uEe5R;z?XXjp0GF2z9vwh2MY&WRvSX9h|u<%90@}(93XE>{vyhV6*5I zkv0tbx!0$(aA&c8*N%Z~CNn8vXFjIaR}Q>ZtnCQi%qEkRXd0Yg0VhQe=BN7&7Md3R z!K6BNEXmMPY2MV4o-~%ixESb7B)@~!Z?J#z{++OH}%1y zSSW7FPoeY?Nk>jbdg3uEj)=N;k|iJ)^W~GGnvV@U9ng1{Q{SB|-|4&4}3mnjJG)JI84O-MqF#!}gJcJ|L<{PnJWuSh$6mbSA z<>>))WOOqaHz=R5B#LeAFiaW#;vJO2|Eioj3VeMo!csi{Il9Qpiv!}Az4%9eTL+B@ zjvvu%g`XW$zm@S)9cJ{|d~Rz$3@iZXoE4gZ;hzBmrV-3l6LVoK1CjBAlF=f573Lv|8ZY1!fX#ph#LE1{%(UPZ zhE2%LOrwfxxGXAnpO;%LRWlnvN%j~~yFX;gh}lr1nbAH+6fzw}Z9_jGiX<{>8jdrW zP2`2mVpJn6Ll|qn^v{RV)d;a$sHbeP`D-y-3T5QA5C$aN>DWcZ{vIhIt(K)Q7?mc_ zg#Lx)s>K%4GnUMree`qxaQ{#Kmp}Q{Jp^YPI^oXWNLx=d!0LT)83d_O816B`j87lY(?ooF zaNz0PdRi9WedV94-~ElA+VR~fJ&nYtr}b3U_h~(~;=51kX*53lik`;e)1iTPKR591 zC-hX-_s?JFscge@dMe@kyq?BmnNRA8{hb{XkLjr_1lba@2g+qscggk zfv3OGQ;FR#4!rxEo*J=~V=-rIjk9!5J&Zojg{3C#NgNiFf!UP=#~;jEH^cDUeZY1y zN;|)xorv^ZFL^jmial>RJe%(3Ci!>0WSsluVk$s^q5EwN2Z@W`A;s^ zO**L$V{7YR9$UPqXE_e&r|~(gVWra+C;nkx88o5ccL>olZ+|@L+>d@}aYXS)9v6H! zGntuc?o3V53O8CtfqKKO%_z1wd_zHYq;<1WNYlG*%B}GETbkzKx^tXqPtyUbbX3|? zoPU+)6aO6!ie}GULX<1dhTI?HmM%TW6B5zn5AS8pF}W*an1VG!Z1!!xj)_=DtaRKG zD%03wmQb0-#(0&8tU_9T6#VFVH|R$PTX|||6;v6R>cJC9uV!vFfCgo@cO<5cHPS$* z7XW{7zoO~o1*2xD$5|#x>-z_(ZkRUBaS7yx&X1v64W!Q9$ZU7jt%!{3GzAEF^_?~7 z8KpB#wEdviF|zY#X`T{$B`hbM-|`7jLcsL`8wI@Y#um_1vBEA}!^^GPivP5|umA-b zdQJPp$CSZDcrWhx7)w^4Zy>yYMj{(IU@vKc;= zV>HLg6y=FPf^?u8rz@pzSFS8ViuvDpMnvKjd6I2;?GBw;&Rs9th6$QDeQ;~YKG%5Z;5fK-GZCV&HDQq)Mn>TJpy4XNSXg0#J^8cWB zbw%+3R5vx+@6iKxJASvd#RhIMl8L-aWG88~(A@Qdy<{8PS8fNSbmz9>b<7)RC|5lF z;m6H;*GoVmi(@;VO?TP`fLYBNAoO5pQ!th@NMM#)(XvR7ywOYL=7WB+b+Mh*d1PK| zY&BF|!N$znVGm1AZe6Dr>!29pO=`#!jR<6-)ofgxbE>RuTvTW(zr$8ep{3h3>JXhj zlm5^kz$CY7;8=WNRAi9GJwVcPZ{OadHtWQyHYLlmY0oN!8R-;kbNWk$N(R_Dulepy z#qd#p6F=)bnPxMAD0!+;xZZt1dDG4pp*siu3y7cJ*qUj9EMdnHcJ;tHd4NG*qo^fshcLnqd`y?#Fex`0I{sRypwmu_Lc%tyn zU+s=w%`gw%xDk~C3Dn&U%dk0EE^&N^_JeaA!)M^QPb0;}0IQNyxPUQ23Q+>c>SDUhLfS`-pt;%o{)I@vJYjn*K&4J8!ZQxdDeMk~pG z+HNu*T2U0lFfl+k7^WtwPYI{!rQ%_6%FgY=-J)8hZmjmfT1`%=R*jZot+8>Hx&xLR zjdVIeBF?W5AdO}i&MGX~$61v*>60aM8q)gAIb_V4h&gGEGbb08qBxHl#6&-d6n2D- z(acWfG9CplLxIy-Yaa`v|9g}(Q&f&-V|b(kH7qKO`fOuSd#f zQH$fTm!rK1&2^opd6orgPnHPU5O(P7nn}9&`D6ug7Fj{FCBP**_uSG;*GZi#6@g7) zA&=xWQc5P%T--U4FQZHs+(hOI9=%e@coG8hI+t6TP%MT@=-gHk3D!|qqbB?c5sqme z{kb4go-dRi5xxaDgd-NG^a=NM*j@JkX$vEg;#r>TM{v_m_{(lrON7PZq~`_L=eX-8 zwHW~VYZ#jq`qrmRj!N##eI$s`n<*h>56POMW|Gcdq8YgS6|oKsgY^y7#B$~KQusEh zie|opd7~Dh+zRWLnA0G{xSejaUvDV7><<};? z)v^rM;9D<9!}c!9i5hstoc*AmZ znIZ`+*BuGvbP?CC&D#bW4V4+8Jwj9_UgI&o>y3)kVqOFU^Ez!Ds;#Z;+_H6B&jR;G zaYLqbCzQLF){5n~WBFq_nJ6nC<8xd;+qPo*a5w~o!q9VKCWG<|siBsEN4J9un8C&K zWYIW;48ysO2QAJB5~2+`>BwNxo#^2#sr(jHUxQWfFkwAGzQUHJGc(n>D(!qpxm}wI>u6KB*hCj9HEL59K14|fLxZ)QJ=>N;WQ(E_WY(gLp-dCr zq|sa2s>?4lylH$2TjE9ZHgFL`nnAB|AQaIs7Hb(xM^BJJPdHIk(l+g{9vWJzEQA^9 zYE^wJ;nB1pbR`r@lnFU2Fw9$p2F7)D>sTWLXR=>v(VjphO4)0qp^hWz?#g0FiU}vb zn!z#xH6sY>h!|7^!hToKP_b?t|D;eToq;aaLbuc_L0pP zTM4mUv{g9p-3!d)D~<$}Re^-~mz*$8F;lUE=n?=rIF4)BNG*mGef*oLZ}2-LpuYl% zXi9dCDXN_zi=CksJA=`gV$1=YE!i35t1~rbVpak-%IGS7WctbYLOx^nBG>^I{+0PH zdBPq1TghRTR#gL6*civ~x%93Zg%{Ghu^wdcz2La`#s-o0{GIjqK{CofRo0VwkS(MK zUCTGm>fuCqqidP%v>r}{H@cSD&gkK}@J81%+uR`@o)2$yEwk;_o3r7Ku4T4;dUG#7 zf=$;l+kQRVAKvI%W_zIe=71iQq5>YQJ{;16QuOAb>cbH|C`E4$S09e*K`DCkX!T)U z4@%LS$Epuc>Om=b^F;OGm>!g(H&0a`j_W}wdUH|_PlpFx%Qw&J;Y4_&Ynkn|9!`Zf zx|Z3_=;68WM%Oai9OJ;^`S3>9GTUCgIUC;ST4vj)H}~=*Ky@v%?bpNo;f=0kwgY;2 zAiUAF%yvi*4~93omf4Qz;i2$G*D~8tJsb{ibS<;Z>*3MxM%OailX`e8ywSDHc1#aX zgg3gD*`BJt3Hr7aF@*dC)exj>ReMN!r4o^bq`p2!{}G6VCkcISiulD5VKQCPC{!h# zan-y~0ggRs-mN@seYVajN=aME<1CxeAL8$GX6{Lw*6g`-$!Pu$1E%xmQl`NuW*w|N zgQotNB&k@oDj0$GrBt33`GY#lr^tbIF3pJO4Vtkb&4`0Snvtl;3u?x!b0XE~C{#!# zS!ws?nT`J{@VZto3Jz!mMw5|tXa%ikmK{`5I?_R-&X_M~*D5wl$rScgIXD0V8ewZB zEjPkUi2{w6WgKW{2C2$AcWxXw)DZ>MQR~vifvAWoTLg*e>6S^Zii`XjU_}=JtJT>J^Gr{<(}Hx2fwtVqIG4=D zu>w4*(Y1BfcAbj^yNg3)18N|{2ST+*rvU@16GoB;qE-Ws7)uLV4Ka z^udm$$96RxV4tWH8rkt00IXX|OxD*$0#H^u2YUoaGB%XAhAPMabz+HHtz&ZwkWe%= zwqXw0&3S9TW|Vn11r{;zL@-|CQ2?8IRAznJ`aje59vjtoZJ6T~bBU$yKDHbJI+z1q z60ahooGC0;+4~#&3ort}Y}+b6f={d*628H)kNn)>?ts?)!^X_`vJt}Wos`H-9joUQ znP8KFu9~n&WC|RO82Ta;qEWWk#6_k){^p6yY=(M*(DW)1LxD+x-3}MP9qB~~PU2&% zV?e|63eHNtg@Ti@y?U7W@8Lzz282;jq(Mru_%TSy=;d<|qm%@Q3k;#a?*b_q6qc~B z0fQ+7ah^5Xg@z5W;WYWm&36>W=wVP2=S<3}7I-+=5OhK80jP@S1K(~O5$QIV9g>AM z5RGorZdN2&-_Jz{$|=AGIK52jKTrp|AUaTBbFu(n=>TD>C`wWJ$PugRNx_d3Sf4H1 zilp;9={EX$RBf@POq;l0VGo#)@-hiTC7|!qsdqN@IO^2okMN}#07_dsAzg!3Ij83w zF;xzd)Hmapmxh+A1>x=e-Q-P0wa9bc+FP@jahQ|ZZ%pJ{u;!x&(rp5TD|#m5y?WUc zDwnxYW^NVvLY12BU;}t^I>VDO<80T|Xa3>|@fhG15rgdUZB?+m?ta{*ken(fc?gG{ zZ%Py@L#@7v3N5sACvc1)MinCtv8XiGRvbO(s8jIMFe11!FUgy3N|*r9{2<#j*cz0O zqV{omVL(H(IKZrN@d`1;*5WDMpB5-TA@GsBxDW=*XR?k8@Y@-H7#je?ge|4oM5N3Q zZH8#P&K-+4z2P7=HvA~F()tay3nzKk(ijPkq6w8?{yBfjf7sXizF!f-3#Ui92SqjS~qfkBq zTvR8AirViDc>oQ?-l^0jmC(}^nhY`cUn|-*=Vu%Xt0E53%jDaXu&)P=6n^VuQD(ug z|J{sn^r0;Xa2Upz%k;N@{*J|OTRj0O`cqet-0Jpupp%*pLS(cSpH}l4B2z8W!f+gE z5fI2Q(}`o)GsBj$jp{w+w~hG><&^oTqB(|QafXqHl=SQRK^==2^W3w0nE7ulv?z*6xfN7!Y1+ER zr~J!6IpPS^w-1RqTf#ur`AKoBEwB2a1jz#Lr$h@JpcLviof$W1N)>vB`C9Dz%DnZU zw760mHbEstrlq`zyE3IjfoZ2EskY)M*@JNLh_0to5Abs-Mym$nbs3^q zL~i>+WRd7R8`^JNbKt9?b^kewi?v*S(0FrkT1`Tvgf+cS(!wqHJVPe2bW4&!{KRIM z>~IBFRw8e-FEuaa@dj#G*6y?mDFLA&6obgayDjcei!7*x24m!A8*8j3ldy8e#~Uw$ zLoa1}xQJvAdWA=)5{`Vr+M3!_;X4~DcEN6KWm>B8Y@1fm)a-1PloZEkzsms=4w;kq z;XvXlDr^)}w}=R#f)a&_x&yK_DljCs{gXSFRjK?xbNR~F-NW5U)rs(b|(K{RL^fCw3AINYu{GB+bw=s~U*ZDto8w3X)^V)YA%<2s5!vl;m}^auULCQIjwY zgpVU2_GJJGcn0{U;JR&lYeHCILL}ii*|wuK>Nk0^y|s+nVvSz!+Q#pOTh{Sb624x% zb$iRz0)f=bMeHhHq}y99@?fSYJfQV5?)?i*q39ZCvP<(V&NFP~Y;E~EN1k?vsAGn~BfUJ8n@Iai)Zt++jTq}9_Q6M_A6A2S zbPIz;EHQeOK9gXbD&FgW)CPaHVd?ua=hxq5WEjYa>XffTc_{P7WHp!Th|eUe z#Fk_#KdU8r5M@k+PD!F|%-Euw@Scr$80^x~ejRyjQP%gEtFM|53?PC1W5 z^pZmZDFu)A8jSzQ240b6bRNSd6r3s!JI1(Q?{%m&vuaMKq02F7aV|(ys>G*VP|_yT zRmxZ14>q++NyX??$B@CRz|P9Rg+xr7yASYztc6p2DLekhlpx!fD31NyleSiiQa2^- zUaHF&M#tI$%*YxBT=t)&lLlTeF8nF)Spz`G`m~q#Ey1Fy-$Fdy?rojyONoAq(Ih;FJ4aUmZHz$gmI-JJnb(RbvCt1%FG5g#iG+11W=Qh!4U7JEqEp0n7%6cz3swgDpjHfX zel|><724aekWt%E&PH*QJcPC446cA0+xF;HZN_@3nz@ZyxOT#IDqQn$oetN{;d+Bz zYg@yACWvRGr|>or#wtg6R@}6WrCM|;;O=IzE*zC&N0z)`&`NF$Np6VyY2t`SOWDp_7Au`cQ110 z)jUIAQNMVK1aq7GU@&FDeQ2?Y2lZ~t;+!#IB6~3f617JzZdyvi!tmtD@E<*9k$aC4d##dS0gjEjy(~J)pk9c;$J22;vmE zDe;MuVSzwRx+(OkxqIRN?e1LQ7=`=sjALHd2}EuDhMb$zE%cY2JnI4tgDX02rla2j05TiAET~ja><*Is{?56#>UQTLAoJOst>pH-=brcf{LlaYKmY&#oD0D<>V<&D8ufx{O`u+w z)Ec8+FtI;-2W=H}B5j>QWoeUPFFUF$Y%2?!p%=?&5ELllp0`t0WA?Caa4Zd&dXjxt z_=2w8!84kHu*_*pc?N85Yz=x%C-b=IIq+oSn>xfLS@md)^!Ws8l=|lIEusO^Mm<7l z3}0;oRH;k{4U9As_IXr+*(KRWv~V?tuMx!o5Rs_It5F7t1I>>x0SUl1uGxZL+>zYP z@mg&mS&4yuASV=immrm6gEVd4#J`pW3 zN1ZI=_|ETwg#enqDwUDSP#@Ctqf(99L1Hl~sTed@mY5(%h(Wd+)?(VahDL*TR?`v7 zKJrcjN)j1Ohqh;06$ek+ZKoGPcb_|JC(n@Cv1)A=B}{E*oM>6KHnUOA)aKdRyzB>W zv$c7)Hp>%4*Y%`o7VAm188mv5V6%j@Bv8 zR>@B>b~8f*`(#ig!0w$m=f5dnj}EOK`Gyo5L#mN9tK;Hyj2w?f(BYW4l8&?F&`O%B z;movPA(Xou%Yz#GfP&KwW`G(1rQb^0zRe~6j%_X(o?`Tl)hgpbmE6NBc|590Yp62c z1Y=g1twMNT;L%SAzKYE+Zp}$HOw-&wv>>|%^3AG&Cc<}QVT62#n+kX!&U|PoaMWu~ zF-lM@>jmaDebfu8(xeZoWj$?#08bpBlLk$Uiq|alve&`&l~!*wFlH#SkKK5oG*q@x zLBKOD3<9xgK&uYAYjV+n3XfRG?NUOpWcgiCydMw@IzUH&EYtchr7bFj-+ZI zPeeW(x?!GiT-0WL*OR4)4JNkvpyWPEN3;!!EMC~yiTn;mZrjUovY z17`0V?Thp!Qpt+f!-4?=by~zx3Zb#txaVq*f)#z}D#n|v%uZo_43!M>K}n_#_MA`G zr^$Wo+d1}>$ZV<#e};rf`xlTeOYS*9)avjIb(?Rg8|PP2!E7i8H0=+iPM>=3tD;Vi zlH5?IGXxsO$iaY!JUj7#-P{1=e{sKkY`bLPI?K!w5_Bv)U}t*IGmC|V)YpV{l)k}@|G=k15{wi2}X$;VVjcPDOD;1O-QpsQea$b2B|c$0E%^(j_X* zfIesdM4kDJ*~>JKaSt0I8%C`W%!67ZB#c1E>ZOmLzXuF8&|y;ZVLZ=S>%Yq?MQ58; z5?*mkTeGOC1GNH_B(Z@Ftq4vC(Iy~Hc5i8boMhi_oKJW(7=pL4+ldz{m{1Zk5NcXW(}EJ!9w!C_!2Qlryk zPYp^@En!jR>0iwJB@;*~Tmfe|U1jz-8!pTlwuq&0&1+em-pJ|KE(zgT>_?2*97gHK zNEl2<-Z*sA;R7OGTES6w@aY1Yz^=+5i`YD?Gzhn*K`>J6KCsi*Z>}>BO|@^c+J@>c zXnEi55Leon^{`sQ4q+{(6pGv2m|V-UpKrWO8AZept~CU#3>!h+^mdKJP(U&x7N`>xI2 zbK}OwI$P^KH*Rf|s|KyZ1h3XLNH9vG*UuIZBP3LG6& zj_fDRbt0;{HU}6c0?jqYmW$RQq6eC5?rFw{TN8YN_sgv2nkNXB$Ro#_OLNVsW71qp zVr1@7N;?#jJ~iDT@}iePU+fD>c_?j+Xb1fyMMSC1bEwqT?lP;uhGa^Cod^`znSrW~ zpt}5Quq0cH^r}7&ki5C$X-HC%fpJ0Y;dC)t|T| zs%xx%CeRv$R2T8kQeC=a@IbobvjXcfj}%;0MzJh2F_;5F`pey@DLF34R%ba(Sm$G& z?dpq;Jv|(ofOHaUsgyp+Y^a>cjWui&BmY&?zu7$TVm@dJjAi2I#Wx@fw-{(7vtNlk z|AJ?u4@j=%6L!~-@hOmeG_Z`P%d-j?;pup7rj?Lh!d_IElVnx5N5Dg0%@=i*J^fVa2eeQqonz9Za6dvdlpe5 z`wasGeYQnou)<8Oc6zC~rb*l7^_VAXD6-SZbobi{w+0*b&Sg3to#!H*Kg z42X2Z1~+R9<^lbL!h5xbrb>4AhKt_-e#R9&fZ!gZI#)T;(q}w@nd1Q&Nu5jQY!++T zEO!S8jD^13#4th_L$#m07(gd!PN;C$Lf1J~m|Uu#8*Z&MGbNK9v{5U4u%iX#4ub}f z&pLjx=}w6z^#oyc7MED0gG!!&795JV7e zJgrRFrqW~-$$TZoYH6kVt;Rfd=a&(TvkAVap;ZS_;z{mBI++mp68V>)hZalC_3e5M zk7Hqzwrbmb5#UNqljYfk5n%C^#(rDRVg*BnqyV^8CYzX%UzzM0Yw6=)?}$J`_0(Pn zfkk?i3lykN3Op-eOe#URFQNdVs)}Sb!3!a! ztWv@%Ei?ofAflTffViMm+lFju1W!#wJkXh&9=XIaHJnY#W|fDUWcnrsR64s;A23Tu znHeuZ#3WJk7I_x0ysULWx5`K8fCD3@)9*$?UuA< zLRbo=gb6SP>|!*G>6k}WH403ye9C~p2n$v>4#Xk-OPTp%;o70iqu8vtgr1*|JpYL2 zn}he_zlZJf+C(=DC6;~g{bKX&JfSn+LLIRzUnCME4OLvb&6Ai-pLtr$(^ckaAy3=Q zllbu_vbOVaR|+&dh~U(AgzZy1tUPu1`kZC~)L!}ipw7nqqRhLdfJT4mcJI0bE) zsJ4Zw&F6HokG~U^yCnO(VvMnfR?;xnwDK$NBpaZ1_<$j1tzn!SoMsbDh>e)__^onZ z&%R6nUc;EAD9jm*RqGDrO=4Rm=yrTf_FD5XUs_M#@l#y zHguV&E?6>h6owunm>7N8bx1*`)j@03Sj{2@O^;kz6pXMuwVkitSEGdvKpO0q@SKgA zAINJoT(F7SQ>7Y{rWHmaVj*Nmi+BW+UhG_K1GR&JSJv>2VDa_=I?s_pS-`}NSswu( z)=fr^Zj0e@#2HXG<3eGxv;R&|lbRut!RY^0J9I@g21;|S%3{$~Sxf?FELfGnoW%=Z z3Bjt2J93x?f_fuMxX*L@%kuI>j*2*hru?7LlMN8xd)BQ5NVzyK@n`jVlp)9V~2bM%z6`Hg1|Azq;aj0(f7<21~Oq_tWzka#j!*m ziE#3k2?cNyh!_{8s11`L`bp#8?D};YygG^IGJ!giOEbxg1aIU(c@b%Wkiv98%V`%q zOsg|4C9&iLXtAcd+4=;gE8e7$Dcf$|OJbB1wPuZUEh{o$qr$+GX3#?sev6K2iWnwc zPp9rqE?g@Z@^UVZ1qCweK9xkr^@OA-7$}dW&k$0Yi3wD<+0Av{8ahc5wr)sWTSpsM z_JtO@MSZxj+tEwe^KwrM8I4 zN2F$u*aC?Rb(dSS9+8=TN?M!jIA_#c5yv^|2y~TlmB~I1HzLm@*Gid~p*6XWsPnEFP^22(ckbAzLfRkv?B4v*wyL2IuA=? z_L)CuL(ppCm?IgWVz_Z4`x0CmB?itGHLk1y;RBpNkcralK&T8g)W#uBpG@teCrLh< zA=Tiudw{4_4IU>lIMn0VIW1#6R!$LM)(S0{~u1Gf-O`2P#tr{M@Huxeoy(nHYry+-ai!JruE0S9Flr#DCmg*ySv#7_(g=s{@W z8Mp+3ml1U(bh)uLu$xe!rO}T}5|)LvNutRb&KRRj3fdEP$CsXKa%mkiiEkLA+?1E*j}%o^Y#t zJ*D-(GDBG=caoWJRN9Ra!L)rInMNd4MLwuskWb_L0=-6E28D)lC;i-t8Ul-Vudy2#A%dxIQ>C!c*zrgOWO$1ob6&i6W@R zoE12z+Rcg(U?B4&r#S`&81NX4z%-8x)zvL+o4(fP>wTIDa^;D~h{q#iIy$o>M{e)C z*>wByu-np?O^#|00FwPJHEAs|Ioc1S|3%RN$S556%o*}nWgjXBK4B_x0Nyx#t$V$x zb^6UbOSl}nImk`MDdjeWF~GW7qa7MPyeelg=^usMFe%)qoD7`GV?i1Ph{YHvk~!-m z?j);lq0A^$5;4d1@Vu+mWSTd*Z=lQM=*f@)9VSOdVaiTj+*Qrz-!cX+?AgV7%${9* z>0^FIV&I9u0|H9c!*DzaQW1CvlP1?6LU0nKGMtg^r;Hw4E~bI)hetL`1-2i}K-qry zG_Js$@*B~D$r&>V#o(-A4&|W{5PSF<6Cf~fSmD$l^l|5boVsclPO>V7!*oTsG@M8V zb&?KD>DDmuyl2k1%4RsVE9*CVTrqD=%n$^arH94^YIm%E`;}f#i^U4^$v!4npI0b25!y*%k5#o%g znZ|8Xb!4rzg_-6+KfyjKj@RCwvJWAb<&Ks>pa*6~N_s+;qbCx$E@){$GaV|4f!(f5 zjVQ#4e(+xF4k#xExu6MLl9qJ}3$RlU314d@ML+u~ZD;*o$W}DV&we8kdLog1Gz@;< zb!eZH^}z(7V2nSwS_?0DE1WQ-PLIX`!^!^j{SAAw|LKQIHWPpMZ!&wcKh0n)mi6$t z?-Bar*^2nsM|qqUyvHT)?iX=|P#V1Wp8Xq~9jskG`}q%{cs@J(o)2?-oKG%&g!|`W zO%LPhm<2l7Kl^3Ue()|*FV4Q8+dW?PmJe`$*b6deGL8f= ztX%j}Jrwuc8xnNRZ0eS5$C_&q&fJG5(}|0G5^EzY#XX*c*C zm-m=>F(A(DvJA)(12Ww|n(;_)2)l{t4!hCk*TrrO(bbKqO1m*sOk7&h@TriJvrW?`EIxJHpk z5F0YC1%`_hCz6hUZVa^03aF#&XD-4AM@GGd7S}h^(GdN#rJa0GR7*5g_O2-on^(WJbE|MiCyctWNGbQ%VyLNgMk!JllR&)U5us%CVbvWZ`IbMbIU}Xmk^l-NKsA z_+-Z=mtsfN;7GPoEcO|Q6CFm-G&U1tMPtlnF)cr9K2xv5%Iau5Uj{H7E3|&!0+q0~ zEs|WDxy7{`GPOCym~3SgaR411pSsuNSm$gUcdrTHWmI1IDI1wDdssMKx%;xWF~Ra> zk@dBgwNYPwM=K2vqh*OK-&aOeQZ>twV4}rXC1sR5kzEVtHP|_`|9Ivoa2E%dn2f42 zqcWH*A7p)bfa?CM!&T~WZ7aw_*UuLzYIeRLBIr$nUhKq_UNB?zJkF#!I>$q-&oFPYG^I~8`x`B96e%- z&onrOi7?9#d6^G+QHN0Z3y8nOm;lIBwvcC4h-8CEAwVhXHjA?XD=g&=f{tqZ(J>NN zw-{@0F>CfYx|t2ftSEVA*Ydx|$j$C-PS0BA%+_qzdU7Tk=F|x5TZG2Jn1@U(wKC_G zdXAw)v=_^vXp$On>>b%q9!37_9dyT?+?{5_C4==kEi!(|f8jAT4Lq71W`$;Ge4*n( zC-hH+0jOa6SS;8+R->Qj3R%raITB3gSG0AE0?n~Gv7?X8f$~7^YKP@a**vB_700aZ z1RKZxklG{gkw4`s~i_my;>p6MD zbuEdt{yVN~!K)rDg>=_n@er};8%SPv*+aznB)H}9b=UdO;2VU0H(Yled#f@ZPQafS z7~`J7>~EYV{3?E$XSas zhH?OfQbcNoWBlGjX(;X`t-N zDTk&hc>ujNUrt|DEh;|;XZR@RdUiAsO7!XXqQSDd4ev8S&ZnV3}0AEDf zlnyBt>n3BQ$5T@aP_TH746PJ>H`1Fh!gdnr#EfttP6VCQ;=_Ph79$KGbcG^>On?Pv z5U~L;Le-N)m=Y-`7%2m%%)_oIi5u*Q4xn#cv9clbX$;^r&{v6oO*p3(AsJQS4Zc&6 z!8r9YmUZNq%8|^dI%IGe3HMWL@B@fxoy}kg&znISDMMVX{Jo>92@yMU0qynZ7POT_ z!T}(!-nAs`LFEP+YT%+x!MYp_Vh#Y}%oijust^$W*a&APrtLDYYfe%Xinm}ar2oSf z6^uo~_5od<0FV{{kP0or8LN#!I93`$I7T~|Z3tinH)VmV%{DZFOb=5Z7~$6i2*(DF zNeHLCn^3JP!kN;U<7UL6UbQKvp@^nNUJcoMzf#P7kZ7esiH@7QaZYx&?+sdVZw)}yyVyRMYEtP4@3$(JC`<+~KBVolw z!@mr|`*Y>df$?&sINF~p>|M)sad}t&c&VV~Rf;=<=3GfKA_hkCxf1V2hpRm_DaOYJ zbCtY}FtMY3}tzIU;wD(OQm8dH?lu>uw2}`o-sf8lB-c+4EE&LQUmh#cF$yeH0LaPY(-_COW^$dvHH$0fuNy9pjpPpc z!=qy(`B75mG^lJ)cgNt$q5k&# zz{*@#t~0kP-!rhhyMK8He{w5UbqwTJE^iwd?l0v^2itNZAc+~tGEFb+G6VTeugSTH zwtNxf6#fky?9Z3R#tVCUdx2zc?b0z zj^#c6BHrB!ByUcY2L`QXk;`jS}>X)EtU?p?kW}c z`$M^5aNx4t!)1RA)bi;nWB?)nZX6_*Nt7QX&zq7>&gqe|Zs0k=Rl~c{VG%7lv2VOk z%I6^e{UiCYT!9hO#24Kh$(45HciQSzTY1zX^v;oDam>CpkC5pS(dKzzY~0+*S}ZIl z5rnpE7>s%N9qkfc|CYOO^Y6F|SANW0yzSdT_zT=c_x>Yywfn&!{%-E#gMSmekELum z8Sa8_7|)|~BL2HJH|T2+{gHfOS7o;uyMIp^;-hWjGx@aW*O_STiH9UUeQBo(Cj z0_9&t`M=^)TU)pmatSZr!1dbe9Vivc<^FtbblG@ic%)3#J*_>a=E`oycF^+R_H>i; zX6pF@6JGUNT#|TF9?LQJ%LBzR(Aj1oK1}~Ro<-}!@F#fI7=+=6cwWi{fhq{KOYqH7 zd3awQjLZ#OJ3b5z8HMv5Ajz+3gU|i7GNn+sC?71hiNCb9wziEI%Hv~_HS&WRw!P&x zQ=94Z{JJJ*i26h?1oJ<%UJIPI(P9A8yD0x+%H0Z*F6JH$Zze1_i-&8B&)~B8{X%XO zn83*eFVPibFEf`*!^M(cfCBmb2caR=@_xdY`Q$&7OY=tMiFT>ZkS}Z-6Cs$&7yQ1h z#X>&h3ulo&%zrk|OSr=JX$+TfiC(JwR<5w@SHg4JNFSEn&U43kzgmMeC<=GKUdCotzgDOnl1^$@&_trMyzRQa^6H8YSV&$mAXoKRrD$9 zxsvjQRX$fVEa@f;3(G_a!?55ZDJTq|%d=)~82(e9g`r{ib5p`DGey!*81E4lX9>dz z!l&`2fRBJ#_tr-+D;&DKL?4LQD07mPkVUkdKRCRP0b{&}{I}*y#Yl-MN{qH}-jo)+ zfw0zVVGCMK3u@tZ!s69oe8-gdF~XAn!gz(FZN0=6`pQo&@;Hl2rmxq zOM-iAa1Yak_jOvX8KE{1iX^Zi7$L8+F5B&EeWv9RBbGJN$Yn;PjiivA9*Hg%p~;1V z{z$HnFJDGzD8JtZE;b?Y&e(HWy8OoG6oR2cl~fGLFg@HJZ-#_tg2fTwmdOgzJyFZs#g{uxRMD|znX3Z;T>o+EtmwWHJV1>*9-!T^%vaAha!sqrzx5}h3<+a3>7zofJ< zJjS#5R~Rl$2_GaZDJP77E6?Ht`bFS%Y?*~LVZOsWN6UFT;W~$J>jL@rN2Q;mAWy>I zU(;)cXwjJPgD4@hs@P{>`!LtM*gu`s(BG8%Z`{ z_!EQ|PYJ6X(fIX|@Fz(hjsNQ@;im|ThJ^WlMp(2X3~%-9`slUJ*MVXwZ^X9YO1{*V z8_bPyFSq3jcWYI3etv$$S4Qf8l3!QX0)lzbfF+;J|T zvsf#-;X6RXF-{tRk$9yW)?f z99%=37&#$_Ubra~IA<;drsidF_gvqgI83#x`L4dLQ+vuivI45*WfT zu8@<$KO`){B8-1-O8oOv;{W@U`2R5_{vW2q|KpVSm!`xYnG&CXFh$#&CLC=ahnG%@ z*9Rw}@dd#&(w`XRi)DxXQ+Qz{94CBMB-~6`;~S!Z{a?YkCb(YAhuu==_!Fi0M)z9?FWKhR>>YHEEEbC(kE z^_*kJ(Y!e61%H_8CCo3})%b``gnVl&&x6n|{X&pholvj;4D~7|N_V#r-X2`XF?^OW zys_@Ime2TUO=dwMJXKlIu!N<{ww$x>(q8`x83HXbM6ClF{2f-i-rHMdm52pV??$x{ z9e_xc{e#7Eo(leWIbTbP_5w>YDdA+OfBh}2Dt)Qx5BB;S^Qhwq8uNoS7nS{?QgPHI zFh&Kfp)3CWJZi|QYSqtS$wo0UT9o0j)!)HgYvA9a6Jhcfq?3XqRFGrvkzdO}Op+xG8?=R)6q;lL3eyW~(-iM$g$BN% z$t?TB{N>?Yg&Zyn7>doXts`DanK0fM>THxVNSt_b&?bh*w92@1h)#!c^d+_Ymj=z- zuGR%*?g*>2h!VW5VGirj5iS2p;&<#G*|EE{y}1A4@ey2{ zvV(&G=2!vK%++t7X%&}$%RNb+pKuo)31LOK#S%7SloE`}FM{r+$^f;Lk zCbjJ<@);MF5RvU%3n!bCThwIa+9G$!aM3ZXwWokxz;Hhn${0b&61t6xn~afV(tM%3-MkmiUz@88?6woZnc!W9cR^tmEi7R($pjb4 z3J;S`bf%NL#_?&!^BY`W=lUAg6I_pT{fKz|F5IzgtGp!q91L)DH0S4r#AhTLjc7$; z#z}k{@iSK{S&?QmH`ZFsC@F9MZuD4q7{D^6HW(2N#M?w}h^Vz9Wt!qz%><&x5s69V zahkph5e7kR_b?qs0)Yl0HmX`vjU-~;N-*q4=m>mrpmor`Wj(Fi`u9iz6ipF-BvmdT zO06O6$B}W+l2|5hxM4L)Q$0+E`VC*VYE-^Fkb{tygG#)a<>=9lfk z(7#OHD#jP z-u!z9iq97+;b%J|DUU*d*WTR|IK0rUTWPAqkN0s&RW>fM??gT>I9i6Pp)jvdN2;MP z{5hWeNci(S_f825?Zfo{#IwZRF#HnFZBxP$Im7th@!T~f+#Cs?>{?%@FulSW!Z18@ zN;oqmy~5G-jf5ovg!vn$#800Ro)Ls=YQLIy$(nX6!iHsqD$p=|PMA0FMKXN1d~mcM zf(2(78j@0oCDnC7Id^bZTR+5|tn2|0Ys0Az_n(kW8bxEy?^@PBEM-dI=hEtYmOM+y z6EZxhAAQ0v@LnRv)Urb{@MzVcWtCFN7^NkPdU>~@(tJJ9wyKf8pu^(qqqI?Y*WB`k zg=d_(Xz^KRFIl>*y<>Ujij`g6J*(Da*Iux0{f3R3HeYzrmW#J;zvR*#mtB6vRd0OL z)qT1Cfx-Ol;XQjtM#ru#O-{EhNO&bFO}e37Hf5nm+Hz%l-~Iy!-&(6?$hsRG8UOA5H7&R3Nmo_}9hd~4 z8gzh*O%R1ni02k*{_R2Fh~sKjw4)!F3iglC85xIUh9T+w%07WGLRfIM;iDyv>u>LY*gEOo|p+$HOT zceC*AtA!sVUh1nb{)ix^;6$>bA@?diLd@J=|hw#aDPUyPr{_pR*CuNSW|?Stdb>< zlitFzwlQUww8b(UqN732f^J2v@HemP^$}jJQYn96cMcGF$cxJVN-4^8O-HZ=*MZ3|jvwA&wzLa3yh3FQs-wV=8LXE!ps~`v9^PJz7x|Xq!rt-)+L)CuugmWn#wNZF zKx%`M-GnFqN#$8PUOvc_H5Lm!>zzuLSwYX%9qp3wVkNi!z(79Fat=Ggj(mQvN(j6I zHlPf-vV@{lwhj5257L*QVX`&^kDIj!+fm7lq+PA&dtCdMI{eSERFd{^@*6 z3m1Z01I5B1Zfn^2VF{D+p~hO%WZagf&R3v+f<0|PiOjR@Z|Rcdz}2zuZV`IzGIX#g{Yyo-HYzbQ19UZnru8N%3y)KJ+MDR(NPJE7nIOB zCu`|On!EUe=&*QZ7=A0yq93}87U+5#*L7T?HPQFl=c=%NqUm$F!uA?^Xt;JMw}0op zyc&iMKL^*w83S{#4rUiuUD3-bcd|gpEs>C1;c34iUEjA0C+-O+kbMEzEvRN7+WSZOJ%? z$SPw3GH(Q_#PH2_6d5drKw8GfS~iUhI+Dt4d^vWi;Xq3@ILt2DscQ6q?O1Dv)Ti1# zXS{N(-;PzbpVitrbb*!Y)*6}U;_-^o4fESl#gD4Y}NY+B*>RIE88fzJjc=i9j6%jN6$ zw)&;@bnR_^Yis@WOZn?xZvxz|UtGUgX9C*PYx%&$7LgZOP=5p%BnKzn<8 zNBi>j&h{1UE8Dx;yW4x(S9P>^baX85=V)=^B6)RS(T+y|ndqvNRRV&+9 zcC1{!vUBB%l`B_vt?XXevvO5edsj!-@~+OV6 zyQ{mqyQh0qPkT>C&+?wmo)tYSd%AkMdwP0St)hvmsD2eiuOizjUL8G@XG4Qa{&{}K zD)!Q@w{B6|A0YZ67>6*rgXcTB-p3_-pRx6EwhCMNje8E$Pgoq{yN!X%_+?fZW+h~` z?mqc*8!RrAxwjBL3iKvVn<)|SI}egh^i=Yk+GPvpT$P~G?-MWV56cVFZw}JiCfEkn ze2L8bqY&d*v`wm-E3qb^kB_oa}$QviWHsu%B&&l4$XB#uWI_2o`tAuu_t6N)J z-)!{q0e(Q{CXqxD$3Eckl}*+q;A!r&2#0h3KEm?|@8uFN@5hC#;Ger1!}*}USPy4e zb-uu@bzdJc_-4DlIE}mTw}rd#V``a_=Y>yU9pcT)xN3%XS;Z~w=1i4yd~q4AD9iGu z@)uio=d(-~!uD6FOKwUZ=B~BD$GB@PKecZ0O4WT7>;mI{+ZadK)V3@+&r;4Aq?ddk z*~W2Wi9|B#rIP7X-HiJAnK=z}8=IyzPLDUmPCDu2x>@e2@!9U2*xb}ScfNP}EI+m+ zwk*@?w#PcW#l?es!% z{(_y?-|*>&Z~XH|XPtW5=^Hj~-FDTRu6fH1H~y!uKJ%R)z4+_jmddwR#{cNd*0u*8 z{`_Nq`SkaG^5Hcf_+b0(r+@pIr;cph_NJ>->FG_2+J5<~La}@G`D@qTap#U*<4=D5 zo8S8W-~RmNBaXkb<))v+Z(5h07f;SO^r>kRpG=%ycW7R0PTGyP#aF~rF*lV=&8Xi# z{iM|8saSk|eO)XaOT|2T{rQimP*6Kcjq=FCXOT~e$wziKN{<HkAn`h6jpH;UdKJo74C+}-GHQsS={Ll~1Of@7D6ZbbA`fbYf&rTA1TYTb6 zv3arSjSe3ypmSa#mGaW*I=bh2+cf~(^^IP$mv%9*lxbntNeEQ+PSo!Qp?|j#L@2+B^5OM3a!TdF! z`~1B5sdRm2=BZt)dLR7w_y4x8`;I#wOx2%r-q7&v?eeuz+Br^@CEa*Ld{U!H5@QuH@CpBko%NggL|MOq`>d05W7Wdz9 z#+i#ddwMrrxMlke40Iq}-#~t7Z~4IWH@^G6Pdxm{@z<+2^cZ6&py`wa?;c z_IU|+O;7xyL|eQ*RhL@hFN!zRb;WuUb5rq#)b`EYosFHT)^z=$GcVn`IDPi4xo6Iw zd1~EODp=PzCsm)^lwMRfo;g2zcJiD=ee#l|n`nw9Cf?J3+NN~<#Qkq+S)Zv-HlEy@ ztnXSHKXu~I&K=y*u&J(o!}@uf(mNVAA4+YgUl7~4xjQy3U7uW)sz1~K7+YzUh&x^B2WerDm)V4t(IB z(}%x%cHPH*eyDRo&TjXY7Vm#{srO*tveW*huhn_k@4EX)U)S&4=epeb=I(`! z&vk!tT5eU_?0Z+W℘?Kb+RQ<;6YEZQE9C-u}@?@7?Y^ox3Fet$QzVe$afW^Xzjw z+Mml^{^_6Id&M_jc9c3Mem5E7X$V@X-do`IDtkOll6r5h>v^jnAaM@@>YnDsI8c;u zf&}hNZx-Z*)RdieQ?YvQH1{0xHjr~M#ZxMS>BduD#+0fiQK_f!d~X%)3u{^6ZgO?@ zyX&UiOI$D2knY#_F_IU0^C-`CyQjI-n8>&b>)fHZo1{_R94{Vgit~*CFX`er5p!Y- zywmu%hEpihu9vBEA?xnA*W&Ku*!em)8T%nXptUKL=%thOp4)zUN4%YI!d+a~z;TTv zi1iRj##nFK^FA1J8{L#DjCo&M>9m(R zEq+(b>5eZ?yVKlR33oar%`l}Tz*u*6+)0h5oW6+{?O8?A!%Kb(uk(@9Tex0gef?c7 OtwXaPd;q!Eb^i}FF2v9P diff --git a/x/wasm/keeper/testdata/download_releases.sh b/x/wasm/keeper/testdata/download_releases.sh deleted file mode 100755 index 544cdd2..0000000 --- a/x/wasm/keeper/testdata/download_releases.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -set -o errexit -o nounset -o pipefail -command -v shellcheck > /dev/null && shellcheck "$0" - -if [ $# -ne 1 ]; then - echo "Usage: ./download_releases.sh RELEASE_TAG" - exit 1 -fi - -tag="$1" - -for contract in burner hackatom ibc_reflect ibc_reflect_send reflect staking cyberpunk; do - url="https://github.com/CosmWasm/cosmwasm/releases/download/$tag/${contract}.wasm" - echo "Downloading $url ..." - wget -O "${contract}.wasm" "$url" -done - -# create the zip variant -gzip -k hackatom.wasm -mv hackatom.wasm.gz hackatom.wasm.gzip - -rm -f version.txt -echo "$tag" >version.txt \ No newline at end of file diff --git a/x/wasm/keeper/testdata/genesis.json b/x/wasm/keeper/testdata/genesis.json deleted file mode 100644 index 08969c7..0000000 --- a/x/wasm/keeper/testdata/genesis.json +++ /dev/null @@ -1,219 +0,0 @@ -{ - "genesis_time": "2020-07-13T07:49:08.2945876Z", - "chain_id": "testing", - "consensus_params": { - "block": { - "max_bytes": "22020096", - "max_gas": "-1", - "time_iota_ms": "1000" - }, - "evidence": { - "max_age_num_blocks": "100000", - "max_age_duration": "172800000000000" - }, - "validator": { - "pub_key_types": [ - "ed25519" - ] - } - }, - "app_hash": "", - "app_state": { - "upgrade": {}, - "evidence": { - "params": { - "max_evidence_age": "120000000000" - }, - "evidence": [] - }, - "supply": { - "supply": [] - }, - "mint": { - "minter": { - "inflation": "0.130000000000000000", - "annual_provisions": "0.000000000000000000" - }, - "params": { - "mint_denom": "ustake", - "inflation_rate_change": "0.130000000000000000", - "inflation_max": "0.200000000000000000", - "inflation_min": "0.070000000000000000", - "goal_bonded": "0.670000000000000000", - "blocks_per_year": "6311520" - } - }, - "gov": { - "starting_proposal_id": "1", - "deposits": null, - "votes": null, - "proposals": null, - "deposit_params": { - "min_deposit": [ - { - "denom": "ustake", - "amount": "1" - } - ], - "max_deposit_period": "172800000000000" - }, - "voting_params": { - "voting_period": "60000000000", - "voting_period_desc": "1minute" - }, - "tally_params": { - "quorum": "0.000000000000000001", - "threshold": "0.000000000000000001", - "veto": "0.334000000000000000" - } - }, - "slashing": { - "params": { - "signed_blocks_window": "100", - "min_signed_per_window": "0.500000000000000000", - "downtime_jail_duration": "600000000000", - "slash_fraction_double_sign": "0.050000000000000000", - "slash_fraction_downtime": "0.010000000000000000" - }, - "signing_infos": {}, - "missed_blocks": {} - }, - "wasm": { - "params": { - "upload_access": { - "type": 3, - "address": "" - }, - "instantiate_default_permission": 3 - }, - "codes": null, - "contracts": null, - "sequences": null - }, - "bank": { - "send_enabled": true - }, - "distribution": { - "params": { - "community_tax": "0.020000000000000000", - "base_proposer_reward": "0.010000000000000000", - "bonus_proposer_reward": "0.040000000000000000", - "withdraw_addr_enabled": true - }, - "fee_pool": { - "community_pool": [] - }, - "delegator_withdraw_infos": [], - "previous_proposer": "", - "outstanding_rewards": [], - "validator_accumulated_commissions": [], - "validator_historical_rewards": [], - "validator_current_rewards": [], - "delegator_starting_infos": [], - "validator_slash_events": [] - }, - "crisis": { - "constant_fee": { - "denom": "ustake", - "amount": "1000" - } - }, - "genutil": { - "gentxs": [ - { - "type": "cosmos-sdk/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/MsgCreateValidator", - "value": { - "description": { - "moniker": "testing", - "identity": "", - "website": "", - "security_contact": "", - "details": "" - }, - "commission": { - "rate": "0.100000000000000000", - "max_rate": "0.200000000000000000", - "max_change_rate": "0.010000000000000000" - }, - "min_self_delegation": "1", - "delegator_address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np", - "validator_address": "cosmosvaloper1ve557a5g9yw2g2z57js3pdmcvd5my6g88d76lj", - "pubkey": "cosmosvalconspub1zcjduepqddfln4tujr2p8actpgqz4h2xnls9y7tu9c9tu5lqkdglmdjalzuqah4neg", - "value": { - "denom": "ustake", - "amount": "250000000" - } - } - } - ], - "fee": { - "amount": [], - "gas": "200000" - }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A//cqZxkpH1re0VrHBtH308nb5t8K+Y/hF0GeRdRBmaJ" - }, - "signature": "5QEEIuUVQTEBMuAtOOHnnKo6rPsIbmfzUxUqRnDFERVqwVr1Kg+ex4f/UGIK0yrOAvOG8zDADwFP4yF8lw+o5g==" - } - ], - "memo": "836fc54e9cad58f4ed6420223ec6290f75342afa@172.17.0.2:26656" - } - } - ] - }, - "auth": { - "params": { - "max_memo_characters": "256", - "tx_sig_limit": "7", - "tx_size_cost_per_byte": "10", - "sig_verify_cost_ed25519": "590", - "sig_verify_cost_secp256k1": "1000" - }, - "accounts": [ - { - "type": "cosmos-sdk/Account", - "value": { - "address": "cosmos1ve557a5g9yw2g2z57js3pdmcvd5my6g8ze20np", - "coins": [ - { - "denom": "ucosm", - "amount": "1000000000" - }, - { - "denom": "ustake", - "amount": "1000000000" - } - ], - "public_key": "", - "account_number": 0, - "sequence": 0 - } - } - ] - }, - "params": null, - "staking": { - "params": { - "unbonding_time": "1814400000000000", - "max_validators": 100, - "max_entries": 7, - "historical_entries": 0, - "bond_denom": "ustake" - }, - "last_total_power": "0", - "last_validator_powers": null, - "validators": null, - "delegations": null, - "unbonding_delegations": null, - "redelegations": null, - "exported": false - } - } -} \ No newline at end of file diff --git a/x/wasm/keeper/testdata/hackatom.wasm b/x/wasm/keeper/testdata/hackatom.wasm deleted file mode 100644 index baa03a853ac6b59f7b98e7fca62b645b90bca024..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177474 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 Q<*WH!!>@y1!`hbrAG;7^s{jB1 diff --git a/x/wasm/keeper/testdata/hackatom.wasm.gzip b/x/wasm/keeper/testdata/hackatom.wasm.gzip deleted file mode 100644 index 3c95e9b1d4cac2f9f0623b3336cdb068744e613e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64560 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 Bn7IG| diff --git a/x/wasm/keeper/testdata/ibc_reflect.wasm b/x/wasm/keeper/testdata/ibc_reflect.wasm deleted file mode 100644 index ec737104c434584f9e38d4aff968d29c128c3cc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254798 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 WrBhPB_oLIkiQnb?+W9r~w*3DY_Xp+x diff --git a/x/wasm/keeper/testdata/ibc_reflect_send.wasm b/x/wasm/keeper/testdata/ibc_reflect_send.wasm deleted file mode 100644 index 0f7d7e45936babe22d78e6fb33c0661bafb49546..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 269429 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�qd$b8;*CQ*wnE>W=6jPr;l^U# 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