-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Move FormatCoins to
core
(#13306)
* refactor: Move FormatCoins to `core` * Rename to shorter names * Add chaneglog * Don't panic * add comments * go mod tidy * fix changelog * move structs to top * Fix test * go mod tidy * Refactor tests
- Loading branch information
1 parent
ff4f995
commit 10ac33e
Showing
17 changed files
with
463 additions
and
311 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package coins | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
"strings" | ||
|
||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" | ||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" | ||
"cosmossdk.io/math" | ||
) | ||
|
||
// formatCoin formats a sdk.Coin into a value-rendered string, using the | ||
// given metadata about the denom. It returns the formatted coin string, the | ||
// display denom, and an optional error. | ||
func formatCoin(coin *basev1beta1.Coin, metadata *bankv1beta1.Metadata) (string, error) { | ||
coinDenom := coin.Denom | ||
|
||
// Return early if no display denom or display denom is the current coin denom. | ||
if metadata == nil || metadata.Display == "" || coinDenom == metadata.Display { | ||
vr, err := math.FormatDec(coin.Amount) | ||
return vr + " " + coin.Denom, err | ||
} | ||
|
||
dispDenom := metadata.Display | ||
|
||
// Find exponents of both denoms. | ||
var coinExp, dispExp uint32 | ||
foundCoinExp, foundDispExp := false, false | ||
for _, unit := range metadata.DenomUnits { | ||
if coinDenom == unit.Denom { | ||
coinExp = unit.Exponent | ||
foundCoinExp = true | ||
} | ||
if dispDenom == unit.Denom { | ||
dispExp = unit.Exponent | ||
foundDispExp = true | ||
} | ||
} | ||
|
||
// If we didn't find either exponent, then we return early. | ||
if !foundCoinExp || !foundDispExp { | ||
vr, err := math.FormatInt(coin.Amount) | ||
return vr + " " + coin.Denom, err | ||
} | ||
|
||
exponentDiff := int64(coinExp) - int64(dispExp) | ||
|
||
dispAmount, err := math.LegacyNewDecFromStr(coin.Amount) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if exponentDiff > 0 { | ||
dispAmount = dispAmount.Mul(math.LegacyNewDec(10).Power(uint64(exponentDiff))) | ||
} else { | ||
dispAmount = dispAmount.Quo(math.LegacyNewDec(10).Power(uint64(-exponentDiff))) | ||
} | ||
|
||
vr, err := math.FormatDec(dispAmount.String()) | ||
return vr + " " + dispDenom, err | ||
} | ||
|
||
// formatCoins formats Coins into a value-rendered string, which uses | ||
// `formatCoin` separated by ", " (a comma and a space), and sorted | ||
// alphabetically by value-rendered denoms. It expects an array of metadata | ||
// (optionally nil), where each metadata at index `i` MUST match the coin denom | ||
// at the same index. | ||
func FormatCoins(coins []*basev1beta1.Coin, metadata []*bankv1beta1.Metadata) (string, error) { | ||
if len(coins) != len(metadata) { | ||
return "", fmt.Errorf("formatCoins expect one metadata for each coin; expected %d, got %d", len(coins), len(metadata)) | ||
} | ||
|
||
formatted := make([]string, len(coins)) | ||
for i, coin := range coins { | ||
var err error | ||
formatted[i], err = formatCoin(coin, metadata[i]) | ||
if err != nil { | ||
return "", err | ||
} | ||
} | ||
|
||
// Sort the formatted coins by display denom. | ||
sort.SliceStable(formatted, func(i, j int) bool { | ||
denomI := strings.Split(formatted[i], " ")[1] | ||
denomJ := strings.Split(formatted[j], " ")[1] | ||
|
||
return denomI < denomJ | ||
}) | ||
|
||
return strings.Join(formatted, ", "), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package coins_test | ||
|
||
import ( | ||
"encoding/json" | ||
"os" | ||
"testing" | ||
|
||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" | ||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" | ||
"cosmossdk.io/core/coins" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// coinsJsonTest is the type of test cases in the coin.json file. | ||
type coinJsonTest struct { | ||
Proto *basev1beta1.Coin | ||
Metadata *bankv1beta1.Metadata | ||
Text string | ||
Error bool | ||
} | ||
|
||
// coinsJsonTest is the type of test cases in the coins.json file. | ||
type coinsJsonTest struct { | ||
Proto []*basev1beta1.Coin | ||
Metadata map[string]*bankv1beta1.Metadata | ||
Text string | ||
Error bool | ||
} | ||
|
||
func TestFormatCoin(t *testing.T) { | ||
var testcases []coinJsonTest | ||
raw, err := os.ReadFile("../../tx/textual/internal/testdata/coin.json") | ||
require.NoError(t, err) | ||
err = json.Unmarshal(raw, &testcases) | ||
require.NoError(t, err) | ||
|
||
for _, tc := range testcases { | ||
t.Run(tc.Text, func(t *testing.T) { | ||
if tc.Proto != nil { | ||
out, err := coins.FormatCoins([]*basev1beta1.Coin{tc.Proto}, []*bankv1beta1.Metadata{tc.Metadata}) | ||
|
||
if tc.Error { | ||
require.Error(t, err) | ||
return | ||
} | ||
|
||
require.NoError(t, err) | ||
require.Equal(t, tc.Text, out) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestFormatCoins(t *testing.T) { | ||
var testcases []coinsJsonTest | ||
raw, err := os.ReadFile("../../tx/textual/internal/testdata/coins.json") | ||
require.NoError(t, err) | ||
err = json.Unmarshal(raw, &testcases) | ||
require.NoError(t, err) | ||
|
||
for _, tc := range testcases { | ||
t.Run(tc.Text, func(t *testing.T) { | ||
if tc.Proto != nil { | ||
metadata := make([]*bankv1beta1.Metadata, len(tc.Proto)) | ||
for i, coin := range tc.Proto { | ||
metadata[i] = tc.Metadata[coin.Denom] | ||
} | ||
|
||
out, err := coins.FormatCoins(tc.Proto, metadata) | ||
|
||
if tc.Error { | ||
require.Error(t, err) | ||
return | ||
} | ||
|
||
require.NoError(t, err) | ||
require.Equal(t, tc.Text, out) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.