Skip to content

Commit

Permalink
Add IsValid check on sendCoins (#3212)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez authored and jackzampolin committed Jan 2, 2019
1 parent a98a5a0 commit 990f3ab
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 28 deletions.
3 changes: 2 additions & 1 deletion types/coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,11 @@ func (coins Coins) IsValid() bool {
}
return coins[0].IsPositive()
default:
// Check single coin case
// check single coin case
if !(Coins{coins[0]}).IsValid() {
return false
}

lowDenom := coins[0].Denom
for _, coin := range coins[1:] {
if strings.ToLower(coin.Denom) != coin.Denom {
Expand Down
13 changes: 12 additions & 1 deletion x/bank/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,13 @@ func addCoins(ctx sdk.Context, am auth.AccountKeeper, addr sdk.AccAddress, amt s
}

// SendCoins moves coins from one account to another
// NOTE: Make sure to revert state changes from tx on error
func sendCoins(ctx sdk.Context, am auth.AccountKeeper, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) {
// Safety check ensuring that when sending coins the keeper must maintain the
// supply invariant.
if !amt.IsValid() {
return nil, sdk.ErrInvalidCoins(amt.String())
}

_, subTags, err := subtractCoins(ctx, am, fromAddr, amt)
if err != nil {
return nil, err
Expand All @@ -212,6 +217,12 @@ func sendCoins(ctx sdk.Context, am auth.AccountKeeper, fromAddr sdk.AccAddress,
// InputOutputCoins handles a list of inputs and outputs
// NOTE: Make sure to revert state changes from tx on error
func inputOutputCoins(ctx sdk.Context, am auth.AccountKeeper, inputs []Input, outputs []Output) (sdk.Tags, sdk.Error) {
// Safety check ensuring that when sending coins the keeper must maintain the
// supply invariant.
if err := ValidateInputsOutputs(inputs, outputs); err != nil {
return nil, err
}

allTags := sdk.EmptyTags()

for _, in := range inputs {
Expand Down
19 changes: 12 additions & 7 deletions x/bank/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package bank
import (
"testing"

"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
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/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
)

type testInput struct {
Expand Down Expand Up @@ -126,7 +127,6 @@ func TestKeeper(t *testing.T) {
require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 21), sdk.NewInt64Coin("foocoin", 4)}))
require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 7), sdk.NewInt64Coin("foocoin", 6)}))
require.True(t, bankKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5)}))

}

func TestSendKeeper(t *testing.T) {
Expand Down Expand Up @@ -159,8 +159,8 @@ func TestSendKeeper(t *testing.T) {
require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}))
require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 5)}))

_, err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 50)})
require.Implements(t, (*sdk.Error)(nil), err2)
_, err := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 50)})
require.Implements(t, (*sdk.Error)(nil), err)
require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}))
require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 5)}))

Expand All @@ -169,6 +169,11 @@ func TestSendKeeper(t *testing.T) {
require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 5)}))
require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 10)}))

// validate coins with invalid denoms or negative values cannot be sent
// NOTE: We must use the Coin literal as the constructor does not allow
// negative values.
_, err = sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.Coin{"FOOCOIN", sdk.NewInt(-5)}})
require.Error(t, err)
}

func TestViewKeeper(t *testing.T) {
Expand Down
51 changes: 32 additions & 19 deletions x/bank/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,8 @@ func (msg MsgSend) ValidateBasic() sdk.Error {
if len(msg.Outputs) == 0 {
return ErrNoOutputs(DefaultCodespace).TraceSDK("")
}
// make sure all inputs and outputs are individually valid
var totalIn, totalOut sdk.Coins
for _, in := range msg.Inputs {
if err := in.ValidateBasic(); err != nil {
return err.TraceSDK("")
}
totalIn = totalIn.Plus(in.Coins)
}
for _, out := range msg.Outputs {
if err := out.ValidateBasic(); err != nil {
return err.TraceSDK("")
}
totalOut = totalOut.Plus(out.Coins)
}
// make sure inputs and outputs match
if !totalIn.IsEqual(totalOut) {
return sdk.ErrInvalidCoins(totalIn.String()).TraceSDK("inputs and outputs don't match")
}
return nil

return ValidateInputsOutputs(msg.Inputs, msg.Outputs)
}

// Implements Msg.
Expand Down Expand Up @@ -170,3 +153,33 @@ func NewOutput(addr sdk.AccAddress, coins sdk.Coins) Output {
}
return output
}

// ----------------------------------------------------------------------------
// Auxiliary

// 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) sdk.Error {
var totalIn, totalOut sdk.Coins

for _, in := range inputs {
if err := in.ValidateBasic(); err != nil {
return err.TraceSDK("")
}
totalIn = totalIn.Plus(in.Coins)
}

for _, out := range outputs {
if err := out.ValidateBasic(); err != nil {
return err.TraceSDK("")
}
totalOut = totalOut.Plus(out.Coins)
}

// make sure inputs and outputs match
if !totalIn.IsEqual(totalOut) {
return sdk.ErrInvalidCoins(totalIn.String()).TraceSDK("inputs and outputs don't match")
}

return nil
}

0 comments on commit 990f3ab

Please sign in to comment.