diff --git a/types/coin.go b/types/coin.go index ca63fa32a634..c8b7c77db77d 100644 --- a/types/coin.go +++ b/types/coin.go @@ -390,19 +390,65 @@ func (coins Coins) SafeSub(coinsB Coins) (Coins, bool) { return diff, diff.IsAnyNegative() } -// Max returns the maximum of each denom of its inputs. -// The inputs should be sorted. Note that the max might -// not be equal to either of its inputs. Uses multiset -// semantics, so unmentioned denoms are implicitly zero. +// Max takes two valid Coins inputs and returns a valid Coins result +// where for every denom D, AmountOf(D) of the result is the maximum +// of AmountOf(D) of the inputs. Note that the result might be not +// be equal to either input. For any valid Coins a, b, and c, the +// following are always true: +// a.IsAllLTE(a.Max(b)) +// b.IsAllLTE(a.Max(b)) +// a.IsAllLTE(c) && b.IsAllLTE(c) == a.Max(b).IsAllLTE(c) +// a.Add(b...).IsEqual(a.Min(b).Add(a.Max(b)...)) +// +// E.g. +// {1A, 3B, 2C}.Max({4A, 2B, 2C} == {4A, 3B, 2C}) +// {2A, 3B}.Max({1B, 4C}) == {2A, 3B, 4C} +// {1A, 2B}.Max({}) == {1A, 2B} func (coins Coins) Max(coinsB Coins) Coins { - // coins + coinsB == min(coins, coinsB) + max(coins, coinsB) - return coins.Add(coinsB...).Sub(coins.Min(coinsB)) + max := make([]Coin, 0) + indexA, indexB := 0, 0 + for indexA < len(coins) && indexB < len(coinsB) { + coinA, coinB := coins[indexA], coinsB[indexB] + switch strings.Compare(coinA.Denom, coinB.Denom) { + case -1: // denom missing from coinsB + max = append(max, coinA) + indexA++ + case 0: // same denom in both + maxCoin := coinA + if coinB.Amount.GT(maxCoin.Amount) { + maxCoin = coinB + } + max = append(max, maxCoin) + indexA++ + indexB++ + case 1: // denom missing from coinsA + max = append(max, coinB) + indexB++ + } + } + for ; indexA < len(coins); indexA++ { + max = append(max, coins[indexA]) + } + for ; indexB < len(coinsB); indexB++ { + max = append(max, coinsB[indexB]) + } + return NewCoins(max...) } -// Min returns the minimum of each denom of its inputs. -// The inputs should be sorted. Note that the min might -// not be equal to either of its inputs. Uses multiset -// semantics, so unmentioned denoms are implicitly zero. +// Min takes two valid Coins inputs and returns a valid Coins result +// where for every denom D, AmountOf(D) of the result is the minimum +// of AmountOf(D) of the inputs. Note that the result might be not +// be equal to either input. For any valid Coins a, b, and c, the +// following are always true: +// a.Min(b).IsAllLTE(a) +// a.Min(b).IsAllLTE(b) +// c.IsAllLTE(a) && c.IsAllLTE(b) == c.IsAllLTE(a.Min(b)) +// a.Add(b...).IsEqual(a.Min(b).Add(a.Max(b)...)) +// +// E.g. +// {1A, 3B, 2C}.Min({4A, 2B, 2C} == {1A, 2B, 2C}) +// {2A, 3B}.Min({1B, 4C}) == {1B} +// {1A, 2B}.Min({3C}) == empty func (coins Coins) Min(coinsB Coins) Coins { min := make([]Coin, 0) for indexA, indexB := 0, 0; indexA < len(coins) && indexB < len(coinsB); { diff --git a/types/coin_test.go b/types/coin_test.go index f82b581aa691..11567bd3a5e4 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -679,8 +679,10 @@ func (s *coinTestSuite) TestMinMax() { for _, tc := range cases { min := tc.input1.Min(tc.input2) max := tc.input1.Max(tc.input2) + intersect := tc.input1.Intersect(tc.input2) s.Require().True(min.IsEqual(tc.min), tc.name) s.Require().True(max.IsEqual(tc.max), tc.name) + s.Require().True(intersect.IsEqual(tc.min), tc.name) } }