diff --git a/codec/types/interface_registry.go b/codec/types/interface_registry.go index ee729993af6..5d7e72e890c 100644 --- a/codec/types/interface_registry.go +++ b/codec/types/interface_registry.go @@ -43,7 +43,7 @@ type InterfaceRegistry interface { // the interface iface. // // Ex: - // registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{}) + // registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{}, &MsgMultiSend{}) RegisterImplementations(iface interface{}, impls ...proto.Message) // ListAllInterfaces list the type URLs of all registered interfaces. diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index f30ade8a7d9..a2115be784d 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -79,7 +79,9 @@ - [cosmos/bank/v1beta1/bank.proto](#cosmos/bank/v1beta1/bank.proto) - [DenomUnit](#cosmos.bank.v1beta1.DenomUnit) + - [Input](#cosmos.bank.v1beta1.Input) - [Metadata](#cosmos.bank.v1beta1.Metadata) + - [Output](#cosmos.bank.v1beta1.Output) - [Params](#cosmos.bank.v1beta1.Params) - [SendEnabled](#cosmos.bank.v1beta1.SendEnabled) - [Supply](#cosmos.bank.v1beta1.Supply) @@ -114,6 +116,8 @@ - [Query](#cosmos.bank.v1beta1.Query) - [cosmos/bank/v1beta1/tx.proto](#cosmos/bank/v1beta1/tx.proto) + - [MsgMultiSend](#cosmos.bank.v1beta1.MsgMultiSend) + - [MsgMultiSendResponse](#cosmos.bank.v1beta1.MsgMultiSendResponse) - [MsgSend](#cosmos.bank.v1beta1.MsgSend) - [MsgSendResponse](#cosmos.bank.v1beta1.MsgSendResponse) @@ -1584,6 +1588,22 @@ denomination unit of the basic token. + + +### Input +Input models transaction input. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `address` | [string](#string) | | | +| `coins` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | | + + + + + + ### Metadata @@ -1609,6 +1629,22 @@ Since: cosmos-sdk 0.43 | + + +### Output +Output models transaction outputs. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `address` | [string](#string) | | | +| `coins` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | | + + + + + + ### Params @@ -2095,6 +2131,32 @@ Query defines the gRPC querier service. + + +### MsgMultiSend +MsgMultiSend represents an arbitrary multi-in, multi-out send message. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `inputs` | [Input](#cosmos.bank.v1beta1.Input) | repeated | | +| `outputs` | [Output](#cosmos.bank.v1beta1.Output) | repeated | | + + + + + + + + +### MsgMultiSendResponse +MsgMultiSendResponse defines the Msg/MultiSend response type. + + + + + + ### MsgSend @@ -2136,6 +2198,7 @@ Msg defines the bank Msg service. | Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | | ----------- | ------------ | ------------- | ------------| ------- | -------- | | `Send` | [MsgSend](#cosmos.bank.v1beta1.MsgSend) | [MsgSendResponse](#cosmos.bank.v1beta1.MsgSendResponse) | Send defines a method for sending coins from one account to another account. | | +| `MultiSend` | [MsgMultiSend](#cosmos.bank.v1beta1.MsgMultiSend) | [MsgMultiSendResponse](#cosmos.bank.v1beta1.MsgMultiSendResponse) | MultiSend defines a method for sending coins from some accounts to other accounts. | | diff --git a/proto/cosmos/bank/v1beta1/bank.proto b/proto/cosmos/bank/v1beta1/bank.proto index 475ca93aeaa..df91008df64 100644 --- a/proto/cosmos/bank/v1beta1/bank.proto +++ b/proto/cosmos/bank/v1beta1/bank.proto @@ -23,6 +23,26 @@ message SendEnabled { bool enabled = 2; } +// Input models transaction input. +message Input { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string address = 1; + repeated cosmos.base.v1beta1.Coin coins = 2 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// Output models transaction outputs. +message Output { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string address = 1; + repeated cosmos.base.v1beta1.Coin coins = 2 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + // Supply represents a struct that passively keeps track of the total supply // amounts in the network. // This message is deprecated now that supply is indexed by denom. diff --git a/proto/cosmos/bank/v1beta1/tx.proto b/proto/cosmos/bank/v1beta1/tx.proto index b9e26d5d494..26b2ab41f4c 100644 --- a/proto/cosmos/bank/v1beta1/tx.proto +++ b/proto/cosmos/bank/v1beta1/tx.proto @@ -11,6 +11,9 @@ option go_package = "github.com/cosmos/cosmos-sdk/x/bank/types"; service Msg { // Send defines a method for sending coins from one account to another account. rpc Send(MsgSend) returns (MsgSendResponse); + + // MultiSend defines a method for sending coins from some accounts to other accounts. + rpc MultiSend(MsgMultiSend) returns (MsgMultiSendResponse); } // MsgSend represents a message to send coins from one account to another. @@ -26,3 +29,14 @@ message MsgSend { // MsgSendResponse defines the Msg/Send response type. message MsgSendResponse {} + +// MsgMultiSend represents an arbitrary multi-in, multi-out send message. +message MsgMultiSend { + option (gogoproto.equal) = false; + + repeated Input inputs = 1 [(gogoproto.nullable) = false]; + repeated Output outputs = 2 [(gogoproto.nullable) = false]; +} + +// MsgMultiSendResponse defines the Msg/MultiSend response type. +message MsgMultiSendResponse {} diff --git a/simapp/params/weights.go b/simapp/params/weights.go index 984eee939be..746e304de2b 100644 --- a/simapp/params/weights.go +++ b/simapp/params/weights.go @@ -3,6 +3,7 @@ package params // Default simulation operation weights for messages and gov proposals const ( DefaultWeightMsgSend int = 100 + DefaultWeightMsgMultiSend int = 10 DefaultWeightMsgSetWithdrawAddress int = 50 DefaultWeightMsgWithdrawDelegationReward int = 50 DefaultWeightMsgWithdrawValidatorCommission int = 50 diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 0421590b7dc..078c6e7bc47 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -13,7 +13,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/authz" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) var bankSendAuthMsgType = banktypes.SendAuthorization{}.MsgTypeURL() @@ -73,7 +72,7 @@ func (s *TestSuite) TestKeeper() { s.Require().Equal(authorization.MsgTypeURL(), bankSendAuthMsgType) s.T().Log("verify fetching authorization with wrong msg type fails") - authorization, _ = app.AuthzKeeper.GetCleanAuthorization(ctx, granteeAddr, granterAddr, sdk.MsgTypeURL(&govtypes.MsgDeposit{})) + authorization, _ = app.AuthzKeeper.GetCleanAuthorization(ctx, granteeAddr, granterAddr, sdk.MsgTypeURL(&banktypes.MsgMultiSend{})) s.Require().Nil(authorization) s.T().Log("verify fetching authorization with wrong grantee fails") diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 1f85e8e4d70..ba8085026a5 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -45,6 +45,44 @@ var ( 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) { @@ -81,3 +119,220 @@ func TestSendNotEnoughBalance(t *testing.T) { require.Equal(t, res2.GetAccountNumber(), origAccNum) require.Equal(t, res2.GetSequence(), origSeq+1) } + +func TestMsgMultiSendWithAccounts(t *testing.T) { + acc := &authtypes.BaseAccount{ + Address: addr1.String(), + } + + genAccs := []authtypes.GenesisAccount{acc} + app := simapp.SetupWithGenesisAccounts(genAccs) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + require.NoError(t, simapp.FundAccount(app.BankKeeper, ctx, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 67)))) + + app.Commit() + + res1 := app.AccountKeeper.GetAccount(ctx, addr1) + require.NotNil(t, res1) + require.Equal(t, acc, res1.(*authtypes.BaseAccount)) + + testCases := []appTestCase{ + { + desc: "make a valid tx", + msgs: []sdk.Msg{multiSendMsg1}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, + expSimPass: true, + expPass: true, + privKeys: []cryptotypes.PrivKey{priv1}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 57)}}, + {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, + }, + }, + { + desc: "wrong accNum should pass Simulate, but not Deliver", + msgs: []sdk.Msg{multiSendMsg1, multiSendMsg2}, + accNums: []uint64{1}, // wrong account number + accSeqs: []uint64{1}, + expSimPass: true, // doesn't check signature + expPass: false, + privKeys: []cryptotypes.PrivKey{priv1}, + }, + { + desc: "wrong accSeq should not pass Simulate", + msgs: []sdk.Msg{multiSendMsg5}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, // wrong account sequence + expSimPass: false, + expPass: false, + privKeys: []cryptotypes.PrivKey{priv1}, + }, + } + + for _, tc := range testCases { + header := tmproto.Header{Height: app.LastBlockHeight() + 1} + txGen := simapp.MakeTestEncodingConfig().TxConfig + _, _, err := simapp.SignCheckDeliver(t, txGen, app.BaseApp, header, tc.msgs, "", tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + if tc.expPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + + for _, eb := range tc.expectedBalances { + simapp.CheckBalance(t, app, eb.addr, eb.coins) + } + } +} + +func TestMsgMultiSendMultipleOut(t *testing.T) { + acc1 := &authtypes.BaseAccount{ + Address: addr1.String(), + } + acc2 := &authtypes.BaseAccount{ + Address: addr2.String(), + } + + genAccs := []authtypes.GenesisAccount{acc1, acc2} + app := simapp.SetupWithGenesisAccounts(genAccs) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + require.NoError(t, simapp.FundAccount(app.BankKeeper, ctx, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + + require.NoError(t, simapp.FundAccount(app.BankKeeper, ctx, addr2, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + + app.Commit() + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{multiSendMsg2}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, + expSimPass: true, + expPass: true, + privKeys: []cryptotypes.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 := tmproto.Header{Height: app.LastBlockHeight() + 1} + txGen := simapp.MakeTestEncodingConfig().TxConfig + _, _, err := simapp.SignCheckDeliver(t, txGen, app.BaseApp, header, tc.msgs, "", tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + require.NoError(t, err) + + for _, eb := range tc.expectedBalances { + simapp.CheckBalance(t, app, eb.addr, eb.coins) + } + } +} + +func TestMsgMultiSendMultipleInOut(t *testing.T) { + acc1 := &authtypes.BaseAccount{ + Address: addr1.String(), + } + acc2 := &authtypes.BaseAccount{ + Address: addr2.String(), + } + acc4 := &authtypes.BaseAccount{ + Address: addr4.String(), + } + + genAccs := []authtypes.GenesisAccount{acc1, acc2, acc4} + app := simapp.SetupWithGenesisAccounts(genAccs) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + require.NoError(t, simapp.FundAccount(app.BankKeeper, ctx, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + + require.NoError(t, simapp.FundAccount(app.BankKeeper, ctx, addr2, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + + require.NoError(t, simapp.FundAccount(app.BankKeeper, ctx, addr4, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + + app.Commit() + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{multiSendMsg3}, + accNums: []uint64{0, 2}, + accSeqs: []uint64{0, 0}, + expSimPass: true, + expPass: true, + privKeys: []cryptotypes.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 := tmproto.Header{Height: app.LastBlockHeight() + 1} + txGen := simapp.MakeTestEncodingConfig().TxConfig + _, _, err := simapp.SignCheckDeliver(t, txGen, app.BaseApp, header, tc.msgs, "", tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + require.NoError(t, err) + + for _, eb := range tc.expectedBalances { + simapp.CheckBalance(t, app, eb.addr, eb.coins) + } + } +} + +func TestMsgMultiSendDependent(t *testing.T) { + acc1 := authtypes.NewBaseAccountWithAddress(addr1) + acc2 := authtypes.NewBaseAccountWithAddress(addr2) + err := acc2.SetAccountNumber(1) + require.NoError(t, err) + + genAccs := []authtypes.GenesisAccount{acc1, acc2} + app := simapp.SetupWithGenesisAccounts(genAccs) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + require.NoError(t, simapp.FundAccount(app.BankKeeper, ctx, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 42)))) + + app.Commit() + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{multiSendMsg1}, + accNums: []uint64{0}, + accSeqs: []uint64{0}, + expSimPass: true, + expPass: true, + privKeys: []cryptotypes.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: []cryptotypes.PrivKey{priv2}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}}, + }, + }, + } + + for _, tc := range testCases { + header := tmproto.Header{Height: app.LastBlockHeight() + 1} + txGen := simapp.MakeTestEncodingConfig().TxConfig + _, _, err := simapp.SignCheckDeliver(t, txGen, app.BaseApp, header, tc.msgs, "", tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + require.NoError(t, err) + + for _, eb := range tc.expectedBalances { + simapp.CheckBalance(t, app, eb.addr, eb.coins) + } + } +} diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index 90d63c34efb..4ba2624b762 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -58,3 +58,45 @@ func BenchmarkOneBankSendTxPerBlock(b *testing.B) { height++ } } + +func BenchmarkOneBankMultiSendTxPerBlock(b *testing.B) { + b.ReportAllocs() + // Add an account at genesis + acc := authtypes.BaseAccount{ + Address: addr1.String(), + } + + // Construct genesis state + genAccs := []authtypes.GenesisAccount{&acc} + benchmarkApp := simapp.SetupWithGenesisAccounts(genAccs) + ctx := benchmarkApp.BaseApp.NewContext(false, tmproto.Header{}) + + // some value conceivably higher than the benchmarks would ever go + require.NoError(b, simapp.FundAccount(benchmarkApp.BankKeeper, ctx, addr1, sdk.NewCoins(sdk.NewInt64Coin("foocoin", 100000000000)))) + + benchmarkApp.Commit() + txGen := simappparams.MakeTestEncodingConfig().TxConfig + + // Precompute all txs + txs, err := simapp.GenSequenceOfTxs(txGen, []sdk.Msg{multiSendMsg1}, []uint64{0}, []uint64{uint64(0)}, b.N, priv1) + require.NoError(b, err) + b.ResetTimer() + + height := int64(3) + + // 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{Header: tmproto.Header{Height: height}}) + _, _, err := benchmarkApp.Check(txGen.TxEncoder(), txs[i]) + if err != nil { + panic("something is broken in checking transaction") + } + + _, _, err = benchmarkApp.Deliver(txGen.TxEncoder(), txs[i]) + require.NoError(b, err) + benchmarkApp.EndBlock(abci.RequestEndBlock{Height: height}) + benchmarkApp.Commit() + height++ + } +} diff --git a/x/bank/handler.go b/x/bank/handler.go index d17e2547fcc..0fb0f53a4cd 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -19,6 +19,10 @@ func NewHandler(k keeper.Keeper) sdk.Handler { res, err := msgServer.Send(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) + case *types.MsgMultiSend: + res, err := msgServer.MultiSend(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) + default: return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized bank message type: %T", msg) } diff --git a/x/bank/keeper/keeper_test.go b/x/bank/keeper/keeper_test.go index 4077f60c7c0..74218383cb0 100644 --- a/x/bank/keeper/keeper_test.go +++ b/x/bank/keeper/keeper_test.go @@ -350,6 +350,90 @@ func (suite *IntegrationTestSuite) TestSendCoinsNewAccount() { suite.Require().NotNil(app.AccountKeeper.GetAccount(ctx, addr2)) } +func (suite *IntegrationTestSuite) TestInputOutputNewAccount() { + app, ctx := suite.app, suite.ctx + + balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50)) + addr1 := sdk.AccAddress([]byte("addr1_______________")) + acc1 := app.AccountKeeper.NewAccountWithAddress(ctx, addr1) + app.AccountKeeper.SetAccount(ctx, acc1) + suite.Require().NoError(simapp.FundAccount(app.BankKeeper, ctx, addr1, balances)) + + acc1Balances := app.BankKeeper.GetAllBalances(ctx, addr1) + suite.Require().Equal(balances, acc1Balances) + + addr2 := sdk.AccAddress([]byte("addr2_______________")) + + suite.Require().Nil(app.AccountKeeper.GetAccount(ctx, addr2)) + suite.Require().Empty(app.BankKeeper.GetAllBalances(ctx, addr2)) + + inputs := []types.Input{ + {Address: addr1.String(), Coins: sdk.NewCoins(newFooCoin(30), newBarCoin(10))}, + } + outputs := []types.Output{ + {Address: addr2.String(), Coins: sdk.NewCoins(newFooCoin(30), newBarCoin(10))}, + } + + suite.Require().NoError(app.BankKeeper.InputOutputCoins(ctx, inputs, outputs)) + + expected := sdk.NewCoins(newFooCoin(30), newBarCoin(10)) + acc2Balances := app.BankKeeper.GetAllBalances(ctx, addr2) + suite.Require().Equal(expected, acc2Balances) + suite.Require().NotNil(app.AccountKeeper.GetAccount(ctx, addr2)) +} + +func (suite *IntegrationTestSuite) TestInputOutputCoins() { + app, ctx := suite.app, suite.ctx + balances := sdk.NewCoins(newFooCoin(90), newBarCoin(30)) + + addr1 := sdk.AccAddress([]byte("addr1_______________")) + acc1 := app.AccountKeeper.NewAccountWithAddress(ctx, addr1) + app.AccountKeeper.SetAccount(ctx, acc1) + + addr2 := sdk.AccAddress([]byte("addr2_______________")) + acc2 := app.AccountKeeper.NewAccountWithAddress(ctx, addr2) + app.AccountKeeper.SetAccount(ctx, acc2) + + addr3 := sdk.AccAddress([]byte("addr3_______________")) + acc3 := app.AccountKeeper.NewAccountWithAddress(ctx, addr3) + app.AccountKeeper.SetAccount(ctx, acc3) + + inputs := []types.Input{ + {Address: addr1.String(), Coins: sdk.NewCoins(newFooCoin(30), newBarCoin(10))}, + {Address: addr1.String(), Coins: sdk.NewCoins(newFooCoin(30), newBarCoin(10))}, + } + outputs := []types.Output{ + {Address: addr2.String(), Coins: sdk.NewCoins(newFooCoin(30), newBarCoin(10))}, + {Address: addr3.String(), Coins: sdk.NewCoins(newFooCoin(30), newBarCoin(10))}, + } + + suite.Require().Error(app.BankKeeper.InputOutputCoins(ctx, inputs, []types.Output{})) + suite.Require().Error(app.BankKeeper.InputOutputCoins(ctx, inputs, outputs)) + + suite.Require().NoError(simapp.FundAccount(app.BankKeeper, ctx, addr1, balances)) + + insufficientInputs := []types.Input{ + {Address: addr1.String(), Coins: sdk.NewCoins(newFooCoin(300), newBarCoin(100))}, + {Address: addr1.String(), Coins: sdk.NewCoins(newFooCoin(300), newBarCoin(100))}, + } + insufficientOutputs := []types.Output{ + {Address: addr2.String(), Coins: sdk.NewCoins(newFooCoin(300), newBarCoin(100))}, + {Address: addr3.String(), Coins: sdk.NewCoins(newFooCoin(300), newBarCoin(100))}, + } + suite.Require().Error(app.BankKeeper.InputOutputCoins(ctx, insufficientInputs, insufficientOutputs)) + suite.Require().NoError(app.BankKeeper.InputOutputCoins(ctx, inputs, outputs)) + + acc1Balances := app.BankKeeper.GetAllBalances(ctx, addr1) + expected := sdk.NewCoins(newFooCoin(30), newBarCoin(10)) + suite.Require().Equal(expected, acc1Balances) + + acc2Balances := app.BankKeeper.GetAllBalances(ctx, addr2) + suite.Require().Equal(expected, acc2Balances) + + acc3Balances := app.BankKeeper.GetAllBalances(ctx, addr3) + suite.Require().Equal(expected, acc3Balances) +} + func (suite *IntegrationTestSuite) TestSendCoins() { app, ctx := suite.app, suite.ctx balances := sdk.NewCoins(newFooCoin(100), newBarCoin(50)) @@ -561,6 +645,104 @@ func (suite *IntegrationTestSuite) TestMsgSendEvents() { suite.Require().Equal(abci.Event(event2), events[9]) } +func (suite *IntegrationTestSuite) TestMsgMultiSendEvents() { + app, ctx := suite.app, suite.ctx + + app.BankKeeper.SetParams(ctx, types.DefaultParams()) + + 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(fooDenom, 50)) + newCoins2 := sdk.NewCoins(sdk.NewInt64Coin(barDenom, 100)) + inputs := []types.Input{ + {Address: addr.String(), Coins: newCoins}, + {Address: addr2.String(), Coins: newCoins2}, + } + outputs := []types.Output{ + {Address: addr3.String(), Coins: newCoins}, + {Address: addr4.String(), Coins: newCoins2}, + } + + suite.Require().Error(app.BankKeeper.InputOutputCoins(ctx, inputs, outputs)) + + events := ctx.EventManager().ABCIEvents() + suite.Require().Equal(0, len(events)) + + // Set addr's coins but not addr2's coins + suite.Require().NoError(simapp.FundAccount(app.BankKeeper, ctx, addr, sdk.NewCoins(sdk.NewInt64Coin(fooDenom, 50)))) + suite.Require().Error(app.BankKeeper.InputOutputCoins(ctx, inputs, outputs)) + + events = ctx.EventManager().ABCIEvents() + suite.Require().Equal(8, len(events)) // 7 events because account funding causes extra minting + coin_spent + coin_recv events + + event1 := sdk.Event{ + Type: sdk.EventTypeMessage, + Attributes: []abci.EventAttribute{}, + } + event1.Attributes = append( + event1.Attributes, + abci.EventAttribute{Key: []byte(types.AttributeKeySender), Value: []byte(addr.String())}, + ) + suite.Require().Equal(abci.Event(event1), events[7]) + + // Set addr's coins and addr2's coins + suite.Require().NoError(simapp.FundAccount(app.BankKeeper, ctx, addr, sdk.NewCoins(sdk.NewInt64Coin(fooDenom, 50)))) + newCoins = sdk.NewCoins(sdk.NewInt64Coin(fooDenom, 50)) + + suite.Require().NoError(simapp.FundAccount(app.BankKeeper, ctx, addr2, sdk.NewCoins(sdk.NewInt64Coin(barDenom, 100)))) + newCoins2 = sdk.NewCoins(sdk.NewInt64Coin(barDenom, 100)) + + suite.Require().NoError(app.BankKeeper.InputOutputCoins(ctx, inputs, outputs)) + + events = ctx.EventManager().ABCIEvents() + suite.Require().Equal(28, len(events)) // 25 due to account funding + coin_spent + coin_recv events + + event2 := sdk.Event{ + Type: sdk.EventTypeMessage, + Attributes: []abci.EventAttribute{}, + } + event2.Attributes = append( + event2.Attributes, + abci.EventAttribute{Key: []byte(types.AttributeKeySender), Value: []byte(addr2.String())}, + ) + event3 := sdk.Event{ + Type: types.EventTypeTransfer, + Attributes: []abci.EventAttribute{}, + } + event3.Attributes = append( + event3.Attributes, + abci.EventAttribute{Key: []byte(types.AttributeKeyRecipient), Value: []byte(addr3.String())}, + ) + event3.Attributes = append( + event3.Attributes, + abci.EventAttribute{Key: []byte(sdk.AttributeKeyAmount), Value: []byte(newCoins.String())}) + event4 := sdk.Event{ + Type: types.EventTypeTransfer, + Attributes: []abci.EventAttribute{}, + } + event4.Attributes = append( + event4.Attributes, + abci.EventAttribute{Key: []byte(types.AttributeKeyRecipient), Value: []byte(addr4.String())}, + ) + event4.Attributes = append( + event4.Attributes, + abci.EventAttribute{Key: []byte(sdk.AttributeKeyAmount), Value: []byte(newCoins2.String())}, + ) + // events are shifted due to the funding account events + suite.Require().Equal(abci.Event(event1), events[21]) + suite.Require().Equal(abci.Event(event2), events[23]) + suite.Require().Equal(abci.Event(event3), events[25]) + suite.Require().Equal(abci.Event(event4), events[27]) +} + func (suite *IntegrationTestSuite) TestSpendableCoins() { app, ctx := suite.app, suite.ctx now := tmtime.Now() diff --git a/x/bank/keeper/msg_server.go b/x/bank/keeper/msg_server.go index c91a7cc9157..5d940b459e4 100644 --- a/x/bank/keeper/msg_server.go +++ b/x/bank/keeper/msg_server.go @@ -69,3 +69,38 @@ func (k msgServer) Send(goCtx context.Context, msg *types.MsgSend) (*types.MsgSe return &types.MsgSendResponse{}, nil } + +func (k msgServer) MultiSend(goCtx context.Context, msg *types.MsgMultiSend) (*types.MsgMultiSendResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // NOTE: totalIn == totalOut should already have been checked + for _, in := range msg.Inputs { + if err := k.IsSendEnabledCoins(ctx, in.Coins...); err != nil { + return nil, err + } + } + + for _, out := range msg.Outputs { + accAddr, err := sdk.AccAddressFromBech32(out.Address) + if err != nil { + panic(err) + } + if k.BlockedAddr(accAddr) { + 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 &types.MsgMultiSendResponse{}, nil +} diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index f2c90f92a4b..e6758d01982 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -16,6 +16,7 @@ import ( type SendKeeper interface { ViewKeeper + InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error SendManyCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddrs []sdk.AccAddress, amts []sdk.Coins) error @@ -69,6 +70,67 @@ func (k BaseSendKeeper) SetParams(ctx sdk.Context, params types.Params) { k.paramSpace.SetParamSet(ctx, ¶ms) } +// InputOutputCoins performs multi-send functionality. It accepts a series of +// inputs that correspond to a series of outputs. It returns an error if the +// inputs and outputs don't lineup or if any single transfer of tokens fails. +func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error { + // Safety check ensuring that when sending coins the keeper must maintain the + // Check supply invariant and validity of Coins. + if err := types.ValidateInputsOutputs(inputs, outputs); err != nil { + return err + } + + for _, in := range inputs { + inAddress, err := sdk.AccAddressFromBech32(in.Address) + if err != nil { + return err + } + + err = k.subUnlockedCoins(ctx, inAddress, in.Coins) + if err != nil { + return err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(types.AttributeKeySender, in.Address), + ), + ) + } + + for _, out := range outputs { + outAddress, err := sdk.AccAddressFromBech32(out.Address) + if err != nil { + return err + } + err = k.addCoins(ctx, outAddress, out.Coins) + if err != nil { + return err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeTransfer, + sdk.NewAttribute(types.AttributeKeyRecipient, out.Address), + sdk.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()), + ), + ) + + // Create account if recipient does not exist. + // + // NOTE: This should ultimately be removed in favor a more flexible approach + // such as delegated fee messages. + accExists := k.ak.HasAccount(ctx, outAddress) + if !accExists { + defer telemetry.IncrCounter(1, "new", "account") + k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, outAddress)) + } + } + + return nil +} + // SendCoins transfers amt coins from a sending account to a receiving account. // An error is returned upon failure. func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error { diff --git a/x/bank/simulation/operations.go b/x/bank/simulation/operations.go index 78d4d3e6cf6..1d09238c3a3 100644 --- a/x/bank/simulation/operations.go +++ b/x/bank/simulation/operations.go @@ -18,7 +18,8 @@ import ( // Simulation operation weights constants const ( - OpWeightMsgSend = "op_weight_msg_send" + OpWeightMsgSend = "op_weight_msg_send" + OpWeightMsgMultiSend = "op_weight_msg_multisend" ) // WeightedOperations returns all the operations from the module with their respective weights @@ -26,18 +27,28 @@ func WeightedOperations( appParams simtypes.AppParams, cdc codec.JSONCodec, ak types.AccountKeeper, bk keeper.Keeper, ) simulation.WeightedOperations { - var weightMsgSend int + 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), + ), } } @@ -150,6 +161,229 @@ func sendMsgSend( return gasInfo, nil } +// SimulateMsgMultiSend tests and runs a single msg multisend, with randomized, capped number of inputs/outputs. +// all accounts in msg fields exist in state +func SimulateMsgMultiSend(ak types.AccountKeeper, bk keeper.Keeper) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + // 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([]cryptotypes.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 + from, _, coins, skip := randomSendFields(r, ctx, accs, bk, ak) + + // make sure account is fresh and not used in previous input + for usedAddrs[from.Address.String()] { + from, _, coins, skip = randomSendFields(r, ctx, accs, bk, ak) + } + + if skip { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgMultiSend, "skip all transfers"), nil, nil + } + + // set input address in used address map + usedAddrs[from.Address.String()] = true + + // set signer privkey + privs[i] = from.PrivKey + + // set next input and accumulate total sent coins + inputs[i] = types.NewInput(from.Address, coins) + totalSentCoins = totalSentCoins.Add(coins...) + } + + // Check send_enabled status of each sent coin denom + if err := bk.IsSendEnabledCoins(ctx, totalSentCoins...); err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgMultiSend, err.Error()), nil, nil + } + + for o := range outputs { + outAddr, _ := simtypes.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 = simtypes.RandSubsetCoins(r, totalSentCoins) + totalSentCoins = totalSentCoins.Sub(outCoins) + } + + outputs[o] = types.NewOutput(outAddr.Address, outCoins) + } + + // remove any output that has no coins + + for i := 0; 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, + } + gasInfo, err := sendMsgMultiSend(r, app, bk, ak, msg, ctx, chainID, privs) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "", gasInfo.GasWanted, gasInfo.GasUsed, nil), nil, nil + } +} + +// SimulateMsgMultiSendToModuleAccount sends coins to Module Accounts +func SimulateMsgMultiSendToModuleAccount(ak types.AccountKeeper, bk keeper.Keeper, moduleAccCount int) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + inputs := make([]types.Input, 2) + outputs := make([]types.Output, moduleAccCount) + // collect signer privKeys + privs := make([]cryptotypes.PrivKey, len(inputs)) + + var totalSentCoins sdk.Coins + for i := range inputs { + sender := accs[i] + privs[i] = sender.PrivKey + spendable := bk.SpendableCoins(ctx, sender.Address) + coins := simtypes.RandSubsetCoins(r, spendable) + inputs[i] = types.NewInput(sender.Address, coins) + totalSentCoins = totalSentCoins.Add(coins...) + } + + if err := bk.IsSendEnabledCoins(ctx, totalSentCoins...); err != nil { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgMultiSend, err.Error()), nil, nil + } + + moduleAccounts := getModuleAccounts(ak, ctx, moduleAccCount) + for i := range outputs { + var outCoins sdk.Coins + // split total sent coins into random subsets for output + if i == len(outputs)-1 { + outCoins = totalSentCoins + } else { + // take random subset of remaining coins for output + // and update remaining coins + outCoins = simtypes.RandSubsetCoins(r, totalSentCoins) + totalSentCoins = totalSentCoins.Sub(outCoins) + } + + outputs[i] = types.NewOutput(moduleAccounts[i].Address, outCoins) + } + + // remove any output that has no coins + + for i := 0; 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, + } + gasInfo, err := sendMsgMultiSend(r, app, bk, ak, msg, ctx, chainID, privs) + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "invalid transfers"), nil, err + } + + return simtypes.NewOperationMsg(msg, true, "", gasInfo.GasWanted, gasInfo.GasUsed, nil), nil, nil + } +} + +// sendMsgMultiSend sends a transaction with a MsgMultiSend from a provided random +// account. +func sendMsgMultiSend( + r *rand.Rand, app *baseapp.BaseApp, bk keeper.Keeper, ak types.AccountKeeper, + msg *types.MsgMultiSend, ctx sdk.Context, chainID string, privkeys []cryptotypes.PrivKey, +) (sdk.GasInfo, error) { + + accountNumbers := make([]uint64, len(msg.Inputs)) + sequenceNumbers := make([]uint64, len(msg.Inputs)) + + for i := 0; i < len(msg.Inputs); i++ { + addr, err := sdk.AccAddressFromBech32(msg.Inputs[i].Address) + if err != nil { + panic(err) + } + acc := ak.GetAccount(ctx, addr) + accountNumbers[i] = acc.GetAccountNumber() + sequenceNumbers[i] = acc.GetSequence() + } + + var ( + fees sdk.Coins + err error + ) + + addr, err := sdk.AccAddressFromBech32(msg.Inputs[0].Address) + if err != nil { + panic(err) + } + + // feePayer is the first signer, i.e. first input address + feePayer := ak.GetAccount(ctx, addr) + spendable := bk.SpendableCoins(ctx, feePayer.GetAddress()) + + coins, hasNeg := spendable.SafeSub(msg.Inputs[0].Coins) + if !hasNeg { + feeCoins := coins.FilterDenoms([]string{sdk.DefaultBondDenom}) + fees, err = simtypes.RandomFees(r, ctx, feeCoins) + if err != nil { + return sdk.GasInfo{}, err + } + } + + txGen := simappparams.MakeTestEncodingConfig().TxConfig + tx, err := helpers.GenTx( + txGen, + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + accountNumbers, + sequenceNumbers, + privkeys..., + ) + if err != nil { + return sdk.GasInfo{}, err + } + + gasInfo, _, err := app.Deliver(txGen.TxEncoder(), tx) + if err != nil { + return sdk.GasInfo{}, err + } + + return gasInfo, nil +} + // randomSendFields returns the sender and recipient simulation accounts as well // as the transferred amount. func randomSendFields( diff --git a/x/bank/simulation/operations_test.go b/x/bank/simulation/operations_test.go index f013bfe427e..aeaa9f93890 100644 --- a/x/bank/simulation/operations_test.go +++ b/x/bank/simulation/operations_test.go @@ -48,6 +48,7 @@ func (suite *SimTestSuite) TestWeightedOperations() { opMsgName string }{ {simappparams.DefaultWeightMsgSend, types.ModuleName, types.TypeMsgSend}, + {simappparams.DefaultWeightMsgMultiSend, types.ModuleName, types.TypeMsgMultiSend}, } for i, w := range weightesOps { @@ -89,6 +90,38 @@ func (suite *SimTestSuite) TestSimulateMsgSend() { suite.Require().Len(futureOperations, 0) } +// TestSimulateMsgSend tests the normal scenario of a valid message of type TypeMsgMultiSend. +// Abonormal scenarios, where the message is created by an errors, are not tested here. +func (suite *SimTestSuite) TestSimulateMsgMultiSend() { + // setup 3 accounts + s := rand.NewSource(1) + r := rand.New(s) + accounts := suite.getTestingAccounts(r, 3) + + // begin a new block + suite.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgMultiSend(suite.app.AccountKeeper, suite.app.BankKeeper) + operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") + require := suite.Require() + require.NoError(err) + + var msg types.MsgMultiSend + types.ModuleCdc.UnmarshalJSON(operationMsg.Msg, &msg) + + require.True(operationMsg.OK) + require.Len(msg.Inputs, 3) + require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Inputs[1].Address) + require.Equal("185121068stake", msg.Inputs[1].Coins.String()) + require.Len(msg.Outputs, 2) + require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Outputs[1].Address) + require.Equal("260469617stake", msg.Outputs[1].Coins.String()) + require.Equal(types.TypeMsgMultiSend, msg.Type()) + require.Equal(types.ModuleName, msg.Route()) + require.Len(futureOperations, 0) +} + func (suite *SimTestSuite) TestSimulateModuleAccountMsgSend() { const ( accCount = 1 @@ -121,6 +154,35 @@ func (suite *SimTestSuite) TestSimulateModuleAccountMsgSend() { suite.Require().Len(futureOperations, 0) } +func (suite *SimTestSuite) TestSimulateMsgMultiSendToModuleAccount() { + const ( + accCount = 2 + mAccCount = 2 + ) + + s := rand.NewSource(1) + r := rand.New(s) + accounts := suite.getTestingAccounts(r, accCount) + + // begin a new block + suite.app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgMultiSendToModuleAccount(suite.app.AccountKeeper, suite.app.BankKeeper, mAccCount) + + operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "") + suite.Require().Error(err) + + var msg types.MsgMultiSend + types.ModuleCdc.UnmarshalJSON(operationMsg.Msg, &msg) + + suite.Require().False(operationMsg.OK) // sending tokens to a module account should fail + suite.Require().Equal(operationMsg.Comment, "invalid transfers") + suite.Require().Equal(types.TypeMsgMultiSend, msg.Type()) + suite.Require().Equal(types.ModuleName, msg.Route()) + suite.Require().Len(futureOperations, 0) +} + func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { accounts := simtypes.RandomAccounts(r, n) diff --git a/x/bank/spec/02_keepers.md b/x/bank/spec/02_keepers.md index 2c275591b4d..3671819a755 100644 --- a/x/bank/spec/02_keepers.md +++ b/x/bank/spec/02_keepers.md @@ -15,7 +15,8 @@ permissions are limited in the way that you expect. ## Blocklisting Addresses The `x/bank` module accepts a map of addresses that are considered blocklisted -from directly and explicitly receiving funds through means such as `MsgSend` and direct API calls like `SendCoinsFromModuleToAccount`. +from directly and explicitly receiving funds through means such as `MsgSend` and +`MsgMultiSend` and direct API calls like `SendCoinsFromModuleToAccount`. Typically, these addresses are module accounts. If these addresses receive funds outside the expected rules of the state machine, invariants are likely to be @@ -98,6 +99,7 @@ accounts. The send keeper does not alter the total supply (mint or burn coins). type SendKeeper interface { ViewKeeper + InputOutputCoins(ctx sdk.Context, inputs []types.Input, outputs []types.Output) error SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error GetParams(ctx sdk.Context) types.Params diff --git a/x/bank/spec/03_messages.md b/x/bank/spec/03_messages.md index 3884c01df7d..61f83abf3d8 100644 --- a/x/bank/spec/03_messages.md +++ b/x/bank/spec/03_messages.md @@ -13,3 +13,15 @@ The message will fail under the following conditions: - The coins do not have sending enabled - The `to` address is restricted + +## MsgMultiSend + +Send coins from and to a series of different address. If any of the receiving addresses do not correspond to an existing account, a new account is created. ++++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L33-L39 + +The message will fail under the following conditions: + +- Any of the coins do not have sending enabled +- Any of the `to` addresses are restricted +- Any of the coins are locked +- The inputs and outputs do not correctly correspond to one another diff --git a/x/bank/spec/04_events.md b/x/bank/spec/04_events.md index e9a7f5140e2..e71b820571a 100644 --- a/x/bank/spec/04_events.md +++ b/x/bank/spec/04_events.md @@ -18,6 +18,15 @@ The bank module emits the following events: | 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} | ## Keeper events diff --git a/x/bank/types/bank.pb.go b/x/bank/types/bank.pb.go index cccddaad6e3..8557be66e56 100644 --- a/x/bank/types/bank.pb.go +++ b/x/bank/types/bank.pb.go @@ -131,6 +131,84 @@ func (m *SendEnabled) GetEnabled() bool { return false } +// Input models transaction input. +type Input struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Coins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=coins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"coins"` +} + +func (m *Input) Reset() { *m = Input{} } +func (m *Input) String() string { return proto.CompactTextString(m) } +func (*Input) ProtoMessage() {} +func (*Input) Descriptor() ([]byte, []int) { + return fileDescriptor_dd052eee12edf988, []int{2} +} +func (m *Input) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Input) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Input.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Input) XXX_Merge(src proto.Message) { + xxx_messageInfo_Input.Merge(m, src) +} +func (m *Input) XXX_Size() int { + return m.Size() +} +func (m *Input) XXX_DiscardUnknown() { + xxx_messageInfo_Input.DiscardUnknown(m) +} + +var xxx_messageInfo_Input proto.InternalMessageInfo + +// Output models transaction outputs. +type Output struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Coins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=coins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"coins"` +} + +func (m *Output) Reset() { *m = Output{} } +func (m *Output) String() string { return proto.CompactTextString(m) } +func (*Output) ProtoMessage() {} +func (*Output) Descriptor() ([]byte, []int) { + return fileDescriptor_dd052eee12edf988, []int{3} +} +func (m *Output) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Output) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Output.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Output) XXX_Merge(src proto.Message) { + xxx_messageInfo_Output.Merge(m, src) +} +func (m *Output) XXX_Size() int { + return m.Size() +} +func (m *Output) XXX_DiscardUnknown() { + xxx_messageInfo_Output.DiscardUnknown(m) +} + +var xxx_messageInfo_Output proto.InternalMessageInfo + // Supply represents a struct that passively keeps track of the total supply // amounts in the network. // This message is deprecated now that supply is indexed by denom. @@ -144,7 +222,7 @@ func (m *Supply) Reset() { *m = Supply{} } func (m *Supply) String() string { return proto.CompactTextString(m) } func (*Supply) ProtoMessage() {} func (*Supply) Descriptor() ([]byte, []int) { - return fileDescriptor_dd052eee12edf988, []int{2} + return fileDescriptor_dd052eee12edf988, []int{4} } func (m *Supply) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -192,7 +270,7 @@ func (m *DenomUnit) Reset() { *m = DenomUnit{} } func (m *DenomUnit) String() string { return proto.CompactTextString(m) } func (*DenomUnit) ProtoMessage() {} func (*DenomUnit) Descriptor() ([]byte, []int) { - return fileDescriptor_dd052eee12edf988, []int{3} + return fileDescriptor_dd052eee12edf988, []int{5} } func (m *DenomUnit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -268,7 +346,7 @@ func (m *Metadata) Reset() { *m = Metadata{} } func (m *Metadata) String() string { return proto.CompactTextString(m) } func (*Metadata) ProtoMessage() {} func (*Metadata) Descriptor() ([]byte, []int) { - return fileDescriptor_dd052eee12edf988, []int{4} + return fileDescriptor_dd052eee12edf988, []int{6} } func (m *Metadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -342,6 +420,8 @@ func (m *Metadata) GetSymbol() string { func init() { proto.RegisterType((*Params)(nil), "cosmos.bank.v1beta1.Params") proto.RegisterType((*SendEnabled)(nil), "cosmos.bank.v1beta1.SendEnabled") + proto.RegisterType((*Input)(nil), "cosmos.bank.v1beta1.Input") + proto.RegisterType((*Output)(nil), "cosmos.bank.v1beta1.Output") proto.RegisterType((*Supply)(nil), "cosmos.bank.v1beta1.Supply") proto.RegisterType((*DenomUnit)(nil), "cosmos.bank.v1beta1.DenomUnit") proto.RegisterType((*Metadata)(nil), "cosmos.bank.v1beta1.Metadata") @@ -350,42 +430,44 @@ func init() { func init() { proto.RegisterFile("cosmos/bank/v1beta1/bank.proto", fileDescriptor_dd052eee12edf988) } var fileDescriptor_dd052eee12edf988 = []byte{ - // 547 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0xbf, 0x6f, 0xd3, 0x40, - 0x14, 0xce, 0x35, 0x3f, 0x48, 0x2e, 0xb0, 0x1c, 0x11, 0x72, 0x23, 0x61, 0x1b, 0x4b, 0x48, 0x29, - 0xa2, 0x4e, 0x0a, 0x0c, 0x28, 0x0b, 0x52, 0xca, 0x0f, 0x31, 0x20, 0x21, 0x57, 0x08, 0x09, 0x86, - 0xe8, 0x1c, 0x5f, 0xc3, 0xa9, 0xbe, 0x3b, 0x2b, 0x77, 0xa9, 0xea, 0xff, 0x80, 0x91, 0x91, 0xb1, - 0x33, 0x2b, 0xfc, 0x0f, 0x74, 0xac, 0x60, 0x61, 0x0a, 0x28, 0x59, 0x98, 0xfb, 0x17, 0x20, 0xdf, - 0x39, 0x89, 0x2b, 0x05, 0xc4, 0xe4, 0xf7, 0xbd, 0xf7, 0xbd, 0xef, 0xbd, 0xfb, 0xee, 0x0c, 0xed, - 0x91, 0x90, 0x4c, 0xc8, 0x6e, 0x88, 0xf9, 0x51, 0xf7, 0x78, 0x2f, 0x24, 0x0a, 0xef, 0x69, 0xe0, - 0x27, 0x13, 0xa1, 0x04, 0xba, 0x6e, 0xea, 0xbe, 0x4e, 0xe5, 0xf5, 0x76, 0x6b, 0x2c, 0xc6, 0x42, - 0xd7, 0xbb, 0x59, 0x64, 0xa8, 0xed, 0x6d, 0x43, 0x1d, 0x9a, 0x42, 0xde, 0x67, 0x4a, 0xeb, 0x29, - 0x92, 0xac, 0xa6, 0x8c, 0x04, 0xe5, 0xa6, 0xee, 0x7d, 0x07, 0xb0, 0xf6, 0x12, 0x4f, 0x30, 0x93, - 0xe8, 0x10, 0x5e, 0x95, 0x84, 0x47, 0x43, 0xc2, 0x71, 0x18, 0x93, 0xc8, 0x02, 0x6e, 0xb9, 0xd3, - 0xbc, 0xe7, 0xfa, 0x1b, 0xf6, 0xf0, 0x0f, 0x08, 0x8f, 0x9e, 0x18, 0xde, 0xe0, 0xd6, 0xc5, 0xcc, - 0xb9, 0x99, 0x62, 0x16, 0xf7, 0xbd, 0x62, 0xff, 0x5d, 0xc1, 0xa8, 0x22, 0x2c, 0x51, 0xa9, 0x17, - 0x34, 0xe5, 0x9a, 0x8f, 0xde, 0xc2, 0x56, 0x44, 0x0e, 0xf1, 0x34, 0x56, 0xc3, 0x4b, 0xf3, 0xb6, - 0x5c, 0xd0, 0xa9, 0x0f, 0x76, 0x2e, 0x66, 0xce, 0x6d, 0xa3, 0xb6, 0x89, 0x55, 0x54, 0x45, 0x39, - 0xa1, 0xb0, 0x4c, 0xbf, 0xf2, 0xf1, 0xd4, 0x29, 0x79, 0xcf, 0x60, 0xb3, 0x90, 0x44, 0x2d, 0x58, - 0x8d, 0x08, 0x17, 0xcc, 0x02, 0x2e, 0xe8, 0x34, 0x02, 0x03, 0x90, 0x05, 0xaf, 0x5c, 0x1a, 0x1d, - 0x2c, 0x61, 0xbf, 0x9e, 0x89, 0xfc, 0x3e, 0x75, 0x80, 0xf7, 0x19, 0xc0, 0xda, 0xc1, 0x34, 0x49, - 0xe2, 0x14, 0x61, 0x58, 0x55, 0x42, 0xe1, 0x38, 0xf7, 0x65, 0x7b, 0xed, 0x8b, 0x24, 0x2b, 0x5f, - 0xf6, 0x05, 0xe5, 0x83, 0xde, 0xd9, 0xcc, 0x29, 0x7d, 0xfa, 0xe9, 0x74, 0xc6, 0x54, 0xbd, 0x9b, - 0x86, 0xfe, 0x48, 0xb0, 0xfc, 0x52, 0xf2, 0xcf, 0xae, 0x8c, 0x8e, 0xba, 0x2a, 0x4d, 0x88, 0xd4, - 0x0d, 0x32, 0x30, 0xca, 0xfd, 0xa7, 0xef, 0xf3, 0xb9, 0xdf, 0xbe, 0xec, 0x3e, 0xbc, 0xf3, 0xcf, - 0xee, 0x13, 0xf3, 0x6e, 0x62, 0x32, 0xc6, 0xa3, 0xb4, 0x7b, 0xdc, 0x7b, 0xd0, 0xf3, 0xcd, 0x9e, - 0xcf, 0x2d, 0xe0, 0xbd, 0x86, 0x8d, 0xc7, 0xd9, 0x11, 0x5f, 0x71, 0xaa, 0xfe, 0x72, 0xf8, 0x36, - 0xac, 0x93, 0x93, 0x44, 0x70, 0xc2, 0x95, 0x3e, 0xfd, 0xb5, 0x60, 0x85, 0x33, 0x63, 0x70, 0x4c, - 0xb1, 0x24, 0xd2, 0x2a, 0xbb, 0xe5, 0x4e, 0x23, 0x58, 0x42, 0xef, 0x2b, 0x80, 0xf5, 0x17, 0x44, - 0xe1, 0x08, 0x2b, 0x8c, 0x5c, 0xd8, 0x8c, 0x88, 0x1c, 0x4d, 0x68, 0xa2, 0xa8, 0xe0, 0xb9, 0x7c, - 0x31, 0x85, 0x1e, 0x65, 0x0c, 0x2e, 0xd8, 0x70, 0xca, 0xa9, 0x92, 0xd6, 0x96, 0x36, 0xce, 0xde, - 0xf8, 0xa0, 0x56, 0xfb, 0x06, 0x30, 0x5a, 0x86, 0x12, 0x21, 0x58, 0xc9, 0xec, 0xb5, 0xca, 0x5a, - 0x5b, 0xc7, 0xd9, 0x76, 0x11, 0x95, 0x49, 0x8c, 0x53, 0xab, 0xa2, 0xd3, 0x4b, 0x98, 0xb1, 0x39, - 0x66, 0xc4, 0xaa, 0x1a, 0x76, 0x16, 0xa3, 0x1b, 0xb0, 0x26, 0x53, 0x16, 0x8a, 0xd8, 0xaa, 0xe9, - 0x6c, 0x8e, 0x06, 0xfb, 0x67, 0x73, 0x1b, 0x9c, 0xcf, 0x6d, 0xf0, 0x6b, 0x6e, 0x83, 0x0f, 0x0b, - 0xbb, 0x74, 0xbe, 0xb0, 0x4b, 0x3f, 0x16, 0x76, 0xe9, 0xcd, 0xce, 0xff, 0xf8, 0xae, 0x2f, 0x2f, - 0xac, 0xe9, 0x7f, 0xe8, 0xfe, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x17, 0x87, 0x70, 0x78, 0xcb, - 0x03, 0x00, 0x00, + // 592 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x54, 0xbf, 0x6f, 0xd3, 0x40, + 0x14, 0xf6, 0x35, 0x8d, 0x49, 0x2f, 0xb0, 0x1c, 0x15, 0x72, 0x23, 0x61, 0x1b, 0x4b, 0x48, 0x29, + 0xa2, 0x4e, 0x0a, 0x0c, 0x28, 0x0b, 0x52, 0xca, 0x0f, 0x75, 0x40, 0x20, 0x57, 0x08, 0x09, 0x86, + 0xe8, 0x9c, 0xbb, 0x06, 0xab, 0xf6, 0x9d, 0x95, 0x3b, 0x57, 0xf5, 0x7f, 0xc0, 0x04, 0x8c, 0x8c, + 0x9d, 0x59, 0xe1, 0x7f, 0xa0, 0x63, 0x05, 0x0b, 0x53, 0x40, 0xc9, 0xc2, 0xdc, 0xbf, 0x00, 0xf9, + 0xce, 0xf9, 0x51, 0x29, 0x20, 0x06, 0x06, 0xa6, 0xbc, 0xef, 0xbd, 0xef, 0x7d, 0xef, 0xe9, 0xbb, + 0xe7, 0x40, 0xbb, 0xcf, 0x45, 0xc2, 0x45, 0x2b, 0xc4, 0xec, 0xa0, 0x75, 0xb8, 0x1d, 0x52, 0x89, + 0xb7, 0x15, 0xf0, 0xd3, 0x21, 0x97, 0x1c, 0x5d, 0xd6, 0x75, 0x5f, 0xa5, 0xca, 0x7a, 0x63, 0x7d, + 0xc0, 0x07, 0x5c, 0xd5, 0x5b, 0x45, 0xa4, 0xa9, 0x8d, 0x0d, 0x4d, 0xed, 0xe9, 0x42, 0xd9, 0xa7, + 0x4b, 0xf3, 0x29, 0x82, 0xce, 0xa6, 0xf4, 0x79, 0xc4, 0x74, 0xdd, 0xfb, 0x0a, 0xa0, 0xf9, 0x14, + 0x0f, 0x71, 0x22, 0xd0, 0x3e, 0xbc, 0x28, 0x28, 0x23, 0x3d, 0xca, 0x70, 0x18, 0x53, 0x62, 0x01, + 0xb7, 0xd2, 0xac, 0xdf, 0x72, 0xfd, 0x25, 0x7b, 0xf8, 0x7b, 0x94, 0x91, 0x07, 0x9a, 0xd7, 0xbd, + 0x76, 0x36, 0x72, 0xae, 0xe6, 0x38, 0x89, 0x3b, 0xde, 0x62, 0xff, 0x4d, 0x9e, 0x44, 0x92, 0x26, + 0xa9, 0xcc, 0xbd, 0xa0, 0x2e, 0xe6, 0x7c, 0xf4, 0x12, 0xae, 0x13, 0xba, 0x8f, 0xb3, 0x58, 0xf6, + 0xce, 0xcd, 0x5b, 0x71, 0x41, 0xb3, 0xd6, 0xdd, 0x3c, 0x1b, 0x39, 0xd7, 0xb5, 0xda, 0x32, 0xd6, + 0xa2, 0x2a, 0x2a, 0x09, 0x0b, 0xcb, 0x74, 0x56, 0xdf, 0x1f, 0x3b, 0x86, 0xf7, 0x08, 0xd6, 0x17, + 0x92, 0x68, 0x1d, 0x56, 0x09, 0x65, 0x3c, 0xb1, 0x80, 0x0b, 0x9a, 0x6b, 0x81, 0x06, 0xc8, 0x82, + 0x17, 0xce, 0x8d, 0x0e, 0xa6, 0xb0, 0x53, 0x2b, 0x44, 0x7e, 0x1e, 0x3b, 0xc0, 0x7b, 0x03, 0x60, + 0x75, 0x97, 0xa5, 0x99, 0x2c, 0xd8, 0x98, 0x90, 0x21, 0x15, 0xa2, 0x54, 0x99, 0x42, 0x84, 0x61, + 0xb5, 0x30, 0x54, 0x58, 0x2b, 0xca, 0xb0, 0x8d, 0xb9, 0x61, 0x82, 0xce, 0x0c, 0xdb, 0xe1, 0x11, + 0xeb, 0xb6, 0x4f, 0x46, 0x8e, 0xf1, 0xe1, 0xbb, 0xd3, 0x1c, 0x44, 0xf2, 0x55, 0x16, 0xfa, 0x7d, + 0x9e, 0x94, 0xaf, 0x55, 0xfe, 0x6c, 0x09, 0x72, 0xd0, 0x92, 0x79, 0x4a, 0x85, 0x6a, 0x10, 0x81, + 0x56, 0xee, 0xd4, 0x5e, 0xeb, 0x85, 0x0c, 0xef, 0x2d, 0x80, 0xe6, 0x93, 0x4c, 0xfe, 0x47, 0x1b, + 0x7d, 0x04, 0xd0, 0xdc, 0xcb, 0xd2, 0x34, 0xce, 0x8b, 0xb9, 0x92, 0x4b, 0x1c, 0x97, 0xa7, 0xf3, + 0x6f, 0xe7, 0x2a, 0xe5, 0xce, 0xc3, 0x72, 0x2e, 0xf8, 0xf2, 0x69, 0xeb, 0xee, 0x8d, 0x3f, 0x76, + 0x1f, 0xe9, 0x4f, 0x2b, 0xa6, 0x03, 0xdc, 0xcf, 0x5b, 0x87, 0xed, 0x3b, 0x6d, 0x5f, 0xef, 0xb9, + 0x6b, 0x01, 0xef, 0x39, 0x5c, 0xbb, 0x5f, 0x5c, 0xc1, 0x33, 0x16, 0xc9, 0xdf, 0xdc, 0x47, 0x03, + 0xd6, 0xe8, 0x51, 0xca, 0x19, 0x65, 0x52, 0x1d, 0xc8, 0xa5, 0x60, 0x86, 0x95, 0xf7, 0x71, 0x84, + 0x05, 0x15, 0x56, 0xc5, 0xad, 0x28, 0xef, 0x35, 0xf4, 0x3e, 0x03, 0x58, 0x7b, 0x4c, 0x25, 0x26, + 0x58, 0x62, 0xe4, 0xc2, 0x3a, 0xa1, 0xa2, 0x3f, 0x8c, 0x52, 0x19, 0x71, 0x56, 0xca, 0x2f, 0xa6, + 0xd0, 0xbd, 0x82, 0xc1, 0x78, 0xd2, 0xcb, 0x58, 0x24, 0xa7, 0x0f, 0x66, 0x2f, 0xfd, 0xe6, 0x66, + 0xfb, 0x06, 0x90, 0x4c, 0x43, 0x81, 0x10, 0x5c, 0x2d, 0xec, 0xb5, 0x2a, 0x4a, 0x5b, 0xc5, 0xc5, + 0x76, 0x24, 0x12, 0x69, 0x8c, 0x73, 0x6b, 0x55, 0x5f, 0x46, 0x09, 0x0b, 0x36, 0xc3, 0x09, 0xb5, + 0xaa, 0x9a, 0x5d, 0xc4, 0xe8, 0x0a, 0x34, 0x45, 0x9e, 0x84, 0x3c, 0xb6, 0x4c, 0x95, 0x2d, 0x51, + 0x77, 0xe7, 0x64, 0x6c, 0x83, 0xd3, 0xb1, 0x0d, 0x7e, 0x8c, 0x6d, 0xf0, 0x6e, 0x62, 0x1b, 0xa7, + 0x13, 0xdb, 0xf8, 0x36, 0xb1, 0x8d, 0x17, 0x9b, 0x7f, 0xe3, 0xbb, 0x7a, 0xbc, 0xd0, 0x54, 0x7f, + 0x33, 0xb7, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x03, 0xbf, 0xe9, 0xee, 0x04, 0x00, 0x00, } func (this *SendEnabled) Equal(that interface{}) bool { @@ -531,6 +613,94 @@ func (m *SendEnabled) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Input) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Input) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Input) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Coins) > 0 { + for iNdEx := len(m.Coins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Coins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBank(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintBank(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Output) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Output) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Output) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Coins) > 0 { + for iNdEx := len(m.Coins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Coins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBank(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintBank(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *Supply) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -729,6 +899,44 @@ func (m *SendEnabled) Size() (n int) { return n } +func (m *Input) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovBank(uint64(l)) + } + if len(m.Coins) > 0 { + for _, e := range m.Coins { + l = e.Size() + n += 1 + l + sovBank(uint64(l)) + } + } + return n +} + +func (m *Output) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovBank(uint64(l)) + } + if len(m.Coins) > 0 { + for _, e := range m.Coins { + l = e.Size() + n += 1 + l + sovBank(uint64(l)) + } + } + return n +} + func (m *Supply) Size() (n int) { if m == nil { return 0 @@ -1013,6 +1221,238 @@ func (m *SendEnabled) Unmarshal(dAtA []byte) error { } return nil } +func (m *Input) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBank + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Input: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Input: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBank + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBank + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBank + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Coins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBank + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBank + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBank + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Coins = append(m.Coins, types.Coin{}) + if err := m.Coins[len(m.Coins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBank(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBank + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Output) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBank + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Output: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Output: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBank + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBank + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBank + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Coins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBank + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBank + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBank + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Coins = append(m.Coins, types.Coin{}) + if err := m.Coins[len(m.Coins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBank(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBank + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Supply) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/bank/types/codec.go b/x/bank/types/codec.go index 3bef66d1b72..a29b4864351 100644 --- a/x/bank/types/codec.go +++ b/x/bank/types/codec.go @@ -14,12 +14,14 @@ import ( // on the provided LegacyAmino codec. These types are used for Amino JSON serialization. func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { cdc.RegisterConcrete(&MsgSend{}, "cosmos-sdk/MsgSend", nil) + cdc.RegisterConcrete(&MsgMultiSend{}, "cosmos-sdk/MsgMultiSend", nil) cdc.RegisterConcrete(&SendAuthorization{}, "cosmos-sdk/SendAuthorization", nil) } func RegisterInterfaces(registry types.InterfaceRegistry) { registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{}, + &MsgMultiSend{}, ) registry.RegisterImplementations( (*authz.Authorization)(nil), diff --git a/x/bank/types/errors.go b/x/bank/types/errors.go index 91305ade9e8..8446d957b67 100644 --- a/x/bank/types/errors.go +++ b/x/bank/types/errors.go @@ -6,7 +6,9 @@ import ( // x/bank module sentinel errors var ( - // Error codes 2-4 were already assigned and removed, but should not be recycled. + ErrNoInputs = sdkerrors.Register(ModuleName, 2, "no inputs to send transaction") + ErrNoOutputs = sdkerrors.Register(ModuleName, 3, "no outputs to send transaction") + ErrInputOutputMismatch = sdkerrors.Register(ModuleName, 4, "sum inputs != sum outputs") ErrSendDisabled = sdkerrors.Register(ModuleName, 5, "send transactions are disabled") ErrDenomMetadataNotFound = sdkerrors.Register(ModuleName, 6, "client denom metadata not found") ErrInvalidKey = sdkerrors.Register(ModuleName, 7, "invalid key") diff --git a/x/bank/types/msgs.go b/x/bank/types/msgs.go index d60d0b2c02c..ae1813aa8d7 100644 --- a/x/bank/types/msgs.go +++ b/x/bank/types/msgs.go @@ -8,7 +8,8 @@ import ( // bank message types const ( - TypeMsgSend = "send" + TypeMsgSend = "send" + TypeMsgMultiSend = "multisend" ) var _ sdk.Msg = &MsgSend{} @@ -61,3 +62,130 @@ func (msg MsgSend) GetSigners() []sdk.AccAddress { } return []sdk.AccAddress{from} } + +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 TypeMsgMultiSend } + +// 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 { + addr, _ := sdk.AccAddressFromBech32(in.Address) + addrs[i] = addr + } + + return addrs +} + +// ValidateBasic - validate transaction input +func (in Input) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(in.Address) + if err != nil { + return err + } + + 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 +//nolint:interfacer +func NewInput(addr sdk.AccAddress, coins sdk.Coins) Input { + return Input{ + Address: addr.String(), + Coins: coins, + } +} + +// ValidateBasic - validate transaction output +func (out Output) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(out.Address) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid output address (%s)", err) + } + + 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 +//nolint:interfacer +func NewOutput(addr sdk.AccAddress, coins sdk.Coins) Output { + return Output{ + Address: addr.String(), + 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/bank/types/msgs_test.go b/x/bank/types/msgs_test.go index 3e0119fdf03..75be4f11857 100644 --- a/x/bank/types/msgs_test.go +++ b/x/bank/types/msgs_test.go @@ -71,3 +71,199 @@ func TestMsgSendGetSigners(t *testing.T) { // TODO: fix this ! require.Equal(t, fmt.Sprintf("%v", res), "[696E707574313131313131313131313131313131]") } + +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("_______alice________")) + addr2 := sdk.AccAddress([]byte("________bob_________")) + addrEmpty := sdk.AccAddress([]byte("")) + addrLong := sdk.AccAddress([]byte("Purposefully long address")) + + someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123)) + multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20)) + + 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 { + expectedErr string // empty means no error expected + txIn Input + }{ + // auth works with different apps + {"", NewInput(addr1, someCoins)}, + {"", NewInput(addr2, someCoins)}, + {"", NewInput(addr2, multiCoins)}, + {"", NewInput(addrLong, someCoins)}, + + {"empty address string is not allowed", NewInput(addrEmpty, someCoins)}, + {": invalid coins", NewInput(addr1, emptyCoins)}, // invalid coins + {": invalid coins", NewInput(addr1, emptyCoins2)}, // invalid coins + {"10eth,0atom: invalid coins", NewInput(addr1, someEmptyCoins)}, // invalid coins + {"1eth,1atom: invalid coins", NewInput(addr1, unsortedCoins)}, // unsorted coins + } + + for i, tc := range cases { + err := tc.txIn.ValidateBasic() + if tc.expectedErr == "" { + require.Nil(t, err, "%d: %+v", i, err) + } else { + require.EqualError(t, err, tc.expectedErr, "%d", i) + } + } +} + +func TestOutputValidation(t *testing.T) { + addr1 := sdk.AccAddress([]byte("_______alice________")) + addr2 := sdk.AccAddress([]byte("________bob_________")) + addrEmpty := sdk.AccAddress([]byte("")) + addrLong := sdk.AccAddress([]byte("Purposefully long address")) + + someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123)) + multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20)) + + 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 { + expectedErr string // empty means no error expected + txOut Output + }{ + // auth works with different apps + {"", NewOutput(addr1, someCoins)}, + {"", NewOutput(addr2, someCoins)}, + {"", NewOutput(addr2, multiCoins)}, + {"", NewOutput(addrLong, someCoins)}, + + {"Invalid output address (empty address string is not allowed): invalid address", NewOutput(addrEmpty, someCoins)}, + {": invalid coins", NewOutput(addr1, emptyCoins)}, // invalid coins + {": invalid coins", NewOutput(addr1, emptyCoins2)}, // invalid coins + {"10eth,0atom: invalid coins", NewOutput(addr1, someEmptyCoins)}, // invalid coins + {"1eth,1atom: invalid coins", NewOutput(addr1, unsortedCoins)}, // unsorted coins + } + + for i, tc := range cases { + err := tc.txOut.ValidateBasic() + if tc.expectedErr == "" { + require.Nil(t, err, "%d: %+v", i, err) + } else { + require.EqualError(t, err, tc.expectedErr, "%d", i) + } + } +} + +func TestMsgMultiSendValidation(t *testing.T) { + addr1 := sdk.AccAddress([]byte("_______alice________")) + addr2 := sdk.AccAddress([]byte("________bob_________")) + 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.String(), 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("input111111111111111")), nil), + NewInput(sdk.AccAddress([]byte("input222222222222222")), nil), + NewInput(sdk.AccAddress([]byte("input333333333333333")), nil), + }, + } + + res := msg.GetSigners() + // TODO: fix this ! + require.Equal(t, "[696E707574313131313131313131313131313131 696E707574323232323232323232323232323232 696E707574333333333333333333333333333333]", fmt.Sprintf("%v", res)) +} + +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 := NewMsgMultiSend(inputs, nil) + + require.Equal(t, signers, tx.GetSigners()) +} diff --git a/x/bank/types/tx.pb.go b/x/bank/types/tx.pb.go index 94db3dbb5d1..6dd52c4b64d 100644 --- a/x/bank/types/tx.pb.go +++ b/x/bank/types/tx.pb.go @@ -107,37 +107,135 @@ func (m *MsgSendResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSendResponse proto.InternalMessageInfo +// MsgMultiSend represents an arbitrary multi-in, multi-out send message. +type MsgMultiSend struct { + Inputs []Input `protobuf:"bytes,1,rep,name=inputs,proto3" json:"inputs"` + Outputs []Output `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs"` +} + +func (m *MsgMultiSend) Reset() { *m = MsgMultiSend{} } +func (m *MsgMultiSend) String() string { return proto.CompactTextString(m) } +func (*MsgMultiSend) ProtoMessage() {} +func (*MsgMultiSend) Descriptor() ([]byte, []int) { + return fileDescriptor_1d8cb1613481f5b7, []int{2} +} +func (m *MsgMultiSend) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMultiSend) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMultiSend.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMultiSend) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMultiSend.Merge(m, src) +} +func (m *MsgMultiSend) XXX_Size() int { + return m.Size() +} +func (m *MsgMultiSend) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMultiSend.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMultiSend proto.InternalMessageInfo + +func (m *MsgMultiSend) GetInputs() []Input { + if m != nil { + return m.Inputs + } + return nil +} + +func (m *MsgMultiSend) GetOutputs() []Output { + if m != nil { + return m.Outputs + } + return nil +} + +// MsgMultiSendResponse defines the Msg/MultiSend response type. +type MsgMultiSendResponse struct { +} + +func (m *MsgMultiSendResponse) Reset() { *m = MsgMultiSendResponse{} } +func (m *MsgMultiSendResponse) String() string { return proto.CompactTextString(m) } +func (*MsgMultiSendResponse) ProtoMessage() {} +func (*MsgMultiSendResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1d8cb1613481f5b7, []int{3} +} +func (m *MsgMultiSendResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMultiSendResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMultiSendResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMultiSendResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMultiSendResponse.Merge(m, src) +} +func (m *MsgMultiSendResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgMultiSendResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMultiSendResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMultiSendResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgSend)(nil), "cosmos.bank.v1beta1.MsgSend") proto.RegisterType((*MsgSendResponse)(nil), "cosmos.bank.v1beta1.MsgSendResponse") + proto.RegisterType((*MsgMultiSend)(nil), "cosmos.bank.v1beta1.MsgMultiSend") + proto.RegisterType((*MsgMultiSendResponse)(nil), "cosmos.bank.v1beta1.MsgMultiSendResponse") } func init() { proto.RegisterFile("cosmos/bank/v1beta1/tx.proto", fileDescriptor_1d8cb1613481f5b7) } var fileDescriptor_1d8cb1613481f5b7 = []byte{ - // 346 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x49, 0xce, 0x2f, 0xce, - 0xcd, 0x2f, 0xd6, 0x4f, 0x4a, 0xcc, 0xcb, 0xd6, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, - 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0xc8, 0xea, 0x81, 0x64, 0xf5, - 0xa0, 0xb2, 0x52, 0x22, 0xe9, 0xf9, 0xe9, 0xf9, 0x60, 0x79, 0x7d, 0x10, 0x0b, 0xa2, 0x54, 0x4a, - 0x0e, 0x6e, 0x50, 0x71, 0x2a, 0xdc, 0xa0, 0xe4, 0xfc, 0xcc, 0x3c, 0x0c, 0x79, 0x24, 0x8b, 0xc0, - 0xe6, 0x82, 0xe5, 0x95, 0x5e, 0x31, 0x72, 0xb1, 0xfb, 0x16, 0xa7, 0x07, 0xa7, 0xe6, 0xa5, 0x08, - 0x59, 0x71, 0xf1, 0xa4, 0x15, 0xe5, 0xe7, 0xc6, 0x27, 0xa6, 0xa4, 0x14, 0xa5, 0x16, 0x17, 0x4b, - 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x3a, 0x89, 0x7f, 0xba, 0x27, 0x2f, 0x5c, 0x99, 0x98, 0x9b, 0x63, - 0xa5, 0x84, 0x2c, 0xab, 0x14, 0xc4, 0x0d, 0xe2, 0x3a, 0x42, 0x78, 0x42, 0x26, 0x5c, 0x5c, 0x25, - 0xf9, 0x70, 0x9d, 0x4c, 0x60, 0x9d, 0xa2, 0x9f, 0xee, 0xc9, 0x0b, 0x42, 0x74, 0x22, 0xe4, 0x94, - 0x82, 0x38, 0x4b, 0xf2, 0x61, 0xba, 0x92, 0xb9, 0xd8, 0x12, 0x73, 0xf3, 0x4b, 0xf3, 0x4a, 0x24, - 0x98, 0x15, 0x98, 0x35, 0xb8, 0x8d, 0x24, 0xf5, 0xe0, 0x3e, 0x2f, 0x4e, 0x85, 0xf9, 0x5c, 0xcf, - 0x39, 0x3f, 0x33, 0xcf, 0xc9, 0xe0, 0xc4, 0x3d, 0x79, 0x86, 0x55, 0xf7, 0xe5, 0x35, 0xd2, 0x33, - 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0xa1, 0x7e, 0x83, 0x50, 0xba, 0xc5, 0x29, - 0xd9, 0xfa, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x60, 0x0d, 0xc5, 0x41, 0x50, 0xa3, 0xad, 0x38, 0x3a, - 0x16, 0xc8, 0x33, 0xbc, 0x58, 0x20, 0xcf, 0xa0, 0x24, 0xc8, 0xc5, 0x0f, 0xf5, 0x6b, 0x50, 0x6a, - 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x51, 0x20, 0x17, 0xb3, 0x6f, 0x71, 0xba, 0x90, 0x17, 0x17, - 0x0b, 0x38, 0x08, 0x64, 0xf4, 0xb0, 0x04, 0xbd, 0x1e, 0x54, 0x93, 0x94, 0x0a, 0x3e, 0x59, 0x98, - 0x91, 0x4e, 0xce, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, - 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5, 0x89, 0xd7, - 0xed, 0x15, 0x90, 0x48, 0x02, 0x7b, 0x21, 0x89, 0x0d, 0x1c, 0x3d, 0xc6, 0x80, 0x00, 0x00, 0x00, - 0xff, 0xff, 0x8f, 0x4a, 0xcb, 0x2c, 0x29, 0x02, 0x00, 0x00, + // 436 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0x3f, 0xef, 0xd2, 0x40, + 0x1c, 0xc6, 0x7b, 0x3f, 0x08, 0x3f, 0x39, 0x48, 0x0c, 0x05, 0x15, 0x2b, 0x69, 0xb1, 0x71, 0x80, + 0xc1, 0xab, 0xa0, 0x83, 0xa9, 0x93, 0x65, 0xd2, 0xa4, 0x31, 0xa9, 0x93, 0x2e, 0xa6, 0x7f, 0xce, + 0xda, 0x40, 0x7b, 0x0d, 0x77, 0x35, 0xf0, 0x0e, 0x4c, 0x5c, 0x7c, 0x09, 0xcc, 0xc6, 0x17, 0xc2, + 0xc8, 0xe8, 0x84, 0x06, 0x16, 0xe3, 0xc8, 0x2b, 0x30, 0xbd, 0xfe, 0x81, 0x44, 0xc4, 0xa9, 0xbd, + 0x3c, 0xdf, 0xcf, 0xd3, 0xe7, 0xe9, 0xf7, 0x60, 0xcf, 0x25, 0x34, 0x24, 0x54, 0x73, 0xec, 0x68, + 0xaa, 0x7d, 0x1c, 0x39, 0x98, 0xd9, 0x23, 0x8d, 0x2d, 0x50, 0x3c, 0x27, 0x8c, 0x88, 0xed, 0x4c, + 0x45, 0xa9, 0x8a, 0x72, 0x55, 0xea, 0xf8, 0xc4, 0x27, 0x5c, 0xd7, 0xd2, 0xb7, 0x6c, 0x54, 0x92, + 0x4b, 0x23, 0x8a, 0x4b, 0x23, 0x97, 0x04, 0xd1, 0x5f, 0xfa, 0xc9, 0x87, 0xb8, 0x2f, 0xd7, 0xd5, + 0xdf, 0x00, 0x5e, 0x9b, 0xd4, 0x7f, 0x8d, 0x23, 0x4f, 0xd4, 0x61, 0xf3, 0xfd, 0x9c, 0x84, 0xef, + 0x6c, 0xcf, 0x9b, 0x63, 0x4a, 0xbb, 0xa0, 0x0f, 0x06, 0x75, 0xe3, 0xce, 0x61, 0xab, 0xb4, 0x97, + 0x76, 0x38, 0xd3, 0xd5, 0x53, 0x55, 0xb5, 0x1a, 0xe9, 0xf1, 0x79, 0x76, 0x12, 0x9f, 0x40, 0xc8, + 0x48, 0x49, 0x5e, 0x71, 0xf2, 0xd6, 0x61, 0xab, 0xb4, 0x32, 0xf2, 0xa8, 0xa9, 0x56, 0x9d, 0x91, + 0x82, 0x72, 0x61, 0xcd, 0x0e, 0x49, 0x12, 0xb1, 0x6e, 0xa5, 0x5f, 0x19, 0x34, 0xc6, 0x77, 0x51, + 0xd9, 0x9c, 0xe2, 0xa2, 0x39, 0x9a, 0x90, 0x20, 0x32, 0x1e, 0xad, 0xb7, 0x8a, 0xf0, 0xf5, 0x87, + 0x32, 0xf0, 0x03, 0xf6, 0x21, 0x71, 0x90, 0x4b, 0x42, 0x2d, 0xef, 0x96, 0x3d, 0x1e, 0x52, 0x6f, + 0xaa, 0xb1, 0x65, 0x8c, 0x29, 0x07, 0xa8, 0x95, 0x5b, 0xeb, 0x37, 0x3e, 0xad, 0x14, 0xe1, 0xd7, + 0x4a, 0x11, 0xd4, 0x16, 0xbc, 0x99, 0x77, 0xb5, 0x30, 0x8d, 0x49, 0x44, 0xb1, 0xfa, 0x19, 0xc0, + 0xa6, 0x49, 0x7d, 0x33, 0x99, 0xb1, 0x80, 0xff, 0x84, 0xa7, 0xb0, 0x16, 0x44, 0x71, 0xc2, 0xd2, + 0xfa, 0x69, 0x24, 0x09, 0x9d, 0x59, 0x06, 0x7a, 0x91, 0x8e, 0x18, 0xd5, 0x34, 0x93, 0x95, 0xcf, + 0x8b, 0xcf, 0xe0, 0x35, 0x49, 0x18, 0x47, 0xaf, 0x38, 0x7a, 0xef, 0x2c, 0xfa, 0x8a, 0xcf, 0xe4, + 0x6c, 0x41, 0xe8, 0x55, 0x1e, 0xf0, 0x36, 0xec, 0x9c, 0x86, 0x29, 0x52, 0x8e, 0xbf, 0x01, 0x58, + 0x31, 0xa9, 0x2f, 0xbe, 0x84, 0x55, 0x1e, 0xb2, 0x77, 0xd6, 0x39, 0xef, 0x26, 0x3d, 0xb8, 0xa4, + 0x16, 0x9e, 0xe2, 0x1b, 0x58, 0x3f, 0xb6, 0xbe, 0xff, 0x2f, 0xa4, 0x1c, 0x91, 0x86, 0xff, 0x1d, + 0x29, 0xac, 0x8d, 0xc9, 0x7a, 0x27, 0x83, 0xcd, 0x4e, 0x06, 0x3f, 0x77, 0x32, 0xf8, 0xb2, 0x97, + 0x85, 0xcd, 0x5e, 0x16, 0xbe, 0xef, 0x65, 0xe1, 0xed, 0xf0, 0xe2, 0xf6, 0x16, 0xd9, 0x35, 0xe5, + 0x4b, 0x74, 0x6a, 0xfc, 0x82, 0x3e, 0xfe, 0x13, 0x00, 0x00, 0xff, 0xff, 0x78, 0xdc, 0x7c, 0x0b, + 0x2b, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -154,6 +252,8 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { // Send defines a method for sending coins from one account to another account. Send(ctx context.Context, in *MsgSend, opts ...grpc.CallOption) (*MsgSendResponse, error) + // MultiSend defines a method for sending coins from some accounts to other accounts. + MultiSend(ctx context.Context, in *MsgMultiSend, opts ...grpc.CallOption) (*MsgMultiSendResponse, error) } type msgClient struct { @@ -173,10 +273,21 @@ func (c *msgClient) Send(ctx context.Context, in *MsgSend, opts ...grpc.CallOpti return out, nil } +func (c *msgClient) MultiSend(ctx context.Context, in *MsgMultiSend, opts ...grpc.CallOption) (*MsgMultiSendResponse, error) { + out := new(MsgMultiSendResponse) + err := c.cc.Invoke(ctx, "/cosmos.bank.v1beta1.Msg/MultiSend", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // Send defines a method for sending coins from one account to another account. Send(context.Context, *MsgSend) (*MsgSendResponse, error) + // MultiSend defines a method for sending coins from some accounts to other accounts. + MultiSend(context.Context, *MsgMultiSend) (*MsgMultiSendResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -186,6 +297,9 @@ type UnimplementedMsgServer struct { func (*UnimplementedMsgServer) Send(ctx context.Context, req *MsgSend) (*MsgSendResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") } +func (*UnimplementedMsgServer) MultiSend(ctx context.Context, req *MsgMultiSend) (*MsgMultiSendResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MultiSend not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -209,6 +323,24 @@ func _Msg_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{ return interceptor(ctx, in, info, handler) } +func _Msg_MultiSend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgMultiSend) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).MultiSend(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cosmos.bank.v1beta1.Msg/MultiSend", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).MultiSend(ctx, req.(*MsgMultiSend)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "cosmos.bank.v1beta1.Msg", HandlerType: (*MsgServer)(nil), @@ -217,6 +349,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "Send", Handler: _Msg_Send_Handler, }, + { + MethodName: "MultiSend", + Handler: _Msg_MultiSend_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "cosmos/bank/v1beta1/tx.proto", @@ -296,6 +432,80 @@ func (m *MsgSendResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *MsgMultiSend) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMultiSend) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMultiSend) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Outputs) > 0 { + for iNdEx := len(m.Outputs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Outputs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Inputs) > 0 { + for iNdEx := len(m.Inputs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Inputs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *MsgMultiSendResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMultiSendResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMultiSendResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -339,6 +549,36 @@ func (m *MsgSendResponse) Size() (n int) { return n } +func (m *MsgMultiSend) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Inputs) > 0 { + for _, e := range m.Inputs { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + if len(m.Outputs) > 0 { + for _, e := range m.Outputs { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgMultiSendResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -543,6 +783,174 @@ func (m *MsgSendResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgMultiSend) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMultiSend: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMultiSend: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Inputs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Inputs = append(m.Inputs, Input{}) + if err := m.Inputs[len(m.Inputs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Outputs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Outputs = append(m.Outputs, Output{}) + if err := m.Outputs[len(m.Outputs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMultiSendResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMultiSendResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMultiSendResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0