diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 7aa6960a2..b4277f2a8 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -338,7 +338,7 @@ func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec) (net.Liste return nil, err } - go tmrpc.StartHTTPServer(listener, createHandler(cdc), logger, &tmrpc.Config{}) + go tmrpc.StartHTTPServer(listener, createHandler(cdc), logger, tmrpc.DefaultConfig()) return listener, nil } diff --git a/go.mod b/go.mod index 749ce329b..eb9dc1777 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( replace ( github.com/tendermint/go-amino => github.com/binance-chain/bnc-go-amino v0.14.1-binance.2 - github.com/tendermint/iavl => github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.3 + github.com/tendermint/iavl => github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4 github.com/tendermint/tendermint => github.com/binance-chain/bnc-tendermint v0.32.3-binance.1 github.com/zondax/ledger-cosmos-go => github.com/binance-chain/ledger-cosmos-go v0.9.9-binance.3 golang.org/x/crypto => github.com/tendermint/crypto v0.0.0-20190823143015-45b1026d81ae diff --git a/go.sum b/go.sum index dc769ca52..b54699b14 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/binance-chain/bnc-go-amino v0.14.1-binance.2 h1:XcbcfisVItk92UKoGbtNT github.com/binance-chain/bnc-go-amino v0.14.1-binance.2/go.mod h1:yaElUUxWtv/TC/ldGtlKAvS1vKwokxgJ1d97I+6is80= github.com/binance-chain/bnc-tendermint v0.32.3-binance.1 h1:LDGvORYLSwsTEQM0W7yrbdgjrAZxQDe18WUTLNuFOEc= github.com/binance-chain/bnc-tendermint v0.32.3-binance.1/go.mod h1:kN5dNxE8voFtDqx2HjbM8sBIH5cUuMtLg0XEHjqzUiY= -github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.3 h1:OyTJet9aGz+c2WKpTf5cAvJNiQeqVFYP4AV9cPpro2M= -github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.3/go.mod h1:Zmh8GRdNJB8DULIOBar3JCZp6tSpcvM1NGKfE9U2EzA= +github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4 h1:BhaV2iiGWfRC6iB8HHOYJeUDwtQMB2pUA4ah+KCbBhI= +github.com/binance-chain/bnc-tendermint-iavl v0.12.0-binance.4/go.mod h1:Zmh8GRdNJB8DULIOBar3JCZp6tSpcvM1NGKfE9U2EzA= github.com/binance-chain/ledger-cosmos-go v0.9.9-binance.3 h1:FFpFbkzlP2HUyxQCm0eoU6mkfgMNynfqZRbeWqlaLdQ= github.com/binance-chain/ledger-cosmos-go v0.9.9-binance.3/go.mod h1:TULULYTvPuWBxFIZFy6KjJaxJzbHeUderYNB1YhD6N0= github.com/binance-chain/tss v0.1.2 h1:AyTedSG5HG/WAvM9PDPWjTXQ+dvNdHg3x1c+1a584PQ= diff --git a/types/upgrade.go b/types/upgrade.go index 5857a4c1f..21d05dd68 100644 --- a/types/upgrade.go +++ b/types/upgrade.go @@ -5,6 +5,7 @@ import "fmt" var UpgradeMgr = NewUpgradeManager(UpgradeConfig{}) const FixSignBytesOverflow = "FixSignBytesOverflow" // fix json unmarshal overflow when build SignBytes +const BEP8 = "BEP8" // Mini-BEP2 token var MainNetConfig = UpgradeConfig{ HeightMap: map[string]int64{}, diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index ee252775c..3f974857e 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -43,11 +43,6 @@ func (tx StdTx) GetMsgs() []sdk.Msg { return tx.Msgs } // in the order they appear in tx.GetMsgs(). // Duplicate addresses will be omitted. func (tx StdTx) GetSigners() []sdk.AccAddress { - // shortcut for most cases - if len(tx.GetMsgs()) == 1 { - return tx.GetMsgs()[0].GetSigners() - } - seen := map[string]bool{} var signers []sdk.AccAddress for _, msg := range tx.GetMsgs() { diff --git a/x/bank/handler.go b/x/bank/handler.go index 2e9306292..8454f45d7 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -30,6 +30,15 @@ func handleMsgSend(ctx sdk.Context, k Keeper, msg MsgSend) sdk.Result { return err.Result() } } + + if sdk.IsUpgrade(sdk.BEP8) { + am := k.GetAccountKeeper() + for _, in := range msg.Inputs { + if err := checkAndValidateMiniTokenCoins(ctx, am, in.Address, in.Coins); err != nil { + return err.Result() + } + } + } // NOTE: totalIn == totalOut should already have been checked tags, err := k.InputOutputCoins(ctx, msg.Inputs, msg.Outputs) if err != nil { diff --git a/x/bank/handler_test.go b/x/bank/handler_test.go new file mode 100644 index 000000000..3925dce1b --- /dev/null +++ b/x/bank/handler_test.go @@ -0,0 +1,124 @@ +package bank + +import ( + "github.com/stretchr/testify/require" + "testing" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +func setup() (sdk.Context, sdk.Handler, BaseKeeper, auth.AccountKeeper) { + ms, authKey := setupMultiStore() + + cdc := codec.New() + auth.RegisterBaseAccount(cdc) + accountCache := getAccountCache(cdc, ms, authKey) + + ctx := sdk.NewContext(ms, abci.Header{}, sdk.RunTxModeDeliver, log.NewNopLogger()).WithAccountCache(accountCache) + accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) + bankKeeper := NewBaseKeeper(accountKeeper) + handler := NewHandler(bankKeeper) + sdk.UpgradeMgr.AddUpgradeHeight(sdk.BEP8, int64(-1)) + return ctx, handler, bankKeeper, accountKeeper +} + +func TestHandleSendToken(t *testing.T) { + ctx, handler, bankKeeper, accountKeeper := setup() + + ctx = ctx.WithValue(baseapp.TxHashKey, "000") + addr := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + acc := accountKeeper.NewAccountWithAddress(ctx, addr) + acc2 := accountKeeper.NewAccountWithAddress(ctx, addr2) + + accountKeeper.SetAccount(ctx, acc) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) + + //transfer BEP2 token Successfully + bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewCoin("NNB-000", 100)}) + + msg := createSendMsg(acc.GetAddress(), acc2.GetAddress(), sdk.Coins{sdk.NewCoin("NNB-000", 60)}) + sdkResult := handler(ctx, msg) + require.Equal(t, true, sdkResult.Code.IsOK()) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin("NNB-000", 40)})) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin("NNB-000", 60)})) +} + +func TestHandleSendMiniToken(t *testing.T) { + ctx, handler, bankKeeper, accountKeeper := setup() + + ctx = ctx.WithValue(baseapp.TxHashKey, "000") + addr := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + addr3 := sdk.AccAddress([]byte("addr3")) + acc := accountKeeper.NewAccountWithAddress(ctx, addr) + acc2 := accountKeeper.NewAccountWithAddress(ctx, addr2) + + accountKeeper.SetAccount(ctx, acc) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) + + //Transfer Mini token + //Fail to Transfer with value < 1e8 + MiniTokenFoo := "foocoin-000M" + bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewCoin(MiniTokenFoo, 10e8)}) + msg := createSendMsg(acc.GetAddress(), acc2.GetAddress(), sdk.Coins{sdk.NewCoin(MiniTokenFoo, 2)}) + sdkResult := handler(ctx, msg) + require.Equal(t, false, sdkResult.Code.IsOK()) + require.Contains(t, sdkResult.Log, "transfer amount is too small") + + //Success with amount >= 1e8 + msg = createSendMsg(acc.GetAddress(), acc2.GetAddress(), sdk.Coins{sdk.NewCoin(MiniTokenFoo, 1e8)}) + sdkResult = handler(ctx, msg) + require.Equal(t, true, sdkResult.Code.IsOK()) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin(MiniTokenFoo, 9e8)})) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewCoin(MiniTokenFoo, 1e8)})) + + //Fail to Multisend + MiniTokenBar := "barcoin-000M" + bankKeeper.SetCoins(ctx, addr2, sdk.Coins{sdk.NewCoin(MiniTokenBar, 10), sdk.NewCoin(MiniTokenFoo, 1e8)}) + + inputs := []Input{ + NewInput(addr, sdk.Coins{sdk.NewCoin(MiniTokenFoo, 3e8)}), + NewInput(addr2, sdk.Coins{sdk.NewCoin(MiniTokenBar, 3), sdk.NewCoin(MiniTokenFoo, 1e8)}), + } + + outputs := []Output{ + NewOutput(addr, sdk.Coins{sdk.NewCoin(MiniTokenBar, 1)}), + NewOutput(addr3, sdk.Coins{sdk.NewCoin(MiniTokenBar, 2), sdk.NewCoin(MiniTokenFoo, 4e8)}), + } + msg = NewMsgSend(inputs, outputs) + sdkResult = handler(ctx, msg) + require.Equal(t, false, sdkResult.Code.IsOK()) + require.Contains(t, sdkResult.Log, "transfer amount is too small") + + // + //Success with all balance + inputs = []Input{ + NewInput(addr, sdk.Coins{sdk.NewCoin(MiniTokenFoo, 3e8)}), + NewInput(addr2, sdk.Coins{sdk.NewCoin(MiniTokenBar, 10), sdk.NewCoin(MiniTokenFoo, 1e8)}), + } + + outputs = []Output{ + NewOutput(addr, sdk.Coins{sdk.NewCoin(MiniTokenBar, 4)}), + NewOutput(addr3, sdk.Coins{sdk.NewCoin(MiniTokenBar, 6), sdk.NewCoin(MiniTokenFoo, 4e8)}), + } + msg = NewMsgSend(inputs, outputs) + sdkResult = handler(ctx, msg) + require.Equal(t, true, sdkResult.Code.IsOK()) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewCoin(MiniTokenBar, 4), sdk.NewCoin(MiniTokenFoo, 6e8)})) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{})) + require.True(t, bankKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{sdk.NewCoin(MiniTokenBar, 6), sdk.NewCoin(MiniTokenFoo, 4e8)})) +} + +func createSendMsg(from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) sdk.Msg { + input := NewInput(from, coins) + output := NewOutput(to, coins) + msg := NewMsgSend([]Input{input}, []Output{output}) + return msg +} diff --git a/x/bank/keeper.go b/x/bank/keeper.go index 2ba5d8721..6191b63d0 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -14,6 +14,7 @@ type Keeper interface { SetCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) sdk.Error SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) + GetAccountKeeper() auth.AccountKeeper } var _ Keeper = (*BaseKeeper)(nil) @@ -52,6 +53,10 @@ func (keeper BaseKeeper) SubtractCoins( return subtractCoins(ctx, keeper.am, addr, amt) } +func (keeper BaseKeeper) GetAccountKeeper() auth.AccountKeeper { + return keeper.am +} + // AddCoins adds amt to the coins at the addr. func (keeper BaseKeeper) AddCoins( ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 49d9363bd..a44429dff 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -9,7 +9,7 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" - codec "github.com/cosmos/cosmos-sdk/codec" + "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" diff --git a/x/bank/mini_token_helper.go b/x/bank/mini_token_helper.go new file mode 100644 index 000000000..f3914c720 --- /dev/null +++ b/x/bank/mini_token_helper.go @@ -0,0 +1,74 @@ +package bank + +import ( + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +const ( + MiniTokenSymbolSuffixLen = 4 // probably enough. if it collides (unlikely) the issuer can just use another tx. + MiniTokenSymbolMSuffix = "M" + MiniTokenMinExecutionAmount int64 = 100000000 // 1 with 8 decimal digits +) + +func checkAndValidateMiniTokenCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, coins sdk.Coins) sdk.Error { + var err sdk.Error + for _, coin := range coins { + if isMiniTokenSymbol(coin.Denom) { + err = validateMiniTokenAmount(ctx, am, addr, coin) + } + if err != nil { + return err + } + } + return nil +} + +func isMiniTokenSymbol(symbol string) bool { + parts, err := splitSuffixedTokenSymbol(symbol) + if err != nil { + return false + } + suffixPart := parts[1] + + return len(suffixPart) == MiniTokenSymbolSuffixLen && strings.HasSuffix(suffixPart, MiniTokenSymbolMSuffix) +} + +func validateMiniTokenAmount(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, coin sdk.Coin) sdk.Error { + if MiniTokenMinExecutionAmount <= coin.Amount { + return nil + } + + coins := getCoins(ctx, am, addr) + balance := coins.AmountOf(coin.Denom) + if balance < coin.Amount { + return sdk.ErrInsufficientCoins("not enough token to send") + } + + useAllBalance := balance == coin.Amount + + if !useAllBalance { + return sdk.ErrInvalidCoins(fmt.Sprintf("transfer amount is too small, the min amount is %d or total account balance", + MiniTokenMinExecutionAmount)) + } + + return nil +} + +func splitSuffixedTokenSymbol(suffixed string) ([]string, error) { + + split := strings.SplitN(suffixed, "-", 2) + + if len(split) != 2 { + return nil, fmt.Errorf("suffixed token symbol must contain a hyphen ('-')") + } + + if strings.Contains(split[1], "-") { + return nil, fmt.Errorf("suffixed token symbol must contain just one hyphen ('-')") + } + + return split, nil +}