Skip to content

Commit

Permalink
fix: correctly coalesce coins even with repeated denominations(backport
Browse files Browse the repository at this point in the history
cosmos/cosmos-sdk#13265) (backport #1313) (#1320)

* fix: correctly coalesce coins even with repeated denominations(backport cosmos/cosmos-sdk#13265) (#1313)

* fix: correctly coalesce coins even with repeated denominations

* chore: update changelog

(cherry picked from commit 0176215)

# Conflicts:
#	CHANGELOG.md

* Update CHANGELOG.md

---------

Co-authored-by: jaeseung-bae <119839167+jaeseung-bae@users.noreply.github.com>
Co-authored-by: Youngtaek Yoon <noreply@yoon.mailer.me>
  • Loading branch information
3 people committed Mar 29, 2024
1 parent feb8821 commit a14fa3e
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 53 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -55,6 +55,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/staking) [\#1301](https://github.com/Finschia/finschia-sdk/pull/1301) Use bytes instead of string comparison in delete validator queue (backport cosmos/cosmos-sdk#12303)
* (client/keys) [#1312](https://github.com/Finschia/finschia-sdk/pull/1312) ignore error when key not found in `keys delete`
* (store) [\#1310](https://github.com/Finschia/finschia-sdk/pull/1310) fix app-hash mismatch if upgrade migration commit is interrupted(backport cosmos/cosmos-sdk#13530)
* (types) [\#1313](https://github.com/Finschia/finschia-sdk/pull/1313) fix correctly coalesce coins even with repeated denominations(backport cosmos/cosmos-sdk#13265)

### Removed

Expand Down
57 changes: 15 additions & 42 deletions types/coin.go
Expand Up @@ -292,7 +292,7 @@ func (coins Coins) Add(coinsB ...Coin) Coins {
// denomination and addition only occurs when the denominations match, otherwise
// the coin is simply added to the sum assuming it's not zero.
// The function panics if `coins` or `coinsB` are not sorted (ascending).
func (coins Coins) safeAdd(coinsB Coins) Coins {
func (coins Coins) safeAdd(coinsB Coins) (coalesced Coins) {
// probably the best way will be to make Coins and interface and hide the structure
// definition (type alias)
if !coins.isSorted() {
Expand All @@ -302,51 +302,24 @@ func (coins Coins) safeAdd(coinsB Coins) Coins {
panic("Wrong argument: coins must be sorted")
}

sum := ([]Coin)(nil)
indexA, indexB := 0, 0
lenA, lenB := len(coins), len(coinsB)

for {
if indexA == lenA {
if indexB == lenB {
// return nil coins if both sets are empty
return sum
}

// return set B (excluding zero coins) if set A is empty
return append(sum, removeZeroCoins(coinsB[indexB:])...)
} else if indexB == lenB {
// return set A (excluding zero coins) if set B is empty
return append(sum, removeZeroCoins(coins[indexA:])...)
uniqCoins := make(map[string]Coins, len(coins)+len(coinsB))
// Traverse all the coins for each of the coins and coinsB.
for _, cL := range []Coins{coins, coinsB} {
for _, c := range cL {
uniqCoins[c.Denom] = append(uniqCoins[c.Denom], c)
}
}

coinA, coinB := coins[indexA], coinsB[indexB]

switch strings.Compare(coinA.Denom, coinB.Denom) {
case -1: // coin A denom < coin B denom
if !coinA.IsZero() {
sum = append(sum, coinA)
}

indexA++

case 0: // coin A denom == coin B denom
res := coinA.Add(coinB)
if !res.IsZero() {
sum = append(sum, res)
}

indexA++
indexB++

case 1: // coin A denom > coin B denom
if !coinB.IsZero() {
sum = append(sum, coinB)
}

indexB++
for denom, cL := range uniqCoins {
comboCoin := Coin{Denom: denom, Amount: NewInt(0)}
for _, c := range cL {
comboCoin = comboCoin.Add(c)
}
if !comboCoin.IsZero() {
coalesced = append(coalesced, comboCoin)
}
}
return coalesced.Sort()
}

// DenomsSubsetOf returns true if receiver's denom set
Expand Down
48 changes: 39 additions & 9 deletions types/coin_test.go
Expand Up @@ -5,6 +5,7 @@ import (
"strings"
"testing"

"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/Finschia/finschia-sdk/codec"
Expand Down Expand Up @@ -417,21 +418,50 @@ func (s *coinTestSuite) TestEqualCoins() {

func (s *coinTestSuite) TestAddCoins() {
cases := []struct {
name string
inputOne sdk.Coins
inputTwo sdk.Coins
expected sdk.Coins
}{
{sdk.Coins{s.ca1, s.cm1}, sdk.Coins{s.ca1, s.cm1}, sdk.Coins{s.ca2, s.cm2}},
{sdk.Coins{s.ca0, s.cm1}, sdk.Coins{s.ca0, s.cm0}, sdk.Coins{s.cm1}},
{sdk.Coins{s.ca2}, sdk.Coins{s.cm0}, sdk.Coins{s.ca2}},
{sdk.Coins{s.ca1}, sdk.Coins{s.ca1, s.cm2}, sdk.Coins{s.ca2, s.cm2}},
{sdk.Coins{s.ca0, s.cm0}, sdk.Coins{s.ca0, s.cm0}, sdk.Coins(nil)},
{"{1atom,1muon}+{1atom,1muon}", sdk.Coins{s.ca1, s.cm1}, sdk.Coins{s.ca1, s.cm1}, sdk.Coins{s.ca2, s.cm2}},
{"{0atom,1muon}+{0atom,0muon}", sdk.Coins{s.ca0, s.cm1}, sdk.Coins{s.ca0, s.cm0}, sdk.Coins{s.cm1}},
{"{2atom}+{0muon}", sdk.Coins{s.ca2}, sdk.Coins{s.cm0}, sdk.Coins{s.ca2}},
{"{1atom}+{1atom,2muon}", sdk.Coins{s.ca1}, sdk.Coins{s.ca1, s.cm2}, sdk.Coins{s.ca2, s.cm2}},
{"{0atom,0muon}+{0atom,0muon}", sdk.Coins{s.ca0, s.cm0}, sdk.Coins{s.ca0, s.cm0}, sdk.Coins(nil)},
}

for tcIndex, tc := range cases {
res := tc.inputOne.Add(tc.inputTwo...)
s.Require().True(res.IsValid())
s.Require().Equal(tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex)
for _, tc := range cases {
s.T().Run(tc.name, func(t *testing.T) {
res := tc.inputOne.Add(tc.inputTwo...)
require.True(t, res.IsValid(), fmt.Sprintf("%s + %s = %s", tc.inputOne, tc.inputTwo, res))
require.Equal(t, tc.expected, res, "sum of coins is incorrect")
})
}
}

// Tests that even if coins with repeated denominations are passed into .Add that they
// are correctly coalesced. Please see issue https://github.com/cosmos/cosmos-sdk/issues/13234
func TestCoinsAddCoalescesDuplicateDenominations(t *testing.T) {
A := sdk.Coins{
{"den", sdk.NewInt(2)},
{"den", sdk.NewInt(3)},
}
B := sdk.Coins{
{"den", sdk.NewInt(3)},
{"den", sdk.NewInt(2)},
{"den", sdk.NewInt(1)},
}

A = A.Sort()
B = B.Sort()
got := A.Add(B...)

want := sdk.Coins{
{"den", sdk.NewInt(11)},
}

if !got.Equal(want) {
t.Fatalf("Wrong result\n\tGot: %s\n\tWant: %s", got, want)
}
}

Expand Down
2 changes: 1 addition & 1 deletion x/auth/vesting/types/vesting_account.go
Expand Up @@ -4,7 +4,7 @@ import (
"errors"
"time"

yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"

cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
sdk "github.com/Finschia/finschia-sdk/types"
Expand Down
2 changes: 1 addition & 1 deletion x/gov/types/params.go
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"time"

yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"

sdk "github.com/Finschia/finschia-sdk/types"
paramtypes "github.com/Finschia/finschia-sdk/x/params/types"
Expand Down

0 comments on commit a14fa3e

Please sign in to comment.