diff --git a/PENDING.md b/PENDING.md index a8a3efc45..361ca70eb 100644 --- a/PENDING.md +++ b/PENDING.md @@ -19,6 +19,10 @@ ### Gaia +* [\#3787] Fork the `x/bank` module into the Gaia application with only a +modified message handler, where the modified message handler behaves the same as +the standard `x/bank` message handler except for `MsgMultiSend` that must burn +exactly 9 atoms and transfer 1 atom, and `MsgSend` is disabled. * [\#3789] Update validator creation flow: * Remove `NewMsgCreateValidatorOnBehalfOf` and corresponding business logic * Ensure the validator address equals the delegator address during @@ -32,6 +36,7 @@ tags. * [\#3751] Disable (temporarily) support for ED25519 account key pairs. ### Tendermint + * [\#3804] Update to Tendermint `v0.31.0-dev0` diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index d5b031272..36044513f 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -11,6 +11,9 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + // TODO: Remove once transfers are enabled. + gaiabank "github.com/cosmos/cosmos-sdk/cmd/gaia/app/x/bank" + bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -149,8 +152,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b ) // register message routes + // + // TODO: Use standard bank router once transfers are enabled. app.Router(). - AddRoute(bank.RouterKey, bank.NewHandler(app.bankKeeper)). + AddRoute(bank.RouterKey, gaiabank.NewHandler(app.bankKeeper)). AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)). AddRoute(distr.RouterKey, distr.NewHandler(app.distrKeeper)). AddRoute(slashing.RouterKey, slashing.NewHandler(app.slashingKeeper)). diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 74e396962..65a64033b 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -11,13 +11,12 @@ import ( "strings" "time" - "github.com/cosmos/cosmos-sdk/x/bank" - tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" diff --git a/cmd/gaia/app/x/bank/handler.go b/cmd/gaia/app/x/bank/handler.go new file mode 100644 index 000000000..d3aa3415d --- /dev/null +++ b/cmd/gaia/app/x/bank/handler.go @@ -0,0 +1,100 @@ +// Package bank contains a forked version of the bank module. It only contains +// a modified message handler to support a very limited form of transfers during +// mainnet launch -- MsgMultiSend messages. +// +// NOTE: This fork should be removed entirely once transfers are enabled and +// the Gaia router should be reset to using the original bank module handler. +package bank + +import ( + "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +var ( + uatomDenom = "uatom" + atomsToUatoms = int64(1000000) + + // BurnedCoinsAccAddr represents the burn account address used for + // MsgMultiSend message during the period for which transfers are disabled. + // Its Bech32 address is cosmos1x4p90uuy63fqzsheamn48vq88q3eusykf0a69v. + BurnedCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("bankBurnedCoins"))) +) + +// NewHandler returns a handler for "bank" type messages. +func NewHandler(k bank.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case bank.MsgSend: + return handleMsgSend(ctx, k, msg) + + case bank.MsgMultiSend: + return handleMsgMultiSend(ctx, k, msg) + + default: + errMsg := "Unrecognized bank Msg type: %s" + msg.Type() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +// handleMsgSend implements a MsgSend message handler. It operates no differently +// than the standard bank module MsgSend message handler in that it transfers +// an amount from one account to another under the condition of transfers being +// enabled. +func handleMsgSend(ctx sdk.Context, k bank.Keeper, msg bank.MsgSend) sdk.Result { + // No need to modify handleMsgSend as the forked module requires no changes, + // so we can just call the standard bank modules handleMsgSend since we know + // the message is of type MsgSend. + return bank.NewHandler(k)(ctx, msg) +} + +// handleMsgMultiSend implements a modified forked version of a MsgMultiSend +// message handler. If transfers are disabled, a modified version of MsgMultiSend +// is allowed where there must be a single input and only two outputs. The first +// of the two outputs must be to a specific burn address defined by +// burnedCoinsAccAddr. In addition, the output amounts must be of 9atom and +// 1uatom respectively. +func handleMsgMultiSend(ctx sdk.Context, k bank.Keeper, msg bank.MsgMultiSend) sdk.Result { + // NOTE: totalIn == totalOut should already have been checked + if !k.GetSendEnabled(ctx) { + if !validateMultiSendTransfersDisabled(msg) { + return bank.ErrSendDisabled(k.Codespace()).Result() + } + } + + tags, err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) + if err != nil { + return err.Result() + } + + return sdk.Result{ + Tags: tags, + } +} + +func validateMultiSendTransfersDisabled(msg bank.MsgMultiSend) bool { + nineAtoms := sdk.Coins{sdk.NewInt64Coin(uatomDenom, 9*atomsToUatoms)} + oneAtom := sdk.Coins{sdk.NewInt64Coin(uatomDenom, 1*atomsToUatoms)} + + if len(msg.Inputs) != 1 { + return false + } + if len(msg.Outputs) != 2 { + return false + } + + if !msg.Outputs[0].Address.Equals(BurnedCoinsAccAddr) { + return false + } + if !msg.Outputs[0].Coins.IsEqual(nineAtoms) { + return false + } + if !msg.Outputs[1].Coins.IsEqual(oneAtom) { + return false + } + + return true +} diff --git a/cmd/gaia/app/x/bank/handler_test.go b/cmd/gaia/app/x/bank/handler_test.go new file mode 100644 index 000000000..c799e03f8 --- /dev/null +++ b/cmd/gaia/app/x/bank/handler_test.go @@ -0,0 +1,216 @@ +package bank + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/secp256k1" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" +) + +var ( + addrs = []sdk.AccAddress{ + sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()), + sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()), + sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()), + } + + initAmt = sdk.NewInt(atomsToUatoms * 100) +) + +type testInput struct { + ctx sdk.Context + accKeeper auth.AccountKeeper + bankKeeper bank.Keeper +} + +func newTestCodec() *codec.Codec { + cdc := codec.New() + + bank.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + + return cdc +} + +func createTestInput(t *testing.T) testInput { + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tKeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + + cdc := newTestCodec() + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ctx := sdk.NewContext(ms, abci.Header{Time: time.Now().UTC()}, false, log.NewNopLogger()) + + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tKeyParams, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + + require.NoError(t, ms.LoadLatestVersion()) + + paramsKeeper := params.NewKeeper(cdc, keyParams, tKeyParams) + accKeeper := auth.NewAccountKeeper( + cdc, + keyAcc, + paramsKeeper.Subspace(auth.DefaultParamspace), + auth.ProtoBaseAccount, + ) + + bankKeeper := bank.NewBaseKeeper( + accKeeper, + paramsKeeper.Subspace(bank.DefaultParamspace), + bank.DefaultCodespace, + ) + + for _, addr := range addrs { + _, _, err := bankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) + require.NoError(t, err) + } + + return testInput{ctx, accKeeper, bankKeeper} +} + +func TestHandlerMsgSendTransfersDisabled(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, false) + + handler := NewHandler(input.bankKeeper) + amt := sdk.NewInt(atomsToUatoms * 5) + msg := bank.NewMsgSend(addrs[0], addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, amt)}) + + res := handler(input.ctx, msg) + require.False(t, res.IsOK(), "expected failed message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) + + to := input.accKeeper.GetAccount(input.ctx, addrs[1]) + require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) +} + +func TestHandlerMsgSendTransfersEnabled(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, true) + + handler := NewHandler(input.bankKeeper) + amt := sdk.NewInt(atomsToUatoms * 5) + msg := bank.NewMsgSend(addrs[0], addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, amt)}) + + res := handler(input.ctx, msg) + require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + balance := initAmt.Sub(amt) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + to := input.accKeeper.GetAccount(input.ctx, addrs[1]) + balance = initAmt.Add(amt) + require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) +} + +func TestHandlerMsgMultiSendTransfersDisabledBurn(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, false) + + handler := NewHandler(input.bankKeeper) + totalAmt := sdk.NewInt(10 * atomsToUatoms) + burnAmt := sdk.NewInt(9 * atomsToUatoms) + transAmt := sdk.NewInt(1 * atomsToUatoms) + msg := bank.NewMsgMultiSend( + []bank.Input{ + bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}), + }, + []bank.Output{ + bank.NewOutput(BurnedCoinsAccAddr, sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)}), + bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, transAmt)}), + }, + ) + + res := handler(input.ctx, msg) + require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + balance := initAmt.Sub(totalAmt) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + to := input.accKeeper.GetAccount(input.ctx, addrs[1]) + balance = initAmt.Add(transAmt) + require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + burn := input.accKeeper.GetAccount(input.ctx, BurnedCoinsAccAddr) + require.Equal(t, burn.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)}) +} + +func TestHandlerMsgMultiSendTransfersDisabledInvalidBurn(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, false) + + handler := NewHandler(input.bankKeeper) + totalAmt := sdk.NewInt(15 * atomsToUatoms) + burnAmt := sdk.NewInt(10 * atomsToUatoms) + transAmt := sdk.NewInt(5 * atomsToUatoms) + msg := bank.NewMsgMultiSend( + []bank.Input{ + bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}), + }, + []bank.Output{ + bank.NewOutput(BurnedCoinsAccAddr, sdk.Coins{sdk.NewCoin(uatomDenom, burnAmt)}), + bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, transAmt)}), + }, + ) + + res := handler(input.ctx, msg) + require.False(t, res.IsOK(), "expected failed message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) + + to := input.accKeeper.GetAccount(input.ctx, addrs[1]) + require.Equal(t, to.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, initAmt)}) +} + +func TestHandlerMsgMultiSendTransfersEnabled(t *testing.T) { + input := createTestInput(t) + input.bankKeeper.SetSendEnabled(input.ctx, true) + + handler := NewHandler(input.bankKeeper) + totalAmt := sdk.NewInt(10 * atomsToUatoms) + outAmt := sdk.NewInt(5 * atomsToUatoms) + msg := bank.NewMsgMultiSend( + []bank.Input{ + bank.NewInput(addrs[0], sdk.Coins{sdk.NewCoin(uatomDenom, totalAmt)}), + }, + []bank.Output{ + bank.NewOutput(addrs[1], sdk.Coins{sdk.NewCoin(uatomDenom, outAmt)}), + bank.NewOutput(addrs[2], sdk.Coins{sdk.NewCoin(uatomDenom, outAmt)}), + }, + ) + + res := handler(input.ctx, msg) + require.True(t, res.IsOK(), "expected successful message execution: %v", res.Log) + + from := input.accKeeper.GetAccount(input.ctx, addrs[0]) + balance := initAmt.Sub(totalAmt) + require.Equal(t, from.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + out1 := input.accKeeper.GetAccount(input.ctx, addrs[1]) + balance = initAmt.Add(outAmt) + require.Equal(t, out1.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) + + out2 := input.accKeeper.GetAccount(input.ctx, addrs[2]) + balance = initAmt.Add(outAmt) + require.Equal(t, out2.GetCoins(), sdk.Coins{sdk.NewCoin(uatomDenom, balance)}) +}