-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge PR #3807: Custom MsgMultiSend Handler (x/bank fork)
- Loading branch information
1 parent
805e7fb
commit bf7cbbb
Showing
5 changed files
with
328 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)}) | ||
} |