diff --git a/Makefile b/Makefile index 1b046d51eb..55faeb5913 100644 --- a/Makefile +++ b/Makefile @@ -72,8 +72,8 @@ ifeq ($(OS),Windows_NT) go build -mod=readonly $(BUILD_FLAGS) -o build/wasmd.exe ./cmd/wasmd go build -mod=readonly $(BUILD_FLAGS) -o build/wasmcli.exe ./cmd/wasmcli else - go build -mod=readonly $(BUILD_FLAGS) -o build/wasmd ./cmd/wasmd - go build -mod=readonly $(BUILD_FLAGS) -o build/wasmcli ./cmd/wasmcli + go build $(BUILD_FLAGS) -o build/wasmd ./cmd/wasmd + go build $(BUILD_FLAGS) -o build/wasmcli ./cmd/wasmcli endif build-linux: go.sum @@ -83,12 +83,12 @@ build-contract-tests-hooks: ifeq ($(OS),Windows_NT) go build -mod=readonly $(BUILD_FLAGS) -o build/contract_tests.exe ./cmd/contract_tests else - go build -mod=readonly $(BUILD_FLAGS) -o build/contract_tests ./cmd/contract_tests + go build $(BUILD_FLAGS) -o build/contract_tests ./cmd/contract_tests endif install: go.sum - go install -mod=readonly $(BUILD_FLAGS) ./cmd/wasmd - go install -mod=readonly $(BUILD_FLAGS) ./cmd/wasmcli + go install $(BUILD_FLAGS) ./cmd/wasmd + go install $(BUILD_FLAGS) ./cmd/wasmcli ######################################## @@ -120,7 +120,7 @@ go-mod-cache: go.sum go.sum: go.mod @echo "--> Ensure dependencies have not been modified" - @go mod verify + # @go mod verify draw-deps: @# requires brew install graphviz or apt-get install graphviz diff --git a/app/app.go b/app/app.go index f7084a1023..77d01f7719 100644 --- a/app/app.go +++ b/app/app.go @@ -13,6 +13,7 @@ import ( tmos "github.com/tendermint/tendermint/libs/os" dbm "github.com/tendermint/tm-db" + "github.com/CosmWasm/wasmd/x/message" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/simapp" @@ -55,6 +56,8 @@ var ( genutil.AppModuleBasic{}, auth.AppModuleBasic{}, bank.AppModuleBasic{}, + bank.AppModuleBasic{}, + message.AppModuleBasic{}, staking.AppModuleBasic{}, mint.AppModuleBasic{}, distr.AppModuleBasic{}, @@ -113,6 +116,7 @@ type WasmApp struct { // keepers accountKeeper auth.AccountKeeper bankKeeper bank.Keeper + messageKeeper message.Keeper supplyKeeper supply.Keeper stakingKeeper staking.Keeper slashingKeeper slashing.Keeper @@ -154,7 +158,7 @@ func NewWasmApp( bApp.SetAppVersion(version.Version) keys := sdk.NewKVStoreKeys( - bam.MainStoreKey, auth.StoreKey, staking.StoreKey, + bam.MainStoreKey, auth.StoreKey, message.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, gov.StoreKey, params.StoreKey, evidence.StoreKey, upgrade.StoreKey, wasm.StoreKey, @@ -174,6 +178,7 @@ func NewWasmApp( app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tKeys[params.TStoreKey]) app.subspaces[auth.ModuleName] = app.paramsKeeper.Subspace(auth.DefaultParamspace) app.subspaces[bank.ModuleName] = app.paramsKeeper.Subspace(bank.DefaultParamspace) + app.subspaces[message.ModuleName] = app.paramsKeeper.Subspace(message.DefaultParamspace) app.subspaces[staking.ModuleName] = app.paramsKeeper.Subspace(staking.DefaultParamspace) app.subspaces[mint.ModuleName] = app.paramsKeeper.Subspace(mint.DefaultParamspace) app.subspaces[distr.ModuleName] = app.paramsKeeper.Subspace(distr.DefaultParamspace) @@ -189,6 +194,9 @@ func NewWasmApp( app.bankKeeper = bank.NewBaseKeeper( app.accountKeeper, app.subspaces[bank.ModuleName], app.ModuleAccountAddrs(), ) + app.messageKeeper = message.NewBaseKeeper( + app.cdc, keys[message.StoreKey], app.subspaces[message.ModuleName], app.accountKeeper, app.ModuleAccountAddrs(), + ) app.supplyKeeper = supply.NewKeeper( app.cdc, keys[supply.StoreKey], app.accountKeeper, app.bankKeeper, maccPerms, ) @@ -255,7 +263,7 @@ func NewWasmApp( // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks supportedFeatures := "staking" - app.wasmKeeper = wasm.NewKeeper(app.cdc, keys[wasm.StoreKey], app.accountKeeper, app.bankKeeper, app.stakingKeeper, wasmRouter, wasmDir, wasmConfig, supportedFeatures, nil, nil) + app.wasmKeeper = wasm.NewKeeper(app.cdc, keys[wasm.StoreKey], app.accountKeeper, app.bankKeeper, app.messageKeeper, app.stakingKeeper, wasmRouter, wasmDir, wasmConfig, supportedFeatures, nil, nil) // NOTE: Any module instantiated in the module manager that is later modified // must be passed by reference here. @@ -263,6 +271,7 @@ func NewWasmApp( genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), auth.NewAppModule(app.accountKeeper), bank.NewAppModule(app.bankKeeper, app.accountKeeper), + message.NewAppModule(app.messageKeeper, app.accountKeeper), crisis.NewAppModule(&app.crisisKeeper), supply.NewAppModule(app.supplyKeeper, app.accountKeeper), gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper), @@ -285,7 +294,7 @@ func NewWasmApp( // NOTE: The genutils module must occur after staking so that pools are // properly initialized with tokens from genesis accounts. app.mm.SetOrderInitGenesis( - distr.ModuleName, staking.ModuleName, auth.ModuleName, bank.ModuleName, + distr.ModuleName, staking.ModuleName, auth.ModuleName, message.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, evidence.ModuleName, wasm.ModuleName, ) @@ -300,6 +309,7 @@ func NewWasmApp( app.sm = module.NewSimulationManager( auth.NewAppModule(app.accountKeeper), bank.NewAppModule(app.bankKeeper, app.accountKeeper), + // message.NewAppModule(app.messageKeeper, app.accountKeeper), supply.NewAppModule(app.supplyKeeper, app.accountKeeper), gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper), mint.NewAppModule(app.mintKeeper), diff --git a/go.mod b/go.mod index 25bfe342a9..56eecb47c2 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/CosmWasm/go-cosmwasm v0.8.0 github.com/btcsuite/btcd v0.0.0-20190807005414-4063feeff79a // indirect github.com/cosmos/cosmos-sdk v0.38.3 + github.com/gogo/protobuf v1.3.1 github.com/golang/mock v1.4.3 // indirect github.com/gorilla/mux v1.7.4 github.com/onsi/ginkgo v1.8.0 // indirect diff --git a/go.sum b/go.sum index 4c484989ec..37cf0bbf20 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,7 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -59,14 +60,22 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btcd v0.0.0-20190807005414-4063feeff79a h1:We35J+0yvVFrEXbtViYUW8H/wNOhqjIF3PsrW4yYmGw= github.com/btcsuite/btcd v0.0.0-20190807005414-4063feeff79a/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd h1:qdGvebPBDuYDPGi1WCPjy1tGyMpmDK8IEapSsszn7HE= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723 h1:ZA/jbKoGcVAnER6pCHPEkGdZOV7U1oLUedErBHCUMs0= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -241,11 +250,13 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89 h1:12K8AlpT0/6QUXSfV0yi4Q0jkbq8NDtIKFtF61AoqV0= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -257,6 +268,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= diff --git a/x/message/alias.go b/x/message/alias.go new file mode 100644 index 0000000000..d83bed6ea0 --- /dev/null +++ b/x/message/alias.go @@ -0,0 +1,65 @@ +package message + +// nolint + +import ( + "github.com/CosmWasm/wasmd/x/message/internal/keeper" + "github.com/CosmWasm/wasmd/x/message/internal/types" +) + +const ( + QueryMessage = keeper.QueryMessage + ModuleName = types.ModuleName + StoreKey = types.StoreKey + QuerierRoute = types.QuerierRoute + RouterKey = types.RouterKey + DefaultParamspace = types.DefaultParamspace + DefaultSendEnabled = types.DefaultSendEnabled + + EventTypeText = types.EventTypeText + AttributeKeyRecipient = types.AttributeKeyRecipient + AttributeKeySender = types.AttributeKeySender + AttributeValueCategory = types.AttributeValueCategory +) + +var ( + // RegisterInvariants = keeper.RegisterInvariants + // NonnegativeBalanceInvariant = keeper.NonnegativeBalanceInvariant + NewBaseKeeper = keeper.NewBaseKeeper + NewBaseMessageKeeper = keeper.NewBaseMessageKeeper + // NewBaseViewKeeper = keeper.NewBaseViewKeeper + NewQuerier = keeper.NewQuerier + RegisterCodec = types.RegisterCodec + // ErrNoInputs = types.ErrNoInputs + // ErrNoOutputs = types.ErrNoOutputs + // ErrInputOutputMismatch = types.ErrInputOutputMismatch + ErrSendDisabled = types.ErrSendDisabled + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + ValidateGenesis = types.ValidateGenesis + NewMsgTextSend = types.NewMsgTextSend + // NewMsgMultiSend = types.NewMsgMultiSend + // NewInput = types.NewInput + // NewOutput = types.NewOutput + // ValidateInputsOutputs = types.ValidateInputsOutputs + ParamKeyTable = types.ParamKeyTable + NewQueryBalanceParams = types.NewQueryBalanceParams + ModuleCdc = types.ModuleCdc + ParamStoreKeySendEnabled = types.ParamStoreKeySendEnabled +) + +type ( + Keeper = keeper.Keeper + BaseKeeper = keeper.BaseKeeper + MessageKeeper = keeper.MessageKeeper + BaseMessageKeeper = keeper.BaseMessageKeeper + // ViewKeeper = keeper.ViewKeeper + // BaseViewKeeper = keeper.BaseViewKeeper + GenesisState = types.GenesisState + MsgTextSend = types.MsgTextSend + MsgTextsSend = types.MsgTextsSend + // MsgMultiSend = types.MsgMultiSend + // Input = types.Input + // Output = types.Output + QueryBalanceParams = types.QueryBalanceParams +) diff --git a/x/message/app_test.go b/x/message/app_test.go new file mode 100644 index 0000000000..5b202cc6aa --- /dev/null +++ b/x/message/app_test.go @@ -0,0 +1,375 @@ +package message_test + +// import ( +// "testing" + +// "github.com/stretchr/testify/require" + +// "github.com/cosmos/cosmos-sdk/x/distribution" +// "github.com/cosmos/cosmos-sdk/x/supply" + +// abci "github.com/tendermint/tendermint/abci/types" +// "github.com/tendermint/tendermint/crypto" +// "github.com/tendermint/tendermint/crypto/secp256k1" + +// "github.com/cosmos/cosmos-sdk/simapp" +// sdk "github.com/cosmos/cosmos-sdk/types" +// "github.com/cosmos/cosmos-sdk/x/auth" +// authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" +// "github.com/cosmos/cosmos-sdk/x/bank/internal/types" +// ) + +// type ( +// expectedBalance struct { +// addr sdk.AccAddress +// coins sdk.Coins +// } + +// appTestCase struct { +// expSimPass bool +// expPass bool +// msgs []sdk.Msg +// accNums []uint64 +// accSeqs []uint64 +// privKeys []crypto.PrivKey +// expectedBalances []expectedBalance +// } +// ) + +// var ( +// priv1 = secp256k1.GenPrivKey() +// addr1 = sdk.AccAddress(priv1.PubKey().Address()) +// priv2 = secp256k1.GenPrivKey() +// addr2 = sdk.AccAddress(priv2.PubKey().Address()) +// addr3 = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) +// priv4 = secp256k1.GenPrivKey() +// addr4 = sdk.AccAddress(priv4.PubKey().Address()) + +// coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} +// halfCoins = sdk.Coins{sdk.NewInt64Coin("foocoin", 5)} + +// sendMsg1 = types.NewMsgSend(addr1, addr2, coins) + +// multiSendMsg1 = types.MsgMultiSend{ +// Inputs: []types.Input{types.NewInput(addr1, coins)}, +// Outputs: []types.Output{types.NewOutput(addr2, coins)}, +// } +// multiSendMsg2 = types.MsgMultiSend{ +// Inputs: []types.Input{types.NewInput(addr1, coins)}, +// Outputs: []types.Output{ +// types.NewOutput(addr2, halfCoins), +// types.NewOutput(addr3, halfCoins), +// }, +// } +// multiSendMsg3 = types.MsgMultiSend{ +// Inputs: []types.Input{ +// types.NewInput(addr1, coins), +// types.NewInput(addr4, coins), +// }, +// Outputs: []types.Output{ +// types.NewOutput(addr2, coins), +// types.NewOutput(addr3, coins), +// }, +// } +// multiSendMsg4 = types.MsgMultiSend{ +// Inputs: []types.Input{ +// types.NewInput(addr2, coins), +// }, +// Outputs: []types.Output{ +// types.NewOutput(addr1, coins), +// }, +// } +// multiSendMsg5 = types.MsgMultiSend{ +// Inputs: []types.Input{ +// types.NewInput(addr1, coins), +// }, +// Outputs: []types.Output{ +// types.NewOutput(moduleAccAddr, coins), +// }, +// } +// ) + +// func TestSendNotEnoughBalance(t *testing.T) { +// acc := &auth.BaseAccount{ +// Address: addr1, +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 67)}, +// } + +// genAccs := []authexported.GenesisAccount{acc} +// app := simapp.SetupWithGenesisAccounts(genAccs) + +// ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) + +// res1 := app.AccountKeeper.GetAccount(ctxCheck, addr1) +// require.NotNil(t, res1) +// require.Equal(t, acc, res1.(*auth.BaseAccount)) + +// origAccNum := res1.GetAccountNumber() +// origSeq := res1.GetSequence() + +// sendMsg := types.NewMsgSend(addr1, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 100)}) +// header := abci.Header{Height: app.LastBlockHeight() + 1} +// simapp.SignCheckDeliver(t, app.Codec(), app.BaseApp, header, []sdk.Msg{sendMsg}, []uint64{origAccNum}, []uint64{origSeq}, false, false, priv1) + +// simapp.CheckBalance(t, app, addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 67)}) + +// res2 := app.AccountKeeper.GetAccount(app.NewContext(true, abci.Header{}), addr1) +// require.NotNil(t, res2) + +// require.Equal(t, res2.GetAccountNumber(), origAccNum) +// require.Equal(t, res2.GetSequence(), origSeq+1) +// } + +// // A module account cannot be the recipient of bank sends unless it has been marked as such +// func TestSendToModuleAcc(t *testing.T) { +// tests := []struct { +// name string +// fromBalance sdk.Coins +// msg types.MsgSend +// expSimPass bool +// expPass bool +// expFromBalance sdk.Coins +// expToBalance sdk.Coins +// }{ +// { +// name: "Normal module account cannot be the recipient of bank sends", +// fromBalance: coins, +// msg: types.NewMsgSend(addr1, moduleAccAddr, coins), +// expSimPass: false, +// expPass: false, +// expFromBalance: coins, +// expToBalance: sdk.NewCoins(), +// }, +// { +// name: "Allowed module account can be the recipient of bank sends", +// fromBalance: coins, +// msg: types.NewMsgSend(addr1, supply.NewModuleAddress(distribution.ModuleName), coins), +// expPass: true, +// expSimPass: true, +// expFromBalance: sdk.NewCoins(), +// expToBalance: coins, +// }, +// } + +// for _, test := range tests { +// test := test +// t.Run(test.name, func(t *testing.T) { +// acc := &auth.BaseAccount{ +// Address: test.msg.FromAddress, +// Coins: test.fromBalance, +// } + +// genAccs := []authexported.GenesisAccount{acc} +// app := simapp.SetupWithGenesisAccounts(genAccs) + +// ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) + +// res1 := app.AccountKeeper.GetAccount(ctxCheck, test.msg.FromAddress) +// require.NotNil(t, res1) +// require.Equal(t, acc, res1.(*auth.BaseAccount)) + +// origAccNum := res1.GetAccountNumber() +// origSeq := res1.GetSequence() + +// header := abci.Header{Height: app.LastBlockHeight() + 1} +// simapp.SignCheckDeliver(t, app.Codec(), app.BaseApp, header, []sdk.Msg{test.msg}, []uint64{origAccNum}, []uint64{origSeq}, test.expSimPass, test.expPass, priv1) + +// simapp.CheckBalance(t, app, test.msg.FromAddress, test.expFromBalance) +// simapp.CheckBalance(t, app, test.msg.ToAddress, test.expToBalance) + +// res2 := app.AccountKeeper.GetAccount(app.NewContext(true, abci.Header{}), addr1) +// require.NotNil(t, res2) + +// require.Equal(t, res2.GetAccountNumber(), origAccNum) +// require.Equal(t, res2.GetSequence(), origSeq+1) +// }) +// } +// } + +// func TestMsgMultiSendWithAccounts(t *testing.T) { +// acc := &auth.BaseAccount{ +// Address: addr1, +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 67)}, +// } + +// genAccs := []authexported.GenesisAccount{acc} +// app := simapp.SetupWithGenesisAccounts(genAccs) + +// ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) + +// res1 := app.AccountKeeper.GetAccount(ctxCheck, addr1) +// require.NotNil(t, res1) +// require.Equal(t, acc, res1.(*auth.BaseAccount)) + +// testCases := []appTestCase{ +// { +// msgs: []sdk.Msg{multiSendMsg1}, +// accNums: []uint64{0}, +// accSeqs: []uint64{0}, +// expSimPass: true, +// expPass: true, +// privKeys: []crypto.PrivKey{priv1}, +// expectedBalances: []expectedBalance{ +// {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 57)}}, +// {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, +// }, +// }, +// { +// msgs: []sdk.Msg{multiSendMsg1, multiSendMsg2}, +// accNums: []uint64{0}, +// accSeqs: []uint64{0}, +// expSimPass: true, // doesn't check signature +// expPass: false, +// privKeys: []crypto.PrivKey{priv1}, +// }, +// { +// msgs: []sdk.Msg{multiSendMsg5}, +// accNums: []uint64{0}, +// accSeqs: []uint64{0}, +// expSimPass: false, +// expPass: false, +// privKeys: []crypto.PrivKey{priv1}, +// }, +// } + +// for _, tc := range testCases { +// header := abci.Header{Height: app.LastBlockHeight() + 1} +// simapp.SignCheckDeliver(t, app.Codec(), app.BaseApp, header, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + +// for _, eb := range tc.expectedBalances { +// simapp.CheckBalance(t, app, eb.addr, eb.coins) +// } +// } +// } + +// func TestMsgMultiSendMultipleOut(t *testing.T) { + +// acc1 := &auth.BaseAccount{ +// Address: addr1, +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}, +// } +// acc2 := &auth.BaseAccount{ +// Address: addr2, +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}, +// } + +// genAccs := []authexported.GenesisAccount{acc1, acc2} +// app := simapp.SetupWithGenesisAccounts(genAccs) + +// testCases := []appTestCase{ +// { +// msgs: []sdk.Msg{multiSendMsg2}, +// accNums: []uint64{0}, +// accSeqs: []uint64{0}, +// expSimPass: true, +// expPass: true, +// privKeys: []crypto.PrivKey{priv1}, +// expectedBalances: []expectedBalance{ +// {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, +// {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 47)}}, +// {addr3, sdk.Coins{sdk.NewInt64Coin("foocoin", 5)}}, +// }, +// }, +// } + +// for _, tc := range testCases { +// header := abci.Header{Height: app.LastBlockHeight() + 1} +// simapp.SignCheckDeliver(t, app.Codec(), app.BaseApp, header, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + +// for _, eb := range tc.expectedBalances { +// simapp.CheckBalance(t, app, eb.addr, eb.coins) +// } +// } +// } + +// func TestMsgMultiSendMultipleInOut(t *testing.T) { + +// acc1 := &auth.BaseAccount{ +// Address: addr1, +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}, +// } +// acc2 := &auth.BaseAccount{ +// Address: addr2, +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}, +// } +// acc4 := &auth.BaseAccount{ +// Address: addr4, +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}, +// } + +// genAccs := []authexported.GenesisAccount{acc1, acc2, acc4} +// app := simapp.SetupWithGenesisAccounts(genAccs) + +// testCases := []appTestCase{ +// { +// msgs: []sdk.Msg{multiSendMsg3}, +// accNums: []uint64{0, 2}, +// accSeqs: []uint64{0, 0}, +// expSimPass: true, +// expPass: true, +// privKeys: []crypto.PrivKey{priv1, priv4}, +// expectedBalances: []expectedBalance{ +// {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, +// {addr4, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, +// {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 52)}}, +// {addr3, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, +// }, +// }, +// } + +// for _, tc := range testCases { +// header := abci.Header{Height: app.LastBlockHeight() + 1} +// simapp.SignCheckDeliver(t, app.Codec(), app.BaseApp, header, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + +// for _, eb := range tc.expectedBalances { +// simapp.CheckBalance(t, app, eb.addr, eb.coins) +// } +// } +// } + +// func TestMsgMultiSendDependent(t *testing.T) { +// acc1 := auth.NewBaseAccountWithAddress(addr1) +// acc2 := auth.NewBaseAccountWithAddress(addr2) +// err := acc1.SetCoins(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42))) +// require.NoError(t, err) +// err = acc2.SetAccountNumber(1) +// require.NoError(t, err) + +// genAccs := []authexported.GenesisAccount{&acc1, &acc2} +// app := simapp.SetupWithGenesisAccounts(genAccs) + +// testCases := []appTestCase{ +// { +// msgs: []sdk.Msg{multiSendMsg1}, +// accNums: []uint64{0}, +// accSeqs: []uint64{0}, +// expSimPass: true, +// expPass: true, +// privKeys: []crypto.PrivKey{priv1}, +// expectedBalances: []expectedBalance{ +// {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, +// {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, +// }, +// }, +// { +// msgs: []sdk.Msg{multiSendMsg4}, +// accNums: []uint64{1}, +// accSeqs: []uint64{0}, +// expSimPass: true, +// expPass: true, +// privKeys: []crypto.PrivKey{priv2}, +// expectedBalances: []expectedBalance{ +// {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}}, +// }, +// }, +// } + +// for _, tc := range testCases { +// header := abci.Header{Height: app.LastBlockHeight() + 1} +// simapp.SignCheckDeliver(t, app.Codec(), app.BaseApp, header, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + +// for _, eb := range tc.expectedBalances { +// simapp.CheckBalance(t, app, eb.addr, eb.coins) +// } +// } +// } diff --git a/x/message/bench_test.go b/x/message/bench_test.go new file mode 100644 index 0000000000..19f42c9c3d --- /dev/null +++ b/x/message/bench_test.go @@ -0,0 +1,76 @@ +package message_test + +// import ( +// "testing" + +// abci "github.com/tendermint/tendermint/abci/types" + +// "github.com/cosmos/cosmos-sdk/simapp" +// sdk "github.com/cosmos/cosmos-sdk/types" +// "github.com/cosmos/cosmos-sdk/x/auth" +// authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" +// "github.com/cosmos/cosmos-sdk/x/staking" +// "github.com/cosmos/cosmos-sdk/x/supply" +// ) + +// var moduleAccAddr = supply.NewModuleAddress(staking.BondedPoolName) + +// func BenchmarkOneBankSendTxPerBlock(b *testing.B) { +// // Add an account at genesis +// acc := auth.BaseAccount{ +// Address: addr1, +// // Some value conceivably higher than the benchmarks would ever go +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 100000000000)}, +// } + +// // Construct genesis state +// genAccs := []authexported.GenesisAccount{&acc} +// benchmarkApp := simapp.SetupWithGenesisAccounts(genAccs) + +// // Precompute all txs +// txs := simapp.GenSequenceOfTxs([]sdk.Msg{sendMsg1}, []uint64{0}, []uint64{uint64(0)}, b.N, priv1) +// b.ResetTimer() +// // Run this with a profiler, so its easy to distinguish what time comes from +// // Committing, and what time comes from Check/Deliver Tx. +// for i := 0; i < b.N; i++ { +// benchmarkApp.BeginBlock(abci.RequestBeginBlock{}) +// _, _, err := benchmarkApp.Check(txs[i]) +// if err != nil { +// panic("something is broken in checking transaction") +// } + +// benchmarkApp.Deliver(txs[i]) +// benchmarkApp.EndBlock(abci.RequestEndBlock{}) +// benchmarkApp.Commit() +// } +// } + +// func BenchmarkOneBankMultiSendTxPerBlock(b *testing.B) { +// // Add an account at genesis +// acc := auth.BaseAccount{ +// Address: addr1, +// // Some value conceivably higher than the benchmarks would ever go +// Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 100000000000)}, +// } + +// // Construct genesis state +// genAccs := []authexported.GenesisAccount{&acc} +// benchmarkApp := simapp.SetupWithGenesisAccounts(genAccs) + +// // Precompute all txs +// txs := simapp.GenSequenceOfTxs([]sdk.Msg{multiSendMsg1}, []uint64{0}, []uint64{uint64(0)}, b.N, priv1) +// b.ResetTimer() +// // Run this with a profiler, so its easy to distinguish what time comes from +// // Committing, and what time comes from Check/Deliver Tx. +// for i := 0; i < b.N; i++ { +// benchmarkApp.BeginBlock(abci.RequestBeginBlock{}) +// _, _, err := benchmarkApp.Check(txs[i]) +// if err != nil { +// panic("something is broken in checking transaction") +// } + +// benchmarkApp.Deliver(txs[i]) +// benchmarkApp.EndBlock(abci.RequestEndBlock{}) +// benchmarkApp.Commit() +// } +// } diff --git a/x/message/client/cli/query.go b/x/message/client/cli/query.go new file mode 100644 index 0000000000..aa3b840095 --- /dev/null +++ b/x/message/client/cli/query.go @@ -0,0 +1,67 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/CosmWasm/wasmd/x/message/internal/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + flagEvents = "events" + + eventFormat = "{eventType}.{eventAttribute}={value}" +) + +// GetQueryCmd returns the transaction commands for this module +func GetQueryCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the message module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand(GetMessagesCmd(cdc)) + + return cmd +} + +// GetAccountCmd returns a query account that will display the state of the +// account at a given address. +func GetMessagesCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "messages [address]", + Short: "Query account messages", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + // accGetter := authtypes.NewQueryBalanceParams(cliCtx) + + key, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + bs, err := types.ModuleCdc.MarshalJSON(types.NewQueryBalanceParams(key)) + if err != nil { + return err + } + msg, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.StoreKey, types.QuerierRoute), bs) + + var message types.MsgTextsSend + + err = cliCtx.Codec.UnmarshalJSON(msg, &message) + + return cliCtx.PrintOutput(message) + }, + } + + return flags.GetCommands(cmd)[0] +} diff --git a/x/message/client/cli/tx.go b/x/message/client/cli/tx.go new file mode 100644 index 0000000000..33d369a491 --- /dev/null +++ b/x/message/client/cli/tx.go @@ -0,0 +1,61 @@ +package cli + +import ( + "bufio" + + "github.com/spf13/cobra" + + // "github.com/CosmWasm/wasmd/x/message/client/utils" + "github.com/CosmWasm/wasmd/x/message/internal/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd(cdc *codec.Codec) *cobra.Command { + txCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Message Module subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + txCmd.AddCommand( + SendTextCmd(cdc), + ) + return txCmd +} + +// SendTxCmd will create a send tx and sign it with the given key. +func SendTextCmd(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "send [from_key_or_address] [to_address] [text]", + Short: "Create and sign a send tx", + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContextWithInputAndFrom(inBuf, args[0]).WithCodec(cdc) + + to, err := sdk.AccAddressFromBech32(args[1]) + if err != nil { + return err + } + + text := args[2] + + // build and sign the transaction, then broadcast to Tendermint + msg := types.NewMsgTextSend(cliCtx.GetFromAddress(), to, text) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } + + cmd = flags.PostCommands(cmd)[0] + + return cmd +} diff --git a/x/message/client/rest/query.go b/x/message/client/rest/query.go new file mode 100644 index 0000000000..6081aa9900 --- /dev/null +++ b/x/message/client/rest/query.go @@ -0,0 +1,51 @@ +package rest + +import ( + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" +) + +// query accountREST Handler +func QueryBalancesRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + // vars := mux.Vars(r) + // bech32addr := vars["address"] + + // addr, err := sdk.AccAddressFromBech32(bech32addr) + // if err != nil { + // rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + // return + // } + + // cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + // if !ok { + // return + // } + + // params := types.NewQueryBalanceParams(addr) + // bz, err := cliCtx.Codec.MarshalJSON(params) + // if err != nil { + // rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + // return + // } + + // res, height, err := cliCtx.QueryWithData("custom/bank/balances", bz) + // if err != nil { + // rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + // return + // } + + // cliCtx = cliCtx.WithHeight(height) + + // // the query will return empty if there is no data for this account + // if len(res) == 0 { + // rest.PostProcessResponse(w, cliCtx, sdk.Coins{}) + // return + // } + + // rest.PostProcessResponse(w, cliCtx, res) + return + } +} diff --git a/x/message/client/rest/tx.go b/x/message/client/rest/tx.go new file mode 100644 index 0000000000..f0b4ab3b63 --- /dev/null +++ b/x/message/client/rest/tx.go @@ -0,0 +1,57 @@ +package rest + +import ( + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" +) + +// RegisterRoutes - Central function to define routes that get registered by the main application +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc("/bank/accounts/{address}/transfers", SendRequestHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc("/bank/balances/{address}", QueryBalancesRequestHandlerFn(cliCtx)).Methods("GET") +} + +// SendReq defines the properties of a send request's body. +type SendReq struct { + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + Amount sdk.Coins `json:"amount" yaml:"amount"` +} + +// SendRequestHandlerFn - http request handler to send coins to a address. +func SendRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // vars := mux.Vars(r) + // bech32Addr := vars["address"] + + // toAddr, err := sdk.AccAddressFromBech32(bech32Addr) + // if err != nil { + // rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + // return + // } + + // var req SendReq + // if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + // return + // } + + // req.BaseReq = req.BaseReq.Sanitize() + // if !req.BaseReq.ValidateBasic(w) { + // return + // } + + // fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) + // if err != nil { + // rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + // return + // } + + // msg := types.NewMsgSend(fromAddr, toAddr, req.Amount) + // utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + return + } +} diff --git a/x/message/genesis.go b/x/message/genesis.go new file mode 100644 index 0000000000..14cab8fefc --- /dev/null +++ b/x/message/genesis.go @@ -0,0 +1,15 @@ +package message + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis sets distribution information for genesis. +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { + keeper.SetSendEnabled(ctx, data.SendEnabled) +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + return NewGenesisState(keeper.GetSendEnabled(ctx)) +} diff --git a/x/message/handler.go b/x/message/handler.go new file mode 100644 index 0000000000..33c1a75045 --- /dev/null +++ b/x/message/handler.go @@ -0,0 +1,79 @@ +package message + +import ( + "github.com/CosmWasm/wasmd/x/message/internal/keeper" + "github.com/CosmWasm/wasmd/x/message/internal/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// NewHandler returns a handler for "bank" type messages. +func NewHandler(k keeper.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { + case types.MsgTextSend: + return handleMsgTextSend(ctx, k, msg) + + // case types.MsgMultiSend: + // return handleMsgMultiSend(ctx, k, msg) + + default: + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized bank message type: %T", msg) + } + } +} + +// Handle MsgSend. +func handleMsgTextSend(ctx sdk.Context, k keeper.Keeper, msg types.MsgTextSend) (*sdk.Result, error) { + if !k.GetSendEnabled(ctx) { + return nil, types.ErrSendDisabled + } + + if k.BlacklistedAddr(msg.ToAddress) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive transactions", msg.ToAddress) + } + + err := k.SendTextMessage(ctx, msg.FromAddress, msg.ToAddress, msg.Text) + if err != nil { + return nil, err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + ) + + return &sdk.Result{Events: ctx.EventManager().Events()}, nil +} + +// // Handle MsgMultiSend. +// func handleMsgMultiSend(ctx sdk.Context, k keeper.Keeper, msg types.MsgMultiSend) (*sdk.Result, error) { +// // NOTE: totalIn == totalOut should already have been checked +// if !k.GetSendEnabled(ctx) { +// return nil, types.ErrSendDisabled +// } + +// for _, out := range msg.Outputs { +// if k.BlacklistedAddr(out.Address) { +// return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive transactions", out.Address) +// } +// } + +// err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) +// if err != nil { +// return nil, err +// } + +// ctx.EventManager().EmitEvent( +// sdk.NewEvent( +// sdk.EventTypeMessage, +// sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), +// ), +// ) + +// return &sdk.Result{Events: ctx.EventManager().Events()}, nil +// } diff --git a/x/message/handler_test.go b/x/message/handler_test.go new file mode 100644 index 0000000000..34a96c4004 --- /dev/null +++ b/x/message/handler_test.go @@ -0,0 +1,23 @@ +package message + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +func TestInvalidMsg(t *testing.T) { + h := NewHandler(nil) + + res, err := h(sdk.NewContext(nil, abci.Header{}, false, nil), sdk.NewTestMsg()) + require.Error(t, err) + require.Nil(t, res) + + _, _, log := sdkerrors.ABCIInfo(err, false) + require.True(t, strings.Contains(log, "unrecognized bank message type")) +} diff --git a/x/message/internal/keeper/integration_test.go b/x/message/internal/keeper/integration_test.go new file mode 100644 index 0000000000..b55569d4a4 --- /dev/null +++ b/x/message/internal/keeper/integration_test.go @@ -0,0 +1 @@ +package keeper diff --git a/x/message/internal/keeper/invariants.go b/x/message/internal/keeper/invariants.go new file mode 100644 index 0000000000..b55569d4a4 --- /dev/null +++ b/x/message/internal/keeper/invariants.go @@ -0,0 +1 @@ +package keeper diff --git a/x/message/internal/keeper/keeper.go b/x/message/internal/keeper/keeper.go new file mode 100644 index 0000000000..df0999cb6d --- /dev/null +++ b/x/message/internal/keeper/keeper.go @@ -0,0 +1,184 @@ +package keeper + +import ( + "github.com/CosmWasm/wasmd/x/message/internal/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/params" +) + +var _ Keeper = (*BaseKeeper)(nil) + +// Keeper defines a module interface that facilitates the transfer of coins +// between accounts. +type Keeper interface { + MessageKeeper +} + +// BaseKeeper manages transfers between accounts. It implements the Keeper interface. +type BaseKeeper struct { + BaseMessageKeeper + ak types.AccountKeeper + paramSpace params.Subspace +} + +// NewBaseKeeper returns a new BaseKeeper +func NewBaseKeeper( + cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, ak types.AccountKeeper, blacklistedAddrs map[string]bool, +) BaseKeeper { + + ps := paramSpace.WithKeyTable(types.ParamKeyTable()) + return BaseKeeper{ + BaseMessageKeeper: NewBaseMessageKeeper(cdc, key, ps, ak, blacklistedAddrs), + ak: ak, + paramSpace: ps, + } +} + +// SendKeeper defines a module interface that facilitates the transfer of coins +// between accounts without the possibility of creating coins. +type MessageKeeper interface { + SendTextMessage(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, text string) error + + SetMessage(ctx sdk.Context, addr sdk.AccAddress, toAddr sdk.AccAddress, text string) error + GetMessages(ctx sdk.Context, addr sdk.AccAddress) types.MsgTextsSend + GetSendEnabled(ctx sdk.Context) bool + SetSendEnabled(ctx sdk.Context, enabled bool) + BlacklistedAddr(addr sdk.AccAddress) bool +} + +var _ MessageKeeper = (*BaseMessageKeeper)(nil) + +// BaseSendKeeper only allows transfers between accounts without the possibility of +// creating coins. It implements the SendKeeper interface. +type BaseMessageKeeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + ak types.AccountKeeper + paramSpace params.Subspace + + // list of addresses that are restricted from receiving transactions + blacklistedAddrs map[string]bool +} + +// NewBaseSendKeeper returns a new BaseSendKeeper. +func NewBaseMessageKeeper( + cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, + ak types.AccountKeeper, blacklistedAddrs map[string]bool, +) BaseMessageKeeper { + + return BaseMessageKeeper{ + storeKey: key, + cdc: cdc, + ak: ak, + paramSpace: paramSpace, + blacklistedAddrs: blacklistedAddrs, + } +} + +// SendCoins moves coins from one account to another +func (keeper BaseMessageKeeper) SendTextMessage(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, text string) error { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeText, + sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()), + sdk.NewAttribute("Text", text), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(types.AttributeKeySender, fromAddr.String()), + ), + }) + + err := keeper.SetMessage(ctx, fromAddr, toAddr, text) + if err != nil { + return err + } + + return nil +} + +//GetMessages gets messages sent by user +func (keeper BaseMessageKeeper) GetMessages(ctx sdk.Context, addr sdk.AccAddress) types.MsgTextsSend { + kvstore := ctx.KVStore(keeper.storeKey) + + existingMsgs := kvstore.Get([]byte(addr)) + var messages types.MsgTextsSend + err := keeper.cdc.UnmarshalJSON(existingMsgs, &messages) + if err != nil { + messages = types.MsgTextsSend{} + } + return messages +} + +// SetCoins sets the coins at the addr. +func (keeper BaseMessageKeeper) SetMessage(ctx sdk.Context, addr sdk.AccAddress, toAddr sdk.AccAddress, text string) error { + if text == "" { + sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, text) + } + //for from addr + acc := keeper.ak.GetAccount(ctx, addr) + if acc == nil { + acc = keeper.ak.NewAccountWithAddress(ctx, addr) + } + + keeper.ak.SetAccount(ctx, acc) + + kvstore := ctx.KVStore(keeper.storeKey) + + existingMsgs := kvstore.Get([]byte(addr)) + var messages types.MsgTextsSend + err := keeper.cdc.UnmarshalJSON(existingMsgs, &messages) + if err != nil { + messages = types.MsgTextsSend{} + } + + addmsg, err := keeper.cdc.MarshalJSON(types.MsgTextsSend{append(messages.MsgTexts, types.NewMsgTextSend(addr, toAddr, text))}) + if err != nil { + return err + } + + kvstore.Set([]byte(addr), addmsg) + + //for to addr + acc = keeper.ak.GetAccount(ctx, toAddr) + if acc == nil { + acc = keeper.ak.NewAccountWithAddress(ctx, toAddr) + } + keeper.ak.SetAccount(ctx, acc) + + existingMsgs = kvstore.Get([]byte(toAddr)) + var messagesto types.MsgTextsSend + err = keeper.cdc.UnmarshalJSON(existingMsgs, &messagesto) + if err != nil { + messages = types.MsgTextsSend{} + } + + addmsg, err = keeper.cdc.MarshalJSON(types.MsgTextsSend{append(messagesto.MsgTexts, types.NewMsgTextSend(addr, toAddr, text))}) + if err != nil { + return err + } + + kvstore.Set([]byte(toAddr), addmsg) + + return nil +} + +// GetSendEnabled returns the current SendEnabled +func (keeper BaseMessageKeeper) GetSendEnabled(ctx sdk.Context) bool { + var enabled bool + keeper.paramSpace.Get(ctx, types.ParamStoreKeySendEnabled, &enabled) + return enabled +} + +// SetSendEnabled sets the send enabled +func (keeper BaseMessageKeeper) SetSendEnabled(ctx sdk.Context, enabled bool) { + keeper.paramSpace.Set(ctx, types.ParamStoreKeySendEnabled, &enabled) +} + +// BlacklistedAddr checks if a given address is blacklisted (i.e restricted from +// receiving funds) +func (keeper BaseMessageKeeper) BlacklistedAddr(addr sdk.AccAddress) bool { + return keeper.blacklistedAddrs[addr.String()] +} diff --git a/x/message/internal/keeper/keeper_test.go b/x/message/internal/keeper/keeper_test.go new file mode 100644 index 0000000000..415c9445c5 --- /dev/null +++ b/x/message/internal/keeper/keeper_test.go @@ -0,0 +1,545 @@ +package keeper_test + +// import ( +// "testing" +// "time" + +// "github.com/stretchr/testify/require" +// abci "github.com/tendermint/tendermint/abci/types" +// tmkv "github.com/tendermint/tendermint/libs/kv" +// tmtime "github.com/tendermint/tendermint/types/time" + +// "github.com/cosmos/cosmos-sdk/simapp" +// sdk "github.com/cosmos/cosmos-sdk/types" +// "github.com/cosmos/cosmos-sdk/x/auth" +// "github.com/cosmos/cosmos-sdk/x/auth/vesting" +// keep "github.com/cosmos/cosmos-sdk/x/bank/internal/keeper" +// "github.com/cosmos/cosmos-sdk/x/bank/internal/types" +// "github.com/cosmos/cosmos-sdk/x/supply" +// ) + +// func TestKeeper(t *testing.T) { +// app, ctx := createTestApp(false) + +// addr := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) +// addr3 := sdk.AccAddress([]byte("addr3")) +// acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr) + +// // Test GetCoins/SetCoins +// app.AccountKeeper.SetAccount(ctx, acc) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins())) + +// app.BankKeeper.SetCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) + +// // Test HasCoins +// require.True(t, app.BankKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) +// require.True(t, app.BankKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5)))) +// require.False(t, app.BankKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 15)))) +// require.False(t, app.BankKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 5)))) + +// // Test AddCoins +// app.BankKeeper.AddCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 15))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 25)))) + +// app.BankKeeper.AddCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 15))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 15), sdk.NewInt64Coin("foocoin", 25)))) + +// // Test SubtractCoins +// app.BankKeeper.SubtractCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10))) +// app.BankKeeper.SubtractCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 5))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 15)))) + +// app.BankKeeper.SubtractCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 11))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 15)))) + +// app.BankKeeper.SubtractCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 10))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 15)))) +// require.False(t, app.BankKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 1)))) + +// // Test SendCoins +// app.BankKeeper.SendCoins(ctx, addr, addr2, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5)))) + +// app.BankKeeper.SendCoins(ctx, addr, addr2, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5)))) + +// app.BankKeeper.AddCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 30))) +// app.BankKeeper.SendCoins(ctx, addr, addr2, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 5))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 5)))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 10)))) + +// // Test InputOutputCoins +// input1 := types.NewInput(addr2, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 2))) +// output1 := types.NewOutput(addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 2))) +// app.BankKeeper.InputOutputCoins(ctx, []types.Input{input1}, []types.Output{output1}) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 7)))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 8)))) + +// inputs := []types.Input{ +// types.NewInput(addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 3))), +// types.NewInput(addr2, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 3), sdk.NewInt64Coin("foocoin", 2))), +// } + +// outputs := []types.Output{ +// types.NewOutput(addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 1))), +// types.NewOutput(addr3, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5))), +// } +// app.BankKeeper.InputOutputCoins(ctx, inputs, outputs) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 21), sdk.NewInt64Coin("foocoin", 4)))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 7), sdk.NewInt64Coin("foocoin", 6)))) +// require.True(t, app.BankKeeper.GetCoins(ctx, addr3).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5)))) + +// // Test retrieving black listed accounts +// for acc := range simapp.GetMaccPerms() { +// addr := supply.NewModuleAddress(acc) +// require.Equal(t, app.BlacklistedAccAddrs()[addr.String()], app.BankKeeper.BlacklistedAddr(addr)) +// } +// } + +// func TestSendKeeper(t *testing.T) { +// app, ctx := createTestApp(false) + +// blacklistedAddrs := make(map[string]bool) + +// paramSpace := app.ParamsKeeper.Subspace("newspace") +// sendKeeper := keep.NewBaseSendKeeper(app.AccountKeeper, paramSpace, blacklistedAddrs) +// app.BankKeeper.SetSendEnabled(ctx, true) + +// addr := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) +// acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr) + +// // Test GetCoins/SetCoins +// app.AccountKeeper.SetAccount(ctx, acc) +// require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins())) + +// app.BankKeeper.SetCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10))) +// require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) + +// // Test HasCoins +// require.True(t, sendKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) +// require.True(t, sendKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5)))) +// require.False(t, sendKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 15)))) +// require.False(t, sendKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 5)))) + +// app.BankKeeper.SetCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 15))) + +// // Test SendCoins +// sendKeeper.SendCoins(ctx, addr, addr2, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5))) +// require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) +// require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5)))) + +// sendKeeper.SendCoins(ctx, addr, addr2, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50))) +// require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) +// require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5)))) + +// app.BankKeeper.AddCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 30))) +// sendKeeper.SendCoins(ctx, addr, addr2, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 5))) +// require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 5)))) +// require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 10)))) + +// // validate coins with invalid denoms or negative values cannot be sent +// // NOTE: We must use the Coin literal as the constructor does not allow +// // negative values. +// err := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.Coin{Denom: "FOOCOIN", Amount: sdk.NewInt(-5)}}) +// require.Error(t, err) +// } + +// func TestMsgSendEvents(t *testing.T) { +// app, ctx := createTestApp(false) + +// app.BankKeeper.SetSendEnabled(ctx, true) + +// addr := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) +// acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr) + +// app.AccountKeeper.SetAccount(ctx, acc) +// newCoins := sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50)) +// err := app.BankKeeper.SendCoins(ctx, addr, addr2, newCoins) +// require.Error(t, err) +// events := ctx.EventManager().Events() +// require.Equal(t, 2, len(events)) +// event1 := sdk.Event{ +// Type: types.EventTypeTransfer, +// Attributes: []tmkv.Pair{}, +// } +// event1.Attributes = append( +// event1.Attributes, +// tmkv.Pair{Key: []byte(types.AttributeKeyRecipient), Value: []byte(addr2.String())}) +// event1.Attributes = append( +// event1.Attributes, +// tmkv.Pair{Key: []byte(sdk.AttributeKeyAmount), Value: []byte(newCoins.String())}) +// event2 := sdk.Event{ +// Type: sdk.EventTypeMessage, +// Attributes: []tmkv.Pair{}, +// } +// event2.Attributes = append( +// event2.Attributes, +// tmkv.Pair{Key: []byte(types.AttributeKeySender), Value: []byte(addr.String())}) +// require.Equal(t, event1, events[0]) +// require.Equal(t, event2, events[1]) + +// app.BankKeeper.SetCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50))) +// newCoins = sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50)) +// err = app.BankKeeper.SendCoins(ctx, addr, addr2, newCoins) +// require.NoError(t, err) +// events = ctx.EventManager().Events() +// require.Equal(t, 4, len(events)) +// require.Equal(t, event1, events[2]) +// require.Equal(t, event2, events[3]) +// } + +// func TestMsgMultiSendEvents(t *testing.T) { +// app, ctx := createTestApp(false) + +// app.BankKeeper.SetSendEnabled(ctx, true) + +// addr := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) +// addr3 := sdk.AccAddress([]byte("addr3")) +// addr4 := sdk.AccAddress([]byte("addr4")) +// acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr) +// acc2 := app.AccountKeeper.NewAccountWithAddress(ctx, addr2) + +// app.AccountKeeper.SetAccount(ctx, acc) +// app.AccountKeeper.SetAccount(ctx, acc2) +// newCoins := sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50)) +// newCoins2 := sdk.NewCoins(sdk.NewInt64Coin("barcoin", 100)) +// inputs := []types.Input{ +// {Address: addr, Coins: newCoins}, +// {Address: addr2, Coins: newCoins2}, +// } +// outputs := []types.Output{ +// {Address: addr3, Coins: newCoins}, +// {Address: addr4, Coins: newCoins2}, +// } +// err := app.BankKeeper.InputOutputCoins(ctx, inputs, outputs) +// require.Error(t, err) +// events := ctx.EventManager().Events() +// require.Equal(t, 0, len(events)) + +// // Set addr's coins but not addr2's coins +// app.BankKeeper.SetCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50))) + +// err = app.BankKeeper.InputOutputCoins(ctx, inputs, outputs) +// require.Error(t, err) +// events = ctx.EventManager().Events() +// require.Equal(t, 1, len(events)) +// event1 := sdk.Event{ +// Type: sdk.EventTypeMessage, +// Attributes: []tmkv.Pair{}, +// } +// event1.Attributes = append( +// event1.Attributes, +// tmkv.Pair{Key: []byte(types.AttributeKeySender), Value: []byte(addr.String())}) +// require.Equal(t, event1, events[0]) + +// // Set addr's coins and addr2's coins +// app.BankKeeper.SetCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50))) +// newCoins = sdk.NewCoins(sdk.NewInt64Coin("foocoin", 50)) +// app.BankKeeper.SetCoins(ctx, addr2, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 100))) +// newCoins2 = sdk.NewCoins(sdk.NewInt64Coin("barcoin", 100)) + +// err = app.BankKeeper.InputOutputCoins(ctx, inputs, outputs) +// require.NoError(t, err) +// events = ctx.EventManager().Events() +// require.Equal(t, 5, len(events)) +// event2 := sdk.Event{ +// Type: sdk.EventTypeMessage, +// Attributes: []tmkv.Pair{}, +// } +// event2.Attributes = append( +// event2.Attributes, +// tmkv.Pair{Key: []byte(types.AttributeKeySender), Value: []byte(addr2.String())}) +// event3 := sdk.Event{ +// Type: types.EventTypeTransfer, +// Attributes: []tmkv.Pair{}, +// } +// event3.Attributes = append( +// event3.Attributes, +// tmkv.Pair{Key: []byte(types.AttributeKeyRecipient), Value: []byte(addr3.String())}) +// event3.Attributes = append( +// event3.Attributes, +// tmkv.Pair{Key: []byte(sdk.AttributeKeyAmount), Value: []byte(newCoins.String())}) +// event4 := sdk.Event{ +// Type: types.EventTypeTransfer, +// Attributes: []tmkv.Pair{}, +// } +// event4.Attributes = append( +// event4.Attributes, +// tmkv.Pair{Key: []byte(types.AttributeKeyRecipient), Value: []byte(addr4.String())}) +// event4.Attributes = append( +// event4.Attributes, +// tmkv.Pair{Key: []byte(sdk.AttributeKeyAmount), Value: []byte(newCoins2.String())}) +// require.Equal(t, event1, events[1]) +// require.Equal(t, event2, events[2]) +// require.Equal(t, event3, events[3]) +// require.Equal(t, event4, events[4]) +// } + +// func TestViewKeeper(t *testing.T) { +// app, ctx := createTestApp(false) + +// //paramSpace := app.ParamsKeeper.Subspace(types.DefaultParamspace) +// viewKeeper := keep.NewBaseViewKeeper(app.AccountKeeper) + +// addr := sdk.AccAddress([]byte("addr1")) +// acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr) + +// // Test GetCoins/SetCoins +// app.AccountKeeper.SetAccount(ctx, acc) +// require.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins())) + +// app.BankKeeper.SetCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10))) +// require.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) + +// // Test HasCoins +// require.True(t, viewKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 10)))) +// require.True(t, viewKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 5)))) +// require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 15)))) +// require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.NewCoins(sdk.NewInt64Coin("barcoin", 5)))) +// } + +// func TestVestingAccountSend(t *testing.T) { +// app, ctx := createTestApp(false) +// now := tmtime.Now() +// ctx = ctx.WithBlockHeader(abci.Header{Time: now}) +// endTime := now.Add(24 * time.Hour) + +// origCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) +// sendCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 50)) + +// addr1 := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) +// bacc := auth.NewBaseAccountWithAddress(addr1) +// bacc.SetCoins(origCoins) +// vacc := vesting.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) +// app.AccountKeeper.SetAccount(ctx, vacc) + +// // require that no coins be sendable at the beginning of the vesting schedule +// err := app.BankKeeper.SendCoins(ctx, addr1, addr2, sendCoins) +// require.Error(t, err) + +// // receive some coins +// vacc.SetCoins(origCoins.Add(sendCoins...)) +// app.AccountKeeper.SetAccount(ctx, vacc) + +// // require that all vested coins are spendable plus any received +// ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) +// err = app.BankKeeper.SendCoins(ctx, addr1, addr2, sendCoins) +// vacc = app.AccountKeeper.GetAccount(ctx, addr1).(*vesting.ContinuousVestingAccount) +// require.NoError(t, err) +// require.Equal(t, origCoins, vacc.GetCoins()) +// } + +// func TestPeriodicVestingAccountSend(t *testing.T) { +// app, ctx := createTestApp(false) +// now := tmtime.Now() +// ctx = ctx.WithBlockHeader(abci.Header{Time: now}) +// origCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) +// sendCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 50)) + +// addr1 := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) +// bacc := auth.NewBaseAccountWithAddress(addr1) +// bacc.SetCoins(origCoins) +// periods := vesting.Periods{ +// vesting.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin("stake", 50)}}, +// vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin("stake", 25)}}, +// vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin("stake", 25)}}, +// } +// vacc := vesting.NewPeriodicVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), periods) +// app.AccountKeeper.SetAccount(ctx, vacc) + +// // require that no coins be sendable at the beginning of the vesting schedule +// err := app.BankKeeper.SendCoins(ctx, addr1, addr2, sendCoins) +// require.Error(t, err) + +// // receive some coins +// vacc.SetCoins(origCoins.Add(sendCoins...)) +// app.AccountKeeper.SetAccount(ctx, vacc) + +// // require that all vested coins are spendable plus any received +// ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) +// err = app.BankKeeper.SendCoins(ctx, addr1, addr2, sendCoins) +// vacc = app.AccountKeeper.GetAccount(ctx, addr1).(*vesting.PeriodicVestingAccount) +// require.NoError(t, err) +// require.Equal(t, origCoins, vacc.GetCoins()) +// } + +// func TestVestingAccountReceive(t *testing.T) { +// app, ctx := createTestApp(false) +// now := tmtime.Now() +// ctx = ctx.WithBlockHeader(abci.Header{Time: now}) +// endTime := now.Add(24 * time.Hour) + +// origCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) +// sendCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 50)) + +// addr1 := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) + +// bacc := auth.NewBaseAccountWithAddress(addr1) +// bacc.SetCoins(origCoins) +// vacc := vesting.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) +// acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr2) +// app.AccountKeeper.SetAccount(ctx, vacc) +// app.AccountKeeper.SetAccount(ctx, acc) +// app.BankKeeper.SetCoins(ctx, addr2, origCoins) + +// // send some coins to the vesting account +// app.BankKeeper.SendCoins(ctx, addr2, addr1, sendCoins) + +// // require the coins are spendable +// vacc = app.AccountKeeper.GetAccount(ctx, addr1).(*vesting.ContinuousVestingAccount) +// require.Equal(t, origCoins.Add(sendCoins...), vacc.GetCoins()) +// require.Equal(t, vacc.SpendableCoins(now), sendCoins) + +// // require coins are spendable plus any that have vested +// require.Equal(t, vacc.SpendableCoins(now.Add(12*time.Hour)), origCoins) +// } + +// func TestPeriodicVestingAccountReceive(t *testing.T) { +// app, ctx := createTestApp(false) +// now := tmtime.Now() +// ctx = ctx.WithBlockHeader(abci.Header{Time: now}) + +// origCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) +// sendCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 50)) + +// addr1 := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) + +// bacc := auth.NewBaseAccountWithAddress(addr1) +// bacc.SetCoins(origCoins) +// periods := vesting.Periods{ +// vesting.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin("stake", 50)}}, +// vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin("stake", 25)}}, +// vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin("stake", 25)}}, +// } +// vacc := vesting.NewPeriodicVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), periods) +// acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr2) +// app.AccountKeeper.SetAccount(ctx, vacc) +// app.AccountKeeper.SetAccount(ctx, acc) +// app.BankKeeper.SetCoins(ctx, addr2, origCoins) + +// // send some coins to the vesting account +// app.BankKeeper.SendCoins(ctx, addr2, addr1, sendCoins) + +// // require the coins are spendable +// vacc = app.AccountKeeper.GetAccount(ctx, addr1).(*vesting.PeriodicVestingAccount) +// require.Equal(t, origCoins.Add(sendCoins...), vacc.GetCoins()) +// require.Equal(t, vacc.SpendableCoins(now), sendCoins) + +// // require coins are spendable plus any that have vested +// require.Equal(t, vacc.SpendableCoins(now.Add(12*time.Hour)), origCoins) +// } + +// func TestDelegateCoins(t *testing.T) { +// app, ctx := createTestApp(false) +// now := tmtime.Now() +// ctx = ctx.WithBlockHeader(abci.Header{Time: now}) +// endTime := now.Add(24 * time.Hour) +// ak := app.AccountKeeper + +// origCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) +// delCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 50)) + +// addr1 := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) +// addrModule := sdk.AccAddress([]byte("moduleAcc")) + +// bacc := auth.NewBaseAccountWithAddress(addr1) +// bacc.SetCoins(origCoins) +// macc := ak.NewAccountWithAddress(ctx, addrModule) // we don't need to define an actual module account bc we just need the address for testing +// vacc := vesting.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) +// acc := ak.NewAccountWithAddress(ctx, addr2) +// ak.SetAccount(ctx, vacc) +// ak.SetAccount(ctx, acc) +// ak.SetAccount(ctx, macc) +// app.BankKeeper.SetCoins(ctx, addr2, origCoins) + +// ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) + +// // require the ability for a non-vesting account to delegate +// err := app.BankKeeper.DelegateCoins(ctx, addr2, addrModule, delCoins) +// acc = ak.GetAccount(ctx, addr2) +// macc = ak.GetAccount(ctx, addrModule) +// require.NoError(t, err) +// require.Equal(t, origCoins.Sub(delCoins), acc.GetCoins()) +// require.Equal(t, delCoins, macc.GetCoins()) + +// // require the ability for a vesting account to delegate +// err = app.BankKeeper.DelegateCoins(ctx, addr1, addrModule, delCoins) +// vacc = ak.GetAccount(ctx, addr1).(*vesting.ContinuousVestingAccount) +// require.NoError(t, err) +// require.Equal(t, delCoins, vacc.GetCoins()) +// } + +// func TestUndelegateCoins(t *testing.T) { +// app, ctx := createTestApp(false) +// now := tmtime.Now() +// ctx = ctx.WithBlockHeader(abci.Header{Time: now}) +// endTime := now.Add(24 * time.Hour) +// ak := app.AccountKeeper + +// origCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) +// delCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 50)) + +// addr1 := sdk.AccAddress([]byte("addr1")) +// addr2 := sdk.AccAddress([]byte("addr2")) +// addrModule := sdk.AccAddress([]byte("moduleAcc")) + +// bacc := auth.NewBaseAccountWithAddress(addr1) +// bacc.SetCoins(origCoins) +// macc := ak.NewAccountWithAddress(ctx, addrModule) // we don't need to define an actual module account bc we just need the address for testing +// vacc := vesting.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) +// acc := ak.NewAccountWithAddress(ctx, addr2) +// ak.SetAccount(ctx, vacc) +// ak.SetAccount(ctx, acc) +// ak.SetAccount(ctx, macc) +// app.BankKeeper.SetCoins(ctx, addr2, origCoins) + +// ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) + +// // require the ability for a non-vesting account to delegate +// err := app.BankKeeper.DelegateCoins(ctx, addr2, addrModule, delCoins) +// require.NoError(t, err) + +// acc = ak.GetAccount(ctx, addr2) +// macc = ak.GetAccount(ctx, addrModule) +// require.Equal(t, origCoins.Sub(delCoins), acc.GetCoins()) +// require.Equal(t, delCoins, macc.GetCoins()) + +// // require the ability for a non-vesting account to undelegate +// err = app.BankKeeper.UndelegateCoins(ctx, addrModule, addr2, delCoins) +// require.NoError(t, err) + +// acc = ak.GetAccount(ctx, addr2) +// macc = ak.GetAccount(ctx, addrModule) +// require.Equal(t, origCoins, acc.GetCoins()) +// require.True(t, macc.GetCoins().Empty()) + +// // require the ability for a vesting account to delegate +// err = app.BankKeeper.DelegateCoins(ctx, addr1, addrModule, delCoins) +// require.NoError(t, err) + +// vacc = ak.GetAccount(ctx, addr1).(*vesting.ContinuousVestingAccount) +// macc = ak.GetAccount(ctx, addrModule) +// require.Equal(t, origCoins.Sub(delCoins), vacc.GetCoins()) +// require.Equal(t, delCoins, macc.GetCoins()) + +// // require the ability for a vesting account to undelegate +// err = app.BankKeeper.UndelegateCoins(ctx, addrModule, addr1, delCoins) +// require.NoError(t, err) + +// vacc = ak.GetAccount(ctx, addr1).(*vesting.ContinuousVestingAccount) +// macc = ak.GetAccount(ctx, addrModule) +// require.Equal(t, origCoins, vacc.GetCoins()) +// require.True(t, macc.GetCoins().Empty()) +// } diff --git a/x/message/internal/keeper/querier.go b/x/message/internal/keeper/querier.go new file mode 100644 index 0000000000..e29721ab74 --- /dev/null +++ b/x/message/internal/keeper/querier.go @@ -0,0 +1,45 @@ +package keeper + +import ( + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/CosmWasm/wasmd/x/message/internal/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + // query balance path + QueryMessage = "message" +) + +// NewQuerier returns a new sdk.Keeper instance. +func NewQuerier(k Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { + switch path[0] { + case QueryMessage: + return queryMessages(ctx, req, k) + + default: + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown query path: %s", path[0]) + } + } +} + +// queryBalance fetch an account's balance for the supplied height. +// Height and account address are passed as first and second path components respectively. +func queryMessages(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { + var params types.QueryBalanceParams + if err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms); err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + + coins := k.GetMessages(ctx, params.Address) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, coins) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + + return bz, nil +} diff --git a/x/message/internal/keeper/querier_test.go b/x/message/internal/keeper/querier_test.go new file mode 100644 index 0000000000..b8146c2eae --- /dev/null +++ b/x/message/internal/keeper/querier_test.go @@ -0,0 +1,60 @@ +package keeper_test + +// import ( +// "fmt" +// "testing" + +// "github.com/stretchr/testify/require" + +// abci "github.com/tendermint/tendermint/abci/types" + +// sdk "github.com/cosmos/cosmos-sdk/types" +// authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +// keep "github.com/cosmos/cosmos-sdk/x/bank/internal/keeper" +// "github.com/cosmos/cosmos-sdk/x/bank/internal/types" +// ) + +// func TestBalances(t *testing.T) { +// app, ctx := createTestApp(false) +// req := abci.RequestQuery{ +// Path: fmt.Sprintf("custom/bank/%s", keep.QueryBalance), +// Data: []byte{}, +// } + +// querier := keep.NewQuerier(app.BankKeeper) + +// res, err := querier(ctx, []string{"balances"}, req) +// require.NotNil(t, err) +// require.Nil(t, res) + +// _, _, addr := authtypes.KeyTestPubAddr() +// req.Data = app.Codec().MustMarshalJSON(types.NewQueryBalanceParams(addr)) +// res, err = querier(ctx, []string{"balances"}, req) +// require.Nil(t, err) // the account does not exist, no error returned anyway +// require.NotNil(t, res) + +// var coins sdk.Coins +// require.NoError(t, app.Codec().UnmarshalJSON(res, &coins)) +// require.True(t, coins.IsZero()) + +// acc := app.AccountKeeper.NewAccountWithAddress(ctx, addr) +// acc.SetCoins(sdk.NewCoins(sdk.NewInt64Coin("foo", 10))) +// app.AccountKeeper.SetAccount(ctx, acc) +// res, err = querier(ctx, []string{"balances"}, req) +// require.Nil(t, err) +// require.NotNil(t, res) +// require.NoError(t, app.Codec().UnmarshalJSON(res, &coins)) +// require.True(t, coins.AmountOf("foo").Equal(sdk.NewInt(10))) +// } + +// func TestQuerierRouteNotFound(t *testing.T) { +// app, ctx := createTestApp(false) +// req := abci.RequestQuery{ +// Path: "custom/bank/notfound", +// Data: []byte{}, +// } + +// querier := keep.NewQuerier(app.BankKeeper) +// _, err := querier(ctx, []string{"notfound"}, req) +// require.Error(t, err) +// } diff --git a/x/message/internal/types/codec.go b/x/message/internal/types/codec.go new file mode 100644 index 0000000000..0941f4e14c --- /dev/null +++ b/x/message/internal/types/codec.go @@ -0,0 +1,20 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgTextSend{}, "cosmos-sdk/MsgTextSend", nil) + cdc.RegisterConcrete(MsgTextsSend{}, "cosmos-sdk/MsgTextsSend", nil) + // cdc.RegisterConcrete(MsgMultiSend{}, "cosmos-sdk/MsgMultiSend", nil) +} + +var ModuleCdc *codec.Codec + +func init() { + ModuleCdc = codec.New() + RegisterCodec(ModuleCdc) + ModuleCdc.Seal() +} diff --git a/x/message/internal/types/errors.go b/x/message/internal/types/errors.go new file mode 100644 index 0000000000..157408c941 --- /dev/null +++ b/x/message/internal/types/errors.go @@ -0,0 +1,13 @@ +package types + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// x/bank module sentinel errors +var ( + ErrNoInputs = sdkerrors.Register(ModuleName, 1, "no inputs to send transaction") + ErrNoOutputs = sdkerrors.Register(ModuleName, 2, "no outputs to send transaction") + ErrInputOutputMismatch = sdkerrors.Register(ModuleName, 3, "sum inputs != sum outputs") + ErrSendDisabled = sdkerrors.Register(ModuleName, 4, "send transactions are disabled") +) diff --git a/x/message/internal/types/events.go b/x/message/internal/types/events.go new file mode 100644 index 0000000000..2479384c5a --- /dev/null +++ b/x/message/internal/types/events.go @@ -0,0 +1,11 @@ +package types + +// bank module event types +const ( + EventTypeText = "text" + + AttributeKeyRecipient = "recipient" + AttributeKeySender = "sender" + + AttributeValueCategory = ModuleName +) diff --git a/x/message/internal/types/expected_keepers.go b/x/message/internal/types/expected_keepers.go new file mode 100644 index 0000000000..23e06d4549 --- /dev/null +++ b/x/message/internal/types/expected_keepers.go @@ -0,0 +1,18 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/exported" +) + +// AccountKeeper defines the account contract that must be fulfilled when +// creating a x/bank keeper. +type AccountKeeper interface { + NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) exported.Account + + GetAccount(ctx sdk.Context, addr sdk.AccAddress) exported.Account + GetAllAccounts(ctx sdk.Context) []exported.Account + SetAccount(ctx sdk.Context, acc exported.Account) + + IterateAccounts(ctx sdk.Context, process func(exported.Account) bool) +} diff --git a/x/message/internal/types/genesis.go b/x/message/internal/types/genesis.go new file mode 100644 index 0000000000..159b04f1f9 --- /dev/null +++ b/x/message/internal/types/genesis.go @@ -0,0 +1,18 @@ +package types + +// GenesisState is the bank state that must be provided at genesis. +type GenesisState struct { + SendEnabled bool `json:"send_enabled" yaml:"send_enabled"` +} + +// NewGenesisState creates a new genesis state. +func NewGenesisState(sendEnabled bool) GenesisState { + return GenesisState{SendEnabled: sendEnabled} +} + +// DefaultGenesisState returns a default genesis state +func DefaultGenesisState() GenesisState { return NewGenesisState(true) } + +// ValidateGenesis performs basic validation of bank genesis data returning an +// error for any failed validation criteria. +func ValidateGenesis(data GenesisState) error { return nil } diff --git a/x/message/internal/types/key.go b/x/message/internal/types/key.go new file mode 100644 index 0000000000..dbf59da313 --- /dev/null +++ b/x/message/internal/types/key.go @@ -0,0 +1,12 @@ +package types + +const ( + // module name + ModuleName = "message" + // StoreKey is the string store representation + StoreKey = ModuleName + + // TStoreKey is the string transient store representation + TStoreKey = "transient_" + ModuleName + QuerierRoute = ModuleName +) diff --git a/x/message/internal/types/msgs.go b/x/message/internal/types/msgs.go new file mode 100644 index 0000000000..52e82ca4b1 --- /dev/null +++ b/x/message/internal/types/msgs.go @@ -0,0 +1,189 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// RouterKey is they name of the bank module +const RouterKey = ModuleName + +// MsgTextSend - high level transaction of the coin module +type MsgTextSend struct { + FromAddress sdk.AccAddress `json:"from_address" yaml:"from_address"` + ToAddress sdk.AccAddress `json:"to_address" yaml:"to_address"` + Text string `json:"text" yaml:"text"` +} + +type MsgTextsSend struct { + MsgTexts []MsgTextSend +} + +var _ sdk.Msg = MsgTextSend{} + +// NewMsgTextSend- construct arbitrary multi-in, multi-out send msg. +func NewMsgTextSend(fromAddr, toAddr sdk.AccAddress, text string) MsgTextSend { + return MsgTextSend{FromAddress: fromAddr, ToAddress: toAddr, Text: text} +} + +// Route Implements Msg. +func (msg MsgTextSend) Route() string { return RouterKey } + +// Type Implements Msg. +func (msg MsgTextSend) Type() string { return "textSend" } + +// ValidateBasic Implements Msg. +func (msg MsgTextSend) ValidateBasic() error { + if msg.FromAddress.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing sender address") + } + if msg.ToAddress.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing recipient address") + } + if msg.Text == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, msg.Text) + } + return nil +} + +// GetSignBytes Implements Msg. +func (msg MsgTextSend) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +// GetSigners Implements Msg. +func (msg MsgTextSend) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.FromAddress} +} + +// // MsgMultiSend - high level transaction of the coin module +// type MsgMultiSend struct { +// Inputs []Input `json:"inputs" yaml:"inputs"` +// Outputs []Output `json:"outputs" yaml:"outputs"` +// } + +// var _ sdk.Msg = MsgMultiSend{} + +// // NewMsgMultiSend - construct arbitrary multi-in, multi-out send msg. +// func NewMsgMultiSend(in []Input, out []Output) MsgMultiSend { +// return MsgMultiSend{Inputs: in, Outputs: out} +// } + +// // Route Implements Msg +// func (msg MsgMultiSend) Route() string { return RouterKey } + +// // Type Implements Msg +// func (msg MsgMultiSend) Type() string { return "multisend" } + +// // ValidateBasic Implements Msg. +// func (msg MsgMultiSend) ValidateBasic() error { +// // this just makes sure all the inputs and outputs are properly formatted, +// // not that they actually have the money inside +// if len(msg.Inputs) == 0 { +// return ErrNoInputs +// } +// if len(msg.Outputs) == 0 { +// return ErrNoOutputs +// } + +// return ValidateInputsOutputs(msg.Inputs, msg.Outputs) +// } + +// // GetSignBytes Implements Msg. +// func (msg MsgMultiSend) GetSignBytes() []byte { +// return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +// } + +// // GetSigners Implements Msg. +// func (msg MsgMultiSend) GetSigners() []sdk.AccAddress { +// addrs := make([]sdk.AccAddress, len(msg.Inputs)) +// for i, in := range msg.Inputs { +// addrs[i] = in.Address +// } +// return addrs +// } + +// // Input models transaction input +// type Input struct { +// Address sdk.AccAddress `json:"address" yaml:"address"` +// Coins sdk.Coins `json:"coins" yaml:"coins"` +// } + +// // ValidateBasic - validate transaction input +// func (in Input) ValidateBasic() error { +// if len(in.Address) == 0 { +// return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "input address missing") +// } +// if !in.Coins.IsValid() { +// return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, in.Coins.String()) +// } +// if !in.Coins.IsAllPositive() { +// return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, in.Coins.String()) +// } +// return nil +// } + +// // NewInput - create a transaction input, used with MsgMultiSend +// func NewInput(addr sdk.AccAddress, coins sdk.Coins) Input { +// return Input{ +// Address: addr, +// Coins: coins, +// } +// } + +// // Output models transaction outputs +// type Output struct { +// Address sdk.AccAddress `json:"address" yaml:"address"` +// Coins sdk.Coins `json:"coins" yaml:"coins"` +// } + +// // ValidateBasic - validate transaction output +// func (out Output) ValidateBasic() error { +// if len(out.Address) == 0 { +// return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "output address missing") +// } +// if !out.Coins.IsValid() { +// return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, out.Coins.String()) +// } +// if !out.Coins.IsAllPositive() { +// return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, out.Coins.String()) +// } +// return nil +// } + +// // NewOutput - create a transaction output, used with MsgMultiSend +// func NewOutput(addr sdk.AccAddress, coins sdk.Coins) Output { +// return Output{ +// Address: addr, +// Coins: coins, +// } +// } + +// // ValidateInputsOutputs validates that each respective input and output is +// // valid and that the sum of inputs is equal to the sum of outputs. +// func ValidateInputsOutputs(inputs []Input, outputs []Output) error { +// var totalIn, totalOut sdk.Coins + +// for _, in := range inputs { +// if err := in.ValidateBasic(); err != nil { +// return err +// } + +// totalIn = totalIn.Add(in.Coins...) +// } + +// for _, out := range outputs { +// if err := out.ValidateBasic(); err != nil { +// return err +// } + +// totalOut = totalOut.Add(out.Coins...) +// } + +// // make sure inputs and outputs match +// if !totalIn.IsEqual(totalOut) { +// return ErrInputOutputMismatch +// } + +// return nil +// } diff --git a/x/message/internal/types/msgs_test.go b/x/message/internal/types/msgs_test.go new file mode 100644 index 0000000000..cca3593fb2 --- /dev/null +++ b/x/message/internal/types/msgs_test.go @@ -0,0 +1,262 @@ +package types + +// import ( +// "fmt" +// "testing" + +// "github.com/stretchr/testify/require" + +// sdk "github.com/cosmos/cosmos-sdk/types" +// ) + +// func TestMsgSendRoute(t *testing.T) { +// addr1 := sdk.AccAddress([]byte("from")) +// addr2 := sdk.AccAddress([]byte("to")) +// coins := sdk.NewCoins(sdk.NewInt64Coin("atom", 10)) +// var msg = NewMsgSend(addr1, addr2, coins) + +// require.Equal(t, msg.Route(), RouterKey) +// require.Equal(t, msg.Type(), "send") +// } + +// func TestMsgSendValidation(t *testing.T) { +// addr1 := sdk.AccAddress([]byte("from")) +// addr2 := sdk.AccAddress([]byte("to")) +// atom123 := sdk.NewCoins(sdk.NewInt64Coin("atom", 123)) +// atom0 := sdk.NewCoins(sdk.NewInt64Coin("atom", 0)) +// atom123eth123 := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 123)) +// atom123eth0 := sdk.Coins{sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 0)} + +// var emptyAddr sdk.AccAddress + +// cases := []struct { +// valid bool +// tx MsgSend +// }{ +// {true, NewMsgSend(addr1, addr2, atom123)}, // valid send +// {true, NewMsgSend(addr1, addr2, atom123eth123)}, // valid send with multiple coins +// {false, NewMsgSend(addr1, addr2, atom0)}, // non positive coin +// {false, NewMsgSend(addr1, addr2, atom123eth0)}, // non positive coin in multicoins +// {false, NewMsgSend(emptyAddr, addr2, atom123)}, // empty from addr +// {false, NewMsgSend(addr1, emptyAddr, atom123)}, // empty to addr +// } + +// for _, tc := range cases { +// err := tc.tx.ValidateBasic() +// if tc.valid { +// require.Nil(t, err) +// } else { +// require.NotNil(t, err) +// } +// } +// } + +// func TestMsgSendGetSignBytes(t *testing.T) { +// addr1 := sdk.AccAddress([]byte("input")) +// addr2 := sdk.AccAddress([]byte("output")) +// coins := sdk.NewCoins(sdk.NewInt64Coin("atom", 10)) +// var msg = NewMsgSend(addr1, addr2, coins) +// res := msg.GetSignBytes() + +// expected := `{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"10","denom":"atom"}],"from_address":"cosmos1d9h8qat57ljhcm","to_address":"cosmos1da6hgur4wsmpnjyg"}}` +// require.Equal(t, expected, string(res)) +// } + +// func TestMsgSendGetSigners(t *testing.T) { +// var msg = NewMsgSend(sdk.AccAddress([]byte("input1")), sdk.AccAddress{}, sdk.NewCoins()) +// res := msg.GetSigners() +// // TODO: fix this ! +// require.Equal(t, fmt.Sprintf("%v", res), "[696E70757431]") +// } + +// func TestMsgMultiSendRoute(t *testing.T) { +// // Construct a MsgSend +// addr1 := sdk.AccAddress([]byte("input")) +// addr2 := sdk.AccAddress([]byte("output")) +// coins := sdk.NewCoins(sdk.NewInt64Coin("atom", 10)) +// var msg = MsgMultiSend{ +// Inputs: []Input{NewInput(addr1, coins)}, +// Outputs: []Output{NewOutput(addr2, coins)}, +// } + +// // TODO some failures for bad result +// require.Equal(t, msg.Route(), RouterKey) +// require.Equal(t, msg.Type(), "multisend") +// } + +// func TestInputValidation(t *testing.T) { +// addr1 := sdk.AccAddress([]byte{1, 2}) +// addr2 := sdk.AccAddress([]byte{7, 8}) +// someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123)) +// multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20)) + +// var emptyAddr sdk.AccAddress +// emptyCoins := sdk.NewCoins() +// emptyCoins2 := sdk.NewCoins(sdk.NewInt64Coin("eth", 0)) +// someEmptyCoins := sdk.Coins{sdk.NewInt64Coin("eth", 10), sdk.NewInt64Coin("atom", 0)} +// unsortedCoins := sdk.Coins{sdk.NewInt64Coin("eth", 1), sdk.NewInt64Coin("atom", 1)} + +// cases := []struct { +// valid bool +// txIn Input +// }{ +// // auth works with different apps +// {true, NewInput(addr1, someCoins)}, +// {true, NewInput(addr2, someCoins)}, +// {true, NewInput(addr2, multiCoins)}, + +// {false, NewInput(emptyAddr, someCoins)}, // empty address +// {false, NewInput(addr1, emptyCoins)}, // invalid coins +// {false, NewInput(addr1, emptyCoins2)}, // invalid coins +// {false, NewInput(addr1, someEmptyCoins)}, // invalid coins +// {false, NewInput(addr1, unsortedCoins)}, // unsorted coins +// } + +// for i, tc := range cases { +// err := tc.txIn.ValidateBasic() +// if tc.valid { +// require.Nil(t, err, "%d: %+v", i, err) +// } else { +// require.NotNil(t, err, "%d", i) +// } +// } +// } + +// func TestOutputValidation(t *testing.T) { +// addr1 := sdk.AccAddress([]byte{1, 2}) +// addr2 := sdk.AccAddress([]byte{7, 8}) +// someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123)) +// multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20)) + +// var emptyAddr sdk.AccAddress +// emptyCoins := sdk.NewCoins() +// emptyCoins2 := sdk.NewCoins(sdk.NewInt64Coin("eth", 0)) +// someEmptyCoins := sdk.Coins{sdk.NewInt64Coin("eth", 10), sdk.NewInt64Coin("atom", 0)} +// unsortedCoins := sdk.Coins{sdk.NewInt64Coin("eth", 1), sdk.NewInt64Coin("atom", 1)} + +// cases := []struct { +// valid bool +// txOut Output +// }{ +// // auth works with different apps +// {true, NewOutput(addr1, someCoins)}, +// {true, NewOutput(addr2, someCoins)}, +// {true, NewOutput(addr2, multiCoins)}, + +// {false, NewOutput(emptyAddr, someCoins)}, // empty address +// {false, NewOutput(addr1, emptyCoins)}, // invalid coins +// {false, NewOutput(addr1, emptyCoins2)}, // invalid coins +// {false, NewOutput(addr1, someEmptyCoins)}, // invalid coins +// {false, NewOutput(addr1, unsortedCoins)}, // unsorted coins +// } + +// for i, tc := range cases { +// err := tc.txOut.ValidateBasic() +// if tc.valid { +// require.Nil(t, err, "%d: %+v", i, err) +// } else { +// require.NotNil(t, err, "%d", i) +// } +// } +// } + +// func TestMsgMultiSendValidation(t *testing.T) { +// addr1 := sdk.AccAddress([]byte{1, 2}) +// addr2 := sdk.AccAddress([]byte{7, 8}) +// atom123 := sdk.NewCoins(sdk.NewInt64Coin("atom", 123)) +// atom124 := sdk.NewCoins(sdk.NewInt64Coin("atom", 124)) +// eth123 := sdk.NewCoins(sdk.NewInt64Coin("eth", 123)) +// atom123eth123 := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 123)) + +// input1 := NewInput(addr1, atom123) +// input2 := NewInput(addr1, eth123) +// output1 := NewOutput(addr2, atom123) +// output2 := NewOutput(addr2, atom124) +// outputMulti := NewOutput(addr2, atom123eth123) + +// var emptyAddr sdk.AccAddress + +// cases := []struct { +// valid bool +// tx MsgMultiSend +// }{ +// {false, MsgMultiSend{}}, // no input or output +// {false, MsgMultiSend{Inputs: []Input{input1}}}, // just input +// {false, MsgMultiSend{Outputs: []Output{output1}}}, // just output +// {false, MsgMultiSend{ +// Inputs: []Input{NewInput(emptyAddr, atom123)}, // invalid input +// Outputs: []Output{output1}}}, +// {false, MsgMultiSend{ +// Inputs: []Input{input1}, +// Outputs: []Output{{emptyAddr, atom123}}}, // invalid output +// }, +// {false, MsgMultiSend{ +// Inputs: []Input{input1}, +// Outputs: []Output{output2}}, // amounts dont match +// }, +// {true, MsgMultiSend{ +// Inputs: []Input{input1}, +// Outputs: []Output{output1}}, +// }, +// {true, MsgMultiSend{ +// Inputs: []Input{input1, input2}, +// Outputs: []Output{outputMulti}}, +// }, +// } + +// for i, tc := range cases { +// err := tc.tx.ValidateBasic() +// if tc.valid { +// require.Nil(t, err, "%d: %+v", i, err) +// } else { +// require.NotNil(t, err, "%d", i) +// } +// } +// } + +// func TestMsgMultiSendGetSignBytes(t *testing.T) { +// addr1 := sdk.AccAddress([]byte("input")) +// addr2 := sdk.AccAddress([]byte("output")) +// coins := sdk.NewCoins(sdk.NewInt64Coin("atom", 10)) +// var msg = MsgMultiSend{ +// Inputs: []Input{NewInput(addr1, coins)}, +// Outputs: []Output{NewOutput(addr2, coins)}, +// } +// res := msg.GetSignBytes() + +// expected := `{"type":"cosmos-sdk/MsgMultiSend","value":{"inputs":[{"address":"cosmos1d9h8qat57ljhcm","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"cosmos1da6hgur4wsmpnjyg","coins":[{"amount":"10","denom":"atom"}]}]}}` +// require.Equal(t, expected, string(res)) +// } + +// func TestMsgMultiSendGetSigners(t *testing.T) { +// var msg = MsgMultiSend{ +// Inputs: []Input{ +// NewInput(sdk.AccAddress([]byte("input1")), nil), +// NewInput(sdk.AccAddress([]byte("input2")), nil), +// NewInput(sdk.AccAddress([]byte("input3")), nil), +// }, +// } +// res := msg.GetSigners() +// // TODO: fix this ! +// require.Equal(t, fmt.Sprintf("%v", res), "[696E70757431 696E70757432 696E70757433]") +// } + +// /* +// // what to do w/ this test? +// func TestMsgSendSigners(t *testing.T) { +// signers := []sdk.AccAddress{ +// {1, 2, 3}, +// {4, 5, 6}, +// {7, 8, 9}, +// } + +// someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123)) +// inputs := make([]Input, len(signers)) +// for i, signer := range signers { +// inputs[i] = NewInput(signer, someCoins) +// } +// tx := NewMsgSend(inputs, nil) + +// require.Equal(t, signers, tx.Signers()) +// } +// */ diff --git a/x/message/internal/types/params.go b/x/message/internal/types/params.go new file mode 100644 index 0000000000..cdd6aff84f --- /dev/null +++ b/x/message/internal/types/params.go @@ -0,0 +1,33 @@ +package types + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/x/params" +) + +const ( + // DefaultParamspace for params keeper + DefaultParamspace = ModuleName + // DefaultSendEnabled enabled + DefaultSendEnabled = true +) + +// ParamStoreKeySendEnabled is store's key for SendEnabled +var ParamStoreKeySendEnabled = []byte("sendenabled") + +// ParamKeyTable type declaration for parameters +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable( + params.NewParamSetPair(ParamStoreKeySendEnabled, false, validateSendEnabled), + ) +} + +func validateSendEnabled(i interface{}) error { + _, ok := i.(bool) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return nil +} diff --git a/x/message/internal/types/querier.go b/x/message/internal/types/querier.go new file mode 100644 index 0000000000..ef0a8576b8 --- /dev/null +++ b/x/message/internal/types/querier.go @@ -0,0 +1,15 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// QueryBalanceParams defines the params for querying an account balance. +type QueryBalanceParams struct { + Address sdk.AccAddress +} + +// NewQueryBalanceParams creates a new instance of QueryBalanceParams. +func NewQueryBalanceParams(addr sdk.AccAddress) QueryBalanceParams { + return QueryBalanceParams{Address: addr} +} diff --git a/x/message/module.go b/x/message/module.go new file mode 100644 index 0000000000..606bc4cf95 --- /dev/null +++ b/x/message/module.go @@ -0,0 +1,157 @@ +package message + +import ( + "encoding/json" + "fmt" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/CosmWasm/wasmd/x/message/client/cli" + "github.com/CosmWasm/wasmd/x/message/client/rest" + "github.com/CosmWasm/wasmd/x/message/internal/keeper" + "github.com/CosmWasm/wasmd/x/message/internal/types" + + // "github.com/CosmWasm/wasmd/x/message/simulation" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // _ module.AppModuleSimulation = AppModule{} +) + +// AppModuleBasic defines the basic application module used by the bank module. +type AppModuleBasic struct{} + +// Name returns the bank module's name. +func (AppModuleBasic) Name() string { return ModuleName } + +// RegisterCodec registers the bank module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { RegisterCodec(cdc) } + +// DefaultGenesis returns default genesis state as raw bytes for the bank +// module. +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the bank module. +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data GenesisState + if err := ModuleCdc.UnmarshalJSON(bz, &data); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", ModuleName, err) + } + + return ValidateGenesis(data) +} + +// RegisterRESTRoutes registers the REST routes for the bank module. +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + rest.RegisterRoutes(ctx, rtr) +} + +// GetTxCmd returns the root tx command for the bank module. +func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetTxCmd(cdc) +} + +// GetQueryCmd returns no root query command for the bank module. +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { return cli.GetQueryCmd(cdc) } + +// AppModule implements an application module for the bank module. +type AppModule struct { + AppModuleBasic + + keeper Keeper + accountKeeper types.AccountKeeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper Keeper, accountKeeper types.AccountKeeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: keeper, + accountKeeper: accountKeeper, + } +} + +// Name returns the bank module's name. +func (AppModule) Name() string { return ModuleName } + +// RegisterInvariants registers the bank module invariants. +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {} + +// Route returns the message routing key for the bank module. +func (AppModule) Route() string { return RouterKey } + +// NewHandler returns an sdk.Handler for the bank module. +func (am AppModule) NewHandler() sdk.Handler { return NewHandler(am.keeper) } + +// QuerierRoute returns the bank module's querier route name. +func (AppModule) QuerierRoute() string { return RouterKey } + +// NewQuerierHandler returns the bank module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return keeper.NewQuerier(am.keeper) +} + +// InitGenesis performs genesis initialization for the bank module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState GenesisState + ModuleCdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the bank +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return ModuleCdc.MustMarshalJSON(gs) +} + +// BeginBlock performs a no-op. +func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock returns the end blocker for the bank module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +//____________________________________________________________________________ + +// // AppModuleSimulation functions + +// // GenerateGenesisState creates a randomized GenState of the bank module. +// func (AppModule) GenerateGenesisState(simState *module.SimulationState) { +// simulation.RandomizedGenState(simState) +// } + +// // ProposalContents doesn't return any content functions for governance proposals. +// func (AppModule) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent { +// return nil +// } + +// // RandomizedParams creates randomized bank param changes for the simulator. +// func (AppModule) RandomizedParams(r *rand.Rand) []sim.ParamChange { +// return simulation.ParamChanges(r) +// } + +// // RegisterStoreDecoder performs a no-op. +// func (AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} + +// // WeightedOperations returns the all the gov module operations with their respective weights. +// func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { +// return simulation.WeightedOperations( +// simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper, +// ) +// } diff --git a/x/message/simulation/genesis.go b/x/message/simulation/genesis.go new file mode 100644 index 0000000000..0dcabcbab3 --- /dev/null +++ b/x/message/simulation/genesis.go @@ -0,0 +1,36 @@ +package simulation + +// // DONTCOVER + +// import ( +// "fmt" +// "math/rand" + +// "github.com/cosmos/cosmos-sdk/codec" +// "github.com/cosmos/cosmos-sdk/types/module" +// "github.com/cosmos/cosmos-sdk/x/bank/internal/types" +// ) + +// // Simulation parameter constants +// const ( +// SendEnabled = "send_enabled" +// ) + +// // GenSendEnabled randomized SendEnabled +// func GenSendEnabled(r *rand.Rand) bool { +// return r.Int63n(101) <= 95 // 95% chance of transfers being enabled +// } + +// // RandomizedGenState generates a random GenesisState for bank +// func RandomizedGenState(simState *module.SimulationState) { +// var sendEnabled bool +// simState.AppParams.GetOrGenerate( +// simState.Cdc, SendEnabled, &sendEnabled, simState.Rand, +// func(r *rand.Rand) { sendEnabled = GenSendEnabled(r) }, +// ) + +// bankGenesis := types.NewGenesisState(sendEnabled) + +// fmt.Printf("Selected randomly generated bank parameters:\n%s\n", codec.MustMarshalJSONIndent(simState.Cdc, bankGenesis)) +// simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(bankGenesis) +// } diff --git a/x/message/simulation/operations.go b/x/message/simulation/operations.go new file mode 100644 index 0000000000..ae7765d7f9 --- /dev/null +++ b/x/message/simulation/operations.go @@ -0,0 +1,296 @@ +package simulation + +// import ( +// "math/rand" + +// "github.com/tendermint/tendermint/crypto" + +// "github.com/cosmos/cosmos-sdk/baseapp" +// "github.com/cosmos/cosmos-sdk/codec" +// "github.com/cosmos/cosmos-sdk/simapp/helpers" +// simappparams "github.com/cosmos/cosmos-sdk/simapp/params" +// sdk "github.com/cosmos/cosmos-sdk/types" +// "github.com/cosmos/cosmos-sdk/x/bank/internal/keeper" +// "github.com/cosmos/cosmos-sdk/x/bank/internal/types" +// "github.com/cosmos/cosmos-sdk/x/simulation" +// ) + +// // Simulation operation weights constants +// const ( +// OpWeightMsgSend = "op_weight_msg_send" +// OpWeightMsgMultiSend = "op_weight_msg_multisend" +// ) + +// // WeightedOperations returns all the operations from the module with their respective weights +// func WeightedOperations(appParams simulation.AppParams, cdc *codec.Codec, ak types.AccountKeeper, +// bk keeper.Keeper) simulation.WeightedOperations { + +// var weightMsgSend, weightMsgMultiSend int +// appParams.GetOrGenerate(cdc, OpWeightMsgSend, &weightMsgSend, nil, +// func(_ *rand.Rand) { +// weightMsgSend = simappparams.DefaultWeightMsgSend +// }, +// ) + +// appParams.GetOrGenerate(cdc, OpWeightMsgMultiSend, &weightMsgMultiSend, nil, +// func(_ *rand.Rand) { +// weightMsgMultiSend = simappparams.DefaultWeightMsgMultiSend +// }, +// ) + +// return simulation.WeightedOperations{ +// simulation.NewWeightedOperation( +// weightMsgSend, +// SimulateMsgSend(ak, bk), +// ), +// simulation.NewWeightedOperation( +// weightMsgMultiSend, +// SimulateMsgMultiSend(ak, bk), +// ), +// } +// } + +// // SimulateMsgSend tests and runs a single msg send where both +// // accounts already exist. +// // nolint: funlen +// func SimulateMsgSend(ak types.AccountKeeper, bk keeper.Keeper) simulation.Operation { +// return func( +// r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, +// accs []simulation.Account, chainID string, +// ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + +// if !bk.GetSendEnabled(ctx) { +// return simulation.NoOpMsg(types.ModuleName), nil, nil +// } + +// simAccount, toSimAcc, coins, skip, err := randomSendFields(r, ctx, accs, ak) +// if err != nil { +// return simulation.NoOpMsg(types.ModuleName), nil, err +// } + +// if skip { +// return simulation.NoOpMsg(types.ModuleName), nil, nil +// } + +// msg := types.NewMsgSend(simAccount.Address, toSimAcc.Address, coins) + +// err = sendMsgSend(r, app, ak, msg, ctx, chainID, []crypto.PrivKey{simAccount.PrivKey}) +// if err != nil { +// return simulation.NoOpMsg(types.ModuleName), nil, err +// } + +// return simulation.NewOperationMsg(msg, true, ""), nil, nil +// } +// } + +// // sendMsgSend sends a transaction with a MsgSend from a provided random account. +// func sendMsgSend( +// r *rand.Rand, app *baseapp.BaseApp, ak types.AccountKeeper, // nolint:interfacer +// msg types.MsgSend, ctx sdk.Context, chainID string, privkeys []crypto.PrivKey, +// ) error { + +// account := ak.GetAccount(ctx, msg.FromAddress) +// coins := account.SpendableCoins(ctx.BlockTime()) + +// var ( +// fees sdk.Coins +// err error +// ) +// coins, hasNeg := coins.SafeSub(msg.Amount) +// if !hasNeg { +// fees, err = simulation.RandomFees(r, ctx, coins) +// if err != nil { +// return err +// } +// } + +// tx := helpers.GenTx( +// []sdk.Msg{msg}, +// fees, +// helpers.DefaultGenTxGas, +// chainID, +// []uint64{account.GetAccountNumber()}, +// []uint64{account.GetSequence()}, +// privkeys..., +// ) + +// _, _, err = app.Deliver(tx) +// if err != nil { +// return err +// } + +// return nil +// } + +// // SimulateMsgMultiSend tests and runs a single msg multisend, with randomized, capped number of inputs/outputs. +// // all accounts in msg fields exist in state +// // nolint: funlen +// func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simulation.Operation { +// return func( +// r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, +// accs []simulation.Account, chainID string, +// ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + +// if !bk.GetSendEnabled(ctx) { +// return simulation.NoOpMsg(types.ModuleName), nil, nil +// } + +// // random number of inputs/outputs between [1, 3] +// inputs := make([]types.Input, r.Intn(3)+1) +// outputs := make([]types.Output, r.Intn(3)+1) + +// // collect signer privKeys +// privs := make([]crypto.PrivKey, len(inputs)) + +// // use map to check if address already exists as input +// usedAddrs := make(map[string]bool) + +// var totalSentCoins sdk.Coins +// for i := range inputs { +// // generate random input fields, ignore to address +// simAccount, _, coins, skip, err := randomSendFields(r, ctx, accs, ak) + +// // make sure account is fresh and not used in previous input +// for usedAddrs[simAccount.Address.String()] { +// simAccount, _, coins, skip, err = randomSendFields(r, ctx, accs, ak) +// } + +// if err != nil { +// return simulation.NoOpMsg(types.ModuleName), nil, err +// } +// if skip { +// return simulation.NoOpMsg(types.ModuleName), nil, nil +// } + +// // set input address in used address map +// usedAddrs[simAccount.Address.String()] = true + +// // set signer privkey +// privs[i] = simAccount.PrivKey + +// // set next input and accumulate total sent coins +// inputs[i] = types.NewInput(simAccount.Address, coins) +// totalSentCoins = totalSentCoins.Add(coins...) +// } + +// for o := range outputs { +// outAddr, _ := simulation.RandomAcc(r, accs) + +// var outCoins sdk.Coins +// // split total sent coins into random subsets for output +// if o == len(outputs)-1 { +// outCoins = totalSentCoins +// } else { +// // take random subset of remaining coins for output +// // and update remaining coins +// outCoins = simulation.RandSubsetCoins(r, totalSentCoins) +// totalSentCoins = totalSentCoins.Sub(outCoins) +// } + +// outputs[o] = types.NewOutput(outAddr.Address, outCoins) +// } + +// // remove any output that has no coins +// i := 0 +// for i < len(outputs) { +// if outputs[i].Coins.Empty() { +// outputs[i] = outputs[len(outputs)-1] +// outputs = outputs[:len(outputs)-1] +// } else { +// // continue onto next coin +// i++ +// } +// } + +// msg := types.MsgMultiSend{ +// Inputs: inputs, +// Outputs: outputs, +// } + +// err := sendMsgMultiSend(r, app, ak, msg, ctx, chainID, privs) +// if err != nil { +// return simulation.NoOpMsg(types.ModuleName), nil, err +// } + +// return simulation.NewOperationMsg(msg, true, ""), nil, nil +// } +// } + +// // sendMsgMultiSend sends a transaction with a MsgMultiSend from a provided random +// // account. +// func sendMsgMultiSend( +// r *rand.Rand, app *baseapp.BaseApp, ak types.AccountKeeper, // nolint:interfacer +// msg types.MsgMultiSend, ctx sdk.Context, chainID string, privkeys []crypto.PrivKey, +// ) error { + +// accountNumbers := make([]uint64, len(msg.Inputs)) +// sequenceNumbers := make([]uint64, len(msg.Inputs)) + +// for i := 0; i < len(msg.Inputs); i++ { +// acc := ak.GetAccount(ctx, msg.Inputs[i].Address) +// accountNumbers[i] = acc.GetAccountNumber() +// sequenceNumbers[i] = acc.GetSequence() +// } + +// // feePayer is the first signer, i.e. first input address +// feePayer := ak.GetAccount(ctx, msg.Inputs[0].Address) +// coins := feePayer.SpendableCoins(ctx.BlockTime()) + +// var ( +// fees sdk.Coins +// err error +// ) +// coins, hasNeg := coins.SafeSub(msg.Inputs[0].Coins) +// if !hasNeg { +// fees, err = simulation.RandomFees(r, ctx, coins) +// if err != nil { +// return err +// } +// } + +// tx := helpers.GenTx( +// []sdk.Msg{msg}, +// fees, +// helpers.DefaultGenTxGas, +// chainID, +// accountNumbers, +// sequenceNumbers, +// privkeys..., +// ) + +// _, _, err = app.Deliver(tx) +// if err != nil { +// return err +// } + +// return nil +// } + +// // randomSendFields returns the sender and recipient simulation accounts as well +// // as the transferred amount. +// func randomSendFields( +// r *rand.Rand, ctx sdk.Context, accs []simulation.Account, ak types.AccountKeeper, // nolint:interfacer +// ) (simulation.Account, simulation.Account, sdk.Coins, bool, error) { + +// simAccount, _ := simulation.RandomAcc(r, accs) +// toSimAcc, _ := simulation.RandomAcc(r, accs) + +// // disallow sending money to yourself +// for simAccount.PubKey.Equals(toSimAcc.PubKey) { +// toSimAcc, _ = simulation.RandomAcc(r, accs) +// } + +// acc := ak.GetAccount(ctx, simAccount.Address) +// if acc == nil { +// return simAccount, toSimAcc, nil, true, nil // skip error +// } + +// coins := acc.SpendableCoins(ctx.BlockHeader().Time) + +// sendCoins := simulation.RandSubsetCoins(r, coins) +// if sendCoins.Empty() { +// return simAccount, toSimAcc, nil, true, nil // skip error +// } + +// return simAccount, toSimAcc, sendCoins, false, nil +// } diff --git a/x/message/simulation/params.go b/x/message/simulation/params.go new file mode 100644 index 0000000000..63de7aae87 --- /dev/null +++ b/x/message/simulation/params.go @@ -0,0 +1,25 @@ +package simulation + +// // DONTCOVER + +// import ( +// "fmt" +// "math/rand" + +// "github.com/cosmos/cosmos-sdk/x/bank/internal/types" +// "github.com/cosmos/cosmos-sdk/x/simulation" +// ) + +// const keySendEnabled = "sendenabled" + +// // ParamChanges defines the parameters that can be modified by param change proposals +// // on the simulation +// func ParamChanges(r *rand.Rand) []simulation.ParamChange { +// return []simulation.ParamChange{ +// simulation.NewSimParamChange(types.ModuleName, keySendEnabled, +// func(r *rand.Rand) string { +// return fmt.Sprintf("%v", GenSendEnabled(r)) +// }, +// ), +// } +// } diff --git a/x/message/spec/01_state.md b/x/message/spec/01_state.md new file mode 100644 index 0000000000..ba0e25b899 --- /dev/null +++ b/x/message/spec/01_state.md @@ -0,0 +1,9 @@ + + +# State + +Presently, the bank module has no inherent state — it simply reads and writes accounts using the `AccountKeeper` from the `auth` module. + +This implementation choice is intended to minimize necessary state reads/writes, since we expect most transactions to involve coin amounts (for fees), so storing coin data in the account saves reading it separately. diff --git a/x/message/spec/02_keepers.md b/x/message/spec/02_keepers.md new file mode 100644 index 0000000000..d20b42fe3d --- /dev/null +++ b/x/message/spec/02_keepers.md @@ -0,0 +1,135 @@ + + +# Keepers + +The bank module provides three different exported keeper interfaces which can be passed to other modules which need to read or update account balances. Modules should use the least-permissive interface which provides the functionality they require. + +Note that you should always review the `bank` module code to ensure that permissions are limited in the way that you expect. + +## Common Types + +### Input + +An input of a multiparty transfer + +```go +type Input struct { + Address AccAddress + Coins Coins +} +``` + +### Output + +An output of a multiparty transfer. + +```go +type Output struct { + Address AccAddress + Coins Coins +} +``` + +## BaseKeeper + +The base keeper provides full-permission access: the ability to arbitrary modify any account's balance and mint or burn coins. + +```go +type BaseKeeper interface { + SetCoins(addr AccAddress, amt Coins) + SubtractCoins(addr AccAddress, amt Coins) + AddCoins(addr AccAddress, amt Coins) + InputOutputCoins(inputs []Input, outputs []Output) +} +``` + +`setCoins` fetches an account by address, sets the coins on the account, and saves the account. + +``` +setCoins(addr AccAddress, amt Coins) + account = accountKeeper.getAccount(addr) + if account == nil + fail with "no account found" + account.Coins = amt + accountKeeper.setAccount(account) +``` + +`subtractCoins` fetches the coins of an account, subtracts the provided amount, and saves the account. This decreases the total supply. + +``` +subtractCoins(addr AccAddress, amt Coins) + oldCoins = getCoins(addr) + newCoins = oldCoins - amt + if newCoins < 0 + fail with "cannot end up with negative coins" + setCoins(addr, newCoins) +``` + +`addCoins` fetches the coins of an account, adds the provided amount, and saves the account. This increases the total supply. + +``` +addCoins(addr AccAddress, amt Coins) + oldCoins = getCoins(addr) + newCoins = oldCoins + amt + setCoins(addr, newCoins) +``` + +`inputOutputCoins` transfers coins from any number of input accounts to any number of output accounts. + +``` +inputOutputCoins(inputs []Input, outputs []Output) + for input in inputs + subtractCoins(input.Address, input.Coins) + for output in outputs + addCoins(output.Address, output.Coins) +``` + +## SendKeeper + +The send keeper provides access to account balances and the ability to transfer coins between accounts, but not to alter the total supply (mint or burn coins). + +```go +type SendKeeper interface { + SendCoins(from AccAddress, to AccAddress, amt Coins) +} +``` + +`sendCoins` transfers coins from one account to another. + +``` +sendCoins(from AccAddress, to AccAddress, amt Coins) + subtractCoins(from, amt) + addCoins(to, amt) +``` + +## ViewKeeper + +The view keeper provides read-only access to account balances but no balance alteration functionality. All balance lookups are `O(1)`. + +```go +type ViewKeeper interface { + GetCoins(addr AccAddress) Coins + HasCoins(addr AccAddress, amt Coins) bool +} +``` + +`getCoins` returns the coins associated with an account. + +``` +getCoins(addr AccAddress) + account = accountKeeper.getAccount(addr) + if account == nil + return Coins{} + return account.Coins +``` + +`hasCoins` returns whether or not an account has at least the provided amount of coins. + +``` +hasCoins(addr AccAddress, amt Coins) + account = accountKeeper.getAccount(addr) + coins = getCoins(addr) + return coins >= amt +``` diff --git a/x/message/spec/03_messages.md b/x/message/spec/03_messages.md new file mode 100644 index 0000000000..f9cde52283 --- /dev/null +++ b/x/message/spec/03_messages.md @@ -0,0 +1,30 @@ + + +# Messages + +## MsgSend + +```go +type MsgSend struct { + Inputs []Input + Outputs []Output +} +``` + +`handleMsgSend` just runs `inputOutputCoins`. + +``` +handleMsgSend(msg MsgSend) + inputSum = 0 + for input in inputs + inputSum += input.Amount + outputSum = 0 + for output in outputs + outputSum += output.Amount + if inputSum != outputSum: + fail with "input/output amount mismatch" + + return inputOutputCoins(msg.Inputs, msg.Outputs) +``` diff --git a/x/message/spec/04_events.md b/x/message/spec/04_events.md new file mode 100644 index 0000000000..1f97e7ab9b --- /dev/null +++ b/x/message/spec/04_events.md @@ -0,0 +1,29 @@ + + +# Events + +The bank module emits the following events: + +## Handlers + +### MsgSend + +| Type | Attribute Key | Attribute Value | +|----------|---------------|--------------------| +| transfer | recipient | {recipientAddress} | +| transfer | amount | {amount} | +| message | module | bank | +| message | action | send | +| message | sender | {senderAddress} | + +### MsgMultiSend + +| Type | Attribute Key | Attribute Value | +|----------|---------------|--------------------| +| transfer | recipient | {recipientAddress} | +| transfer | amount | {amount} | +| message | module | bank | +| message | action | multisend | +| message | sender | {senderAddress} | diff --git a/x/message/spec/05_params.md b/x/message/spec/05_params.md new file mode 100644 index 0000000000..fe59bed244 --- /dev/null +++ b/x/message/spec/05_params.md @@ -0,0 +1,12 @@ + + +# Parameters + +The bank module contains the following parameters: + +| Key | Type | Example | +|-------------|------|---------| +| sendenabled | bool | true | + diff --git a/x/message/spec/README.md b/x/message/spec/README.md new file mode 100644 index 0000000000..28902554f4 --- /dev/null +++ b/x/message/spec/README.md @@ -0,0 +1,34 @@ + + +# `bank` + +## Abstract + +This document specifies the bank module of the Cosmos SDK. + +The bank module is responsible for handling multi-asset coin transfers between +accounts and tracking special-case pseudo-transfers which must work differently +with particular kinds of accounts (notably delegating/undelegating for vesting +accounts). It exposes several interfaces with varying capabilities for secure +interaction with other modules which must alter user balances. + +This module will be used in the Cosmos Hub. + +## Contents + +1. **[State](01_state.md)** +2. **[Keepers](02_keepers.md)** + - [Common Types](02_keepers.md#common-types) + - [BaseKeeper](02_keepers.md#basekeeper) + - [SendKeeper](02_keepers.md#sendkeeper) + - [ViewKeeper](02_keepers.md#viewkeeper) +3. **[Messages](03_messages.md)** + - [MsgSend](03_messages.md#msgsend) +4. **[Events](04_events.md)** + - [Handlers](04_events.md#handlers) +5. **[Parameters](05_params.md)** diff --git a/x/wasm/internal/keeper/handler_plugin.go b/x/wasm/internal/keeper/handler_plugin.go index 6954190652..268e490173 100644 --- a/x/wasm/internal/keeper/handler_plugin.go +++ b/x/wasm/internal/keeper/handler_plugin.go @@ -5,6 +5,7 @@ import ( "fmt" wasmTypes "github.com/CosmWasm/go-cosmwasm/types" + msgTypes "github.com/CosmWasm/wasmd/x/message" "github.com/CosmWasm/wasmd/x/wasm/internal/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -28,12 +29,14 @@ func NewMessageHandler(router sdk.Router, customEncoders *MessageEncoders) Messa type BankEncoder func(sender sdk.AccAddress, msg *wasmTypes.BankMsg) ([]sdk.Msg, error) type CustomEncoder func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) +type MessageEncoder func(sender sdk.AccAddress, msg *wasmTypes.MessageMsg) ([]sdk.Msg, error) type StakingEncoder func(sender sdk.AccAddress, msg *wasmTypes.StakingMsg) ([]sdk.Msg, error) type WasmEncoder func(sender sdk.AccAddress, msg *wasmTypes.WasmMsg) ([]sdk.Msg, error) type MessageEncoders struct { Bank BankEncoder Custom CustomEncoder + Message MessageEncoder Staking StakingEncoder Wasm WasmEncoder } @@ -42,6 +45,7 @@ func DefaultEncoders() MessageEncoders { return MessageEncoders{ Bank: EncodeBankMsg, Custom: NoCustomMsg, + Message: EncodeMessageMsg, Staking: EncodeStakingMsg, Wasm: EncodeWasmMsg, } @@ -57,6 +61,9 @@ func (e MessageEncoders) Merge(o *MessageEncoders) MessageEncoders { if o.Custom != nil { e.Custom = o.Custom } + if o.Message != nil { + e.Message = o.Message + } if o.Staking != nil { e.Staking = o.Staking } @@ -72,12 +79,14 @@ func (e MessageEncoders) Encode(contractAddr sdk.AccAddress, msg wasmTypes.Cosmo return e.Bank(contractAddr, msg.Bank) case msg.Custom != nil: return e.Custom(contractAddr, msg.Custom) + case msg.Message != nil: + return e.Message(contractAddr, msg.Message) case msg.Staking != nil: return e.Staking(contractAddr, msg.Staking) case msg.Wasm != nil: return e.Wasm(contractAddr, msg.Wasm) } - return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Wasm") + return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Wasm, cannot Encode") } func EncodeBankMsg(sender sdk.AccAddress, msg *wasmTypes.BankMsg) ([]sdk.Msg, error) { @@ -111,6 +120,31 @@ func NoCustomMsg(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Custom variant not supported") } +func EncodeMessageMsg(sender sdk.AccAddress, msg *wasmTypes.MessageMsg) ([]sdk.Msg, error) { + if msg.Send == nil { + return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Message module") + } + if len(msg.Send.Text) == 0 { + return nil, nil + } + fromAddr, stderr := sdk.AccAddressFromBech32(msg.Send.FromAddress) + if stderr != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Send.FromAddress) + } + toAddr, stderr := sdk.AccAddressFromBech32(msg.Send.ToAddress) + if stderr != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, msg.Send.ToAddress) + } + toSend := msg.Send.Text + + sdkMsg := msgTypes.MsgTextSend{ + FromAddress: fromAddr, + ToAddress: toAddr, + Text: toSend, + } + return []sdk.Msg{sdkMsg}, nil +} + func EncodeStakingMsg(sender sdk.AccAddress, msg *wasmTypes.StakingMsg) ([]sdk.Msg, error) { if msg.Delegate != nil { validator, err := sdk.ValAddressFromBech32(msg.Delegate.Validator) diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 5f331b783c..7029b62988 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -2,9 +2,10 @@ package keeper import ( "encoding/binary" - "github.com/cosmos/cosmos-sdk/x/staking" "path/filepath" + "github.com/cosmos/cosmos-sdk/x/staking" + wasm "github.com/CosmWasm/go-cosmwasm" wasmTypes "github.com/CosmWasm/go-cosmwasm/types" "github.com/cosmos/cosmos-sdk/codec" @@ -15,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/tendermint/tendermint/crypto" + "github.com/CosmWasm/wasmd/x/message" "github.com/CosmWasm/wasmd/x/wasm/internal/types" ) @@ -33,6 +35,7 @@ type Keeper struct { cdc *codec.Codec accountKeeper auth.AccountKeeper bankKeeper bank.Keeper + messageKeeper message.Keeper wasmer wasm.Wasmer queryPlugins QueryPlugins @@ -43,7 +46,7 @@ type Keeper struct { // 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 sdk.StoreKey, accountKeeper auth.AccountKeeper, bankKeeper bank.Keeper, +func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, accountKeeper auth.AccountKeeper, bankKeeper bank.Keeper, messageKeeper message.Keeper, stakingKeeper staking.Keeper, router sdk.Router, homeDir string, wasmConfig types.WasmConfig, supportedFeatures string, customEncoders *MessageEncoders, customPlugins *QueryPlugins) Keeper { wasmer, err := wasm.NewWasmer(filepath.Join(homeDir, "wasm"), supportedFeatures, wasmConfig.CacheSize) @@ -59,6 +62,7 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, accountKeeper auth.Accou wasmer: *wasmer, accountKeeper: accountKeeper, bankKeeper: bankKeeper, + messageKeeper: messageKeeper, messenger: messenger, queryGasLimit: wasmConfig.SmartQueryGasLimit, } diff --git a/x/wasm/internal/keeper/test_common.go b/x/wasm/internal/keeper/test_common.go index 9b844aba56..b2af53fe86 100644 --- a/x/wasm/internal/keeper/test_common.go +++ b/x/wasm/internal/keeper/test_common.go @@ -2,15 +2,17 @@ package keeper import ( "fmt" - "github.com/cosmos/cosmos-sdk/x/distribution" "testing" "time" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" + "github.com/CosmWasm/wasmd/x/message" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" @@ -36,6 +38,7 @@ func MakeTestCodec() *codec.Codec { // cdc.RegisterConcrete(&auth.BaseAccount{}, "test/wasm/BaseAccount", nil) auth.AppModuleBasic{}.RegisterCodec(cdc) bank.AppModuleBasic{}.RegisterCodec(cdc) + message.AppModuleBasic{}.RegisterCodec(cdc) supply.AppModuleBasic{}.RegisterCodec(cdc) staking.AppModuleBasic{}.RegisterCodec(cdc) distribution.AppModuleBasic{}.RegisterCodec(cdc) @@ -66,6 +69,7 @@ type TestKeepers struct { func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeatures string, encoders *MessageEncoders, queriers *QueryPlugins) (sdk.Context, TestKeepers) { keyContract := sdk.NewKVStoreKey(wasmTypes.StoreKey) keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyMessage := sdk.NewKVStoreKey(message.StoreKey) keyStaking := sdk.NewKVStoreKey(staking.StoreKey) keySupply := sdk.NewKVStoreKey(supply.StoreKey) keyDistro := sdk.NewKVStoreKey(distribution.StoreKey) @@ -76,6 +80,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyContract, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyMessage, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db) @@ -106,6 +111,15 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat ) bankKeeper.SetSendEnabled(ctx, true) + messageKeeper := message.NewBaseKeeper( + cdc, // amino codec + keyMessage, // target store + pk.Subspace(auth.DefaultParamspace), + accountKeeper, + nil, + ) + messageKeeper.SetSendEnabled(ctx, true) + // this is also used to initialize module accounts (so nil is meaningful here) maccPerms := map[string][]string{ auth.FeeCollectorName: nil, @@ -160,7 +174,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat // Load default wasm config wasmConfig := wasmTypes.DefaultWasmConfig() - keeper := NewKeeper(cdc, keyContract, accountKeeper, bankKeeper, stakingKeeper, router, tempDir, wasmConfig, supportedFeatures, encoders, queriers) + keeper := NewKeeper(cdc, keyContract, accountKeeper, bankKeeper, messageKeeper, stakingKeeper, router, tempDir, wasmConfig, supportedFeatures, encoders, queriers) // add wasm handler so we can loop-back (contracts calling contracts) router.AddRoute(wasmTypes.RouterKey, TestHandler(keeper))