Merge pull request #3213 from cosmos/hotfix/v0.29.1/fix-coin-validity-checks
R4R: Hotfix v0.29.1 for GoS
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## 0.29.1
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* SDK
|
||||
* [\#3207](https://github.com/cosmos/cosmos-sdk/issues/3207) - Fix token printing bug
|
||||
|
||||
## 0.29.0
|
||||
|
||||
BREAKING CHANGES
|
||||
|
||||
+5
-1
@@ -140,8 +140,12 @@ func (coins Coins) IsValid() bool {
|
||||
case 1:
|
||||
return coins[0].IsPositive()
|
||||
default:
|
||||
lowDenom := coins[0].Denom
|
||||
// check single coin case
|
||||
if !(Coins{coins[0]}).IsValid() {
|
||||
return false
|
||||
}
|
||||
|
||||
lowDenom := coins[0].Denom
|
||||
for _, coin := range coins[1:] {
|
||||
if coin.Denom <= lowDenom {
|
||||
return false
|
||||
|
||||
+13
-2
@@ -251,8 +251,13 @@ func TestMinusCoins(t *testing.T) {
|
||||
|
||||
func TestCoins(t *testing.T) {
|
||||
good := Coins{
|
||||
{"GAS", NewInt(1)},
|
||||
{"MINERAL", NewInt(1)},
|
||||
{"gas", NewInt(1)},
|
||||
{"mineral", NewInt(1)},
|
||||
{"tree", NewInt(1)},
|
||||
}
|
||||
mixedCase1 := Coins{
|
||||
{"gAs", NewInt(1)},
|
||||
{"MineraL", NewInt(1)},
|
||||
{"TREE", NewInt(1)},
|
||||
}
|
||||
empty := Coins{
|
||||
@@ -281,8 +286,13 @@ func TestCoins(t *testing.T) {
|
||||
{"GAS", NewInt(1)},
|
||||
{"MINERAL", NewInt(1)},
|
||||
}
|
||||
neg := Coins{
|
||||
{"gas", NewInt(-1)},
|
||||
{"mineral", NewInt(1)},
|
||||
}
|
||||
|
||||
assert.True(t, good.IsValid(), "Coins are valid")
|
||||
assert.False(t, mixedCase1.IsValid(), "Coins denoms contain upper case characters")
|
||||
assert.True(t, good.IsPositive(), "Expected coins to be positive: %v", good)
|
||||
assert.False(t, null.IsPositive(), "Expected coins to not be positive: %v", null)
|
||||
assert.True(t, good.IsAllGTE(empty), "Expected %v to be >= %v", good, empty)
|
||||
@@ -292,6 +302,7 @@ func TestCoins(t *testing.T) {
|
||||
assert.False(t, badSort2.IsValid(), "Coins are not sorted")
|
||||
assert.False(t, badAmt.IsValid(), "Coins cannot include 0 amounts")
|
||||
assert.False(t, dup.IsValid(), "Duplicate coin")
|
||||
assert.False(t, neg.IsValid(), "Negative first-denom coin")
|
||||
}
|
||||
|
||||
func TestCoinsGT(t *testing.T) {
|
||||
|
||||
+12
-1
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -108,7 +107,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) {
|
||||
@@ -146,8 +144,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)})
|
||||
assert.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)}))
|
||||
|
||||
@@ -156,6 +154,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) {
|
||||
|
||||
+32
-19
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user