feat: implement parse method for Int,Dec,Coin (#13696)
* wip: parse coins * review changes * update tests * update tests * Use ValueOfString * fix tests * fix coin tests * FIXES * Add empty coins test case * fix tests * fix tests * review changes * fix tests * fix tests * revert format.go * revert `format.go` * review changes * fix tests & review changes * review changes * review changes * review changes * review changes * review changes * review changes * proposal shorter code * review changes * conflicts * review changes * wip * ParseRepeated for coins * Revert listpb * remove duplicate case * revert some more Co-authored-by: Amaury M <1293565+amaurym@users.noreply.github.com> Co-authored-by: Marko <marbar3778@yahoo.com>
This commit is contained in:
co-authored by
Amaury M
Marko
parent
bcfb7dc330
commit
9d12a5fce3
@@ -10,6 +10,8 @@ import (
|
||||
"cosmossdk.io/math"
|
||||
)
|
||||
|
||||
const emptyCoins = "zero"
|
||||
|
||||
// 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.
|
||||
@@ -78,6 +80,10 @@ func FormatCoins(coins []*basev1beta1.Coin, metadata []*bankv1beta1.Metadata) (s
|
||||
}
|
||||
}
|
||||
|
||||
if len(coins) == 0 {
|
||||
return emptyCoins, nil
|
||||
}
|
||||
|
||||
// Sort the formatted coins by display denom.
|
||||
sort.SliceStable(formatted, func(i, j int) bool {
|
||||
denomI := strings.Split(formatted[i], " ")[1]
|
||||
|
||||
@@ -50,11 +50,13 @@ Value Renderers describe how values of different Protobuf types should be encode
|
||||
|
||||
* an array of `coin` is display as the concatenation of each `coin` encoded as the specification above, the joined together with the delimiter `", "` (a comma and a space, no quotes around).
|
||||
* the list of coins is ordered by unicode code point of the display denom: `A-Z` < `a-z`. For example, the string `aAbBcC` would be sorted `ABCabc`.
|
||||
- if the coins list had 0 items in it then it'll be rendered as `zero`
|
||||
|
||||
### Example
|
||||
|
||||
* `["3cosm", "2000000uatom"]` -> `2 atom, 3 COSM` (assuming the display denoms are `atom` and `COSM`)
|
||||
* `["10atom", "20Acoin"]` -> `20 Acoin, 10 atom` (assuming the display denoms are `atom` and `Acoin`)
|
||||
- `[]` -> `zero`
|
||||
|
||||
### `repeated`
|
||||
|
||||
|
||||
+54
-54
@@ -1,137 +1,137 @@
|
||||
[
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0.000001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0.00001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0.0001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0.001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0.01 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0.1 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "0 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "0.000001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "0.00001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "0.0001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "0.001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "0.01 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "0.1 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "1 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}, {"denom":"stake", "exponent": 0}]},
|
||||
"text": "10 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.000001", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.00001", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.0001", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "100 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.001", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.01", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.1", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "100'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "ucosm", "base":"COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
@@ -217,137 +217,137 @@
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "0 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "1 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "10 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "100 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "1'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "10'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "100'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "1'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "10'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0.01 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0.1 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "100 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1'000 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10'000 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"metadata": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 2}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "100'000 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "0 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "1 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "10 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "100 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "1'000 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "10'000 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "100'000 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "1'000'000 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "point"},
|
||||
"metadata": {"display": "POINT", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
|
||||
"text": "10'000'000 POINT"
|
||||
},
|
||||
{"text":"", "error": true},
|
||||
|
||||
+14
-14
@@ -2,15 +2,15 @@
|
||||
{
|
||||
"proto": [],
|
||||
"metadata":{},
|
||||
"text": ""
|
||||
"text": "zero"
|
||||
},
|
||||
{
|
||||
"proto": [
|
||||
{ "amount": "1", "denom": "ucosm" }
|
||||
],
|
||||
"metadata":{
|
||||
"ucosm": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"ustake": {"display": "STAKE", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
|
||||
"ucosm": {"display": "COSM", "base": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"ustake": {"display": "STAKE", "base": "ustake", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
|
||||
},
|
||||
"text": "0.000001 COSM"
|
||||
},
|
||||
@@ -20,8 +20,8 @@
|
||||
{ "amount": "3", "denom": "ustake" }
|
||||
],
|
||||
"metadata":{
|
||||
"ucosm": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"ustake": {"display": "STAKE", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
|
||||
"ucosm": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"ustake": {"display": "STAKE", "base":"ustake", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
|
||||
},
|
||||
"text": "0.000001 COSM, 0.000003 STAKE"
|
||||
},
|
||||
@@ -31,8 +31,8 @@
|
||||
{ "amount": "1", "denom": "ucosm" }
|
||||
],
|
||||
"metadata": {
|
||||
"ucosm": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"ustake": {"display": "STAKE", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
|
||||
"ucosm": {"display": "COSM", "base":"ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"ustake": {"display": "STAKE", "base":"ustake", "denom_units": [{"denom": "STAKE", "exponent": 6}, {"denom": "ustake", "exponent": 0}]}
|
||||
},
|
||||
"text": "0.000001 COSM, 0.000003 STAKE"
|
||||
},
|
||||
@@ -43,9 +43,9 @@
|
||||
{ "amount": "3", "denom": "uatom" }
|
||||
],
|
||||
"metadata": {
|
||||
"uaa": {"display": "AA", "denom_units": [{"denom": "AA", "exponent": 6}, {"denom": "uaa", "exponent": 0}]},
|
||||
"ubb": {"display": "BB", "denom_units": [{"denom": "BB", "exponent": 6}, {"denom": "ubb", "exponent": 0}]},
|
||||
"uatom": {"display": "atom", "denom_units": [{"denom": "atom", "exponent": 6}, {"denom": "uatom", "exponent": 0}]}
|
||||
"uaa": {"display": "AA", "base":"uaa", "denom_units": [{"denom": "AA", "exponent": 6}, {"denom": "uaa", "exponent": 0}]},
|
||||
"ubb": {"display": "BB", "base":"ubb", "denom_units": [{"denom": "BB", "exponent": 6}, {"denom": "ubb", "exponent": 0}]},
|
||||
"uatom": {"display": "atom", "base":"uatom", "denom_units": [{"denom": "atom", "exponent": 6}, {"denom": "uatom", "exponent": 0}]}
|
||||
},
|
||||
"text": "0.000001 AA, 0.000002 BB, 0.000003 atom"
|
||||
},
|
||||
@@ -57,10 +57,10 @@
|
||||
{ "amount": "1", "denom": "uxa" }
|
||||
],
|
||||
"metadata": {
|
||||
"uxa": {"display": "xA", "denom_units": [{"denom": "xA", "exponent": 6}, {"denom": "uxa", "exponent": 0}]},
|
||||
"uxb": {"display": "xB", "denom_units": [{"denom": "xB", "exponent": 6}, {"denom": "uxb", "exponent": 0}]},
|
||||
"uxc": {"display": "xC", "denom_units": [{"denom": "xC", "exponent": 6}, {"denom": "uxc", "exponent": 0}]},
|
||||
"uxc1": {"display": "xC1", "denom_units": [{"denom": "xC1", "exponent": 6}, {"denom": "uxc1", "exponent": 0}]}
|
||||
"uxa": {"display": "xA", "base":"uxa", "denom_units": [{"denom": "xA", "exponent": 6}, {"denom": "uxa", "exponent": 0}]},
|
||||
"uxb": {"display": "xB", "base":"uxb", "denom_units": [{"denom": "xB", "exponent": 6}, {"denom": "uxb", "exponent": 0}]},
|
||||
"uxc": {"display": "xC", "base":"uxc", "denom_units": [{"denom": "xC", "exponent": 6}, {"denom": "uxc", "exponent": 0}]},
|
||||
"uxc1": {"display": "xC1", "base":"uxc1", "denom_units": [{"denom": "xC1", "exponent": 6}, {"denom": "uxc1", "exponent": 0}]}
|
||||
},
|
||||
"text": "0.000001 xA, 0.000002 xB, 0.000003 xC, 0.000004 xC1"
|
||||
},
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestMetadataQuerier(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
_, err = vr.Format(context.Background(), protoreflect.ValueOf((&basev1beta1.Coin{}).ProtoReflect()))
|
||||
require.ErrorIs(t, err, expErr)
|
||||
_, err = vr.Format(context.Background(), protoreflect.ValueOf(NewGenericList([]*basev1beta1.Coin{{}})))
|
||||
_, err = vr.(valuerenderer.RepeatedValueRenderer).FormatRepeated(context.Background(), protoreflect.ValueOf(NewGenericList([]*basev1beta1.Coin{{}})))
|
||||
require.ErrorIs(t, err, expErr)
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ func TestCoinJsonTestcases(t *testing.T) {
|
||||
t.Run(tc.Text, func(t *testing.T) {
|
||||
if tc.Proto != nil {
|
||||
ctx := context.WithValue(context.Background(), mockCoinMetadataKey(tc.Proto.Denom), tc.Metadata)
|
||||
if tc.Metadata != nil {
|
||||
ctx = context.WithValue(ctx, mockCoinMetadataKey(tc.Metadata.Display), tc.Metadata)
|
||||
}
|
||||
|
||||
screens, err := vr.Format(ctx, protoreflect.ValueOf(tc.Proto.ProtoReflect()))
|
||||
|
||||
if tc.Error {
|
||||
@@ -77,10 +81,20 @@ func TestCoinJsonTestcases(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(screens))
|
||||
require.Equal(t, tc.Text, screens[0].Text)
|
||||
}
|
||||
|
||||
// TODO Add parsing tests
|
||||
// https://github.com/cosmos/cosmos-sdk/issues/13153
|
||||
// Round trip.
|
||||
value, err := vr.Parse(ctx, screens)
|
||||
if tc.Error {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
coin, ok := value.Message().Interface().(*basev1beta1.Coin)
|
||||
require.True(t, ok)
|
||||
|
||||
checkCoinEqual(t, coin, tc.Proto)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,18 @@ package valuerenderer
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
corecoins "cosmossdk.io/core/coins"
|
||||
"cosmossdk.io/math"
|
||||
)
|
||||
|
||||
const emptyCoins = "zero"
|
||||
|
||||
// NewCoinsValueRenderer returns a ValueRenderer for SDK Coin and Coins.
|
||||
func NewCoinsValueRenderer(q CoinMetadataQueryFn) ValueRenderer {
|
||||
return coinsValueRenderer{q}
|
||||
@@ -31,61 +35,187 @@ func (vr coinsValueRenderer) Format(ctx context.Context, v protoreflect.Value) (
|
||||
return nil, fmt.Errorf("expected non-nil coin metadata querier")
|
||||
}
|
||||
|
||||
// Check whether we have a Coin or some Coins.
|
||||
switch protoCoins := v.Interface().(type) {
|
||||
// If it's a repeated Coin:
|
||||
case protoreflect.List:
|
||||
{
|
||||
coins, metadatas := make([]*basev1beta1.Coin, protoCoins.Len()), make([]*bankv1beta1.Metadata, protoCoins.Len())
|
||||
var err error
|
||||
for i := 0; i < protoCoins.Len(); i++ {
|
||||
coin := protoCoins.Get(i).Interface().(protoreflect.Message).Interface().(*basev1beta1.Coin)
|
||||
coins[i] = coin
|
||||
metadatas[i], err = vr.coinMetadataQuerier(ctx, coin.Denom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Since this value renderer has a FormatRepeated method, the Format one
|
||||
// here only handles single coin.
|
||||
coin := v.Interface().(protoreflect.Message).Interface().(*basev1beta1.Coin)
|
||||
|
||||
formatted, err := corecoins.FormatCoins(coins, metadatas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []Screen{{Text: formatted}}, nil
|
||||
}
|
||||
// If it's a single Coin:
|
||||
case protoreflect.Message:
|
||||
{
|
||||
coin := v.Interface().(protoreflect.Message).Interface().(*basev1beta1.Coin)
|
||||
|
||||
metadata, err := vr.coinMetadataQuerier(ctx, coin.Denom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
formatted, err := corecoins.FormatCoins([]*basev1beta1.Coin{coin}, []*bankv1beta1.Metadata{metadata})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []Screen{{Text: formatted}}, nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("got invalid type %t for coins", v.Interface())
|
||||
metadata, err := vr.coinMetadataQuerier(ctx, coin.Denom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (vr coinsValueRenderer) Parse(_ context.Context, screens []Screen) (protoreflect.Value, error) {
|
||||
// ref: https://github.com/cosmos/cosmos-sdk/issues/13153
|
||||
panic("implement me, see #13153")
|
||||
formatted, err := corecoins.FormatCoins([]*basev1beta1.Coin{coin}, []*bankv1beta1.Metadata{metadata})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []Screen{{Text: formatted}}, nil
|
||||
}
|
||||
|
||||
func (vr coinsValueRenderer) FormatRepeated(ctx context.Context, v protoreflect.Value) ([]Screen, error) {
|
||||
return vr.Format(ctx, v)
|
||||
if vr.coinMetadataQuerier == nil {
|
||||
return nil, fmt.Errorf("expected non-nil coin metadata querier")
|
||||
}
|
||||
|
||||
protoCoins := v.List()
|
||||
coins, metadatas := make([]*basev1beta1.Coin, protoCoins.Len()), make([]*bankv1beta1.Metadata, protoCoins.Len())
|
||||
var err error
|
||||
for i := 0; i < protoCoins.Len(); i++ {
|
||||
coin := protoCoins.Get(i).Interface().(protoreflect.Message).Interface().(*basev1beta1.Coin)
|
||||
coins[i] = coin
|
||||
metadatas[i], err = vr.coinMetadataQuerier(ctx, coin.Denom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
formatted, err := corecoins.FormatCoins(coins, metadatas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []Screen{{Text: formatted}}, nil
|
||||
}
|
||||
|
||||
func (vr coinsValueRenderer) Parse(ctx context.Context, screens []Screen) (protoreflect.Value, error) {
|
||||
if len(screens) != 1 {
|
||||
return nilValue, fmt.Errorf("expected single screen: %v", screens)
|
||||
}
|
||||
|
||||
if screens[0].Text == emptyCoins {
|
||||
return protoreflect.ValueOfMessage((&basev1beta1.Coin{}).ProtoReflect()), nil
|
||||
}
|
||||
|
||||
parsed, err := vr.parseCoins(ctx, screens[0].Text)
|
||||
if err != nil {
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
return protoreflect.ValueOfMessage(parsed[0].ProtoReflect()), err
|
||||
}
|
||||
|
||||
func (vr coinsValueRenderer) ParseRepeated(ctx context.Context, screens []Screen, l protoreflect.List) error {
|
||||
// ref: https://github.com/cosmos/cosmos-sdk/issues/13153
|
||||
panic("implement me, see #13153")
|
||||
if len(screens) != 1 {
|
||||
return fmt.Errorf("expected single screen: %v", screens)
|
||||
}
|
||||
|
||||
if screens[0].Text == emptyCoins {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsed, err := vr.parseCoins(ctx, screens[0].Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, c := range parsed {
|
||||
l.Append(protoreflect.ValueOf(c.ProtoReflect()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vr coinsValueRenderer) parseCoins(ctx context.Context, coinsStr string) ([]*basev1beta1.Coin, error) {
|
||||
coins := strings.Split(coinsStr, ", ")
|
||||
metadatas := make([]*bankv1beta1.Metadata, len(coins))
|
||||
|
||||
var err error
|
||||
for i, coin := range coins {
|
||||
coinArr := strings.Split(coin, " ")
|
||||
if len(coinArr) != 2 {
|
||||
return nil, fmt.Errorf("invalid coin %s", coin)
|
||||
}
|
||||
metadatas[i], err = vr.coinMetadataQuerier(ctx, coinArr[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(coins) != len(metadatas) {
|
||||
return []*basev1beta1.Coin{}, fmt.Errorf("formatCoins expect one metadata for each coin; expected %d, got %d", len(coins), len(metadatas))
|
||||
}
|
||||
|
||||
parsedCoins := make([]*basev1beta1.Coin, len(coins))
|
||||
for i, coinStr := range coins {
|
||||
coin, err := parseCoin(coinStr, metadatas[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedCoins[i] = coin
|
||||
}
|
||||
|
||||
return parsedCoins, nil
|
||||
}
|
||||
|
||||
// parseCoin parses a single value-rendered coin into the Coin struct.
|
||||
// It shares a lot of code with `cosmos-sdk.io/core/coins.Format`,
|
||||
// so this code might be refactored once we have
|
||||
// a core Parse function for coins.
|
||||
func parseCoin(coinStr string, metadata *bankv1beta1.Metadata) (*basev1beta1.Coin, error) {
|
||||
coinArr := strings.Split(coinStr, " ")
|
||||
amt1 := coinArr[0]
|
||||
coinDenom := coinArr[1]
|
||||
|
||||
if metadata == nil || metadata.Base == "" || coinArr[1] == metadata.Base {
|
||||
dec, err := parseDec(amt1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &basev1beta1.Coin{
|
||||
Amount: dec,
|
||||
Denom: coinDenom,
|
||||
}, nil
|
||||
}
|
||||
baseDenom := metadata.Base
|
||||
|
||||
// Find exponents of both denoms.
|
||||
foundCoinExp, foundBaseExp := false, false
|
||||
var coinExp, baseExp uint32
|
||||
for _, unit := range metadata.DenomUnits {
|
||||
if coinDenom == unit.Denom {
|
||||
coinExp = unit.Exponent
|
||||
foundCoinExp = true
|
||||
}
|
||||
if baseDenom == unit.Denom {
|
||||
baseExp = unit.Exponent
|
||||
foundBaseExp = true
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find either exponent, then we return early.
|
||||
if !foundCoinExp || !foundBaseExp {
|
||||
amt, err := parseDec(amt1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &basev1beta1.Coin{
|
||||
Amount: amt,
|
||||
Denom: baseDenom,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// remove 1000 separators, (ex: 1'000'000 -> 1000000)
|
||||
amt1 = strings.ReplaceAll(amt1, "'", "")
|
||||
amt, err := math.LegacyNewDecFromStr(amt1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if coinExp > baseExp {
|
||||
amt = amt.Mul(math.LegacyNewDec(10).Power(uint64(coinExp - baseExp)))
|
||||
} else {
|
||||
amt = amt.Quo(math.LegacyNewDec(10).Power(uint64(baseExp - coinExp)))
|
||||
}
|
||||
|
||||
amtStr, err := parseDec(amt.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &basev1beta1.Coin{
|
||||
Amount: amtStr,
|
||||
Denom: baseDenom,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
"cosmossdk.io/math"
|
||||
"cosmossdk.io/tx/textual/valuerenderer"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
@@ -22,6 +23,7 @@ func TestCoinsJsonTestcases(t *testing.T) {
|
||||
|
||||
textual := valuerenderer.NewTextual(mockCoinMetadataQuerier)
|
||||
vr, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("COINS"))
|
||||
vrr := vr.(valuerenderer.RepeatedValueRenderer)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range testcases {
|
||||
@@ -30,29 +32,67 @@ func TestCoinsJsonTestcases(t *testing.T) {
|
||||
// Create a context.Context containing all coins metadata, to simulate
|
||||
// that they are in state.
|
||||
ctx := context.Background()
|
||||
for _, coin := range tc.Proto {
|
||||
ctx = context.WithValue(ctx, mockCoinMetadataKey(coin.Denom), tc.Metadata[coin.Denom])
|
||||
|
||||
for _, v := range tc.Metadata {
|
||||
ctx = context.WithValue(ctx, mockCoinMetadataKey(v.Base), v)
|
||||
ctx = context.WithValue(ctx, mockCoinMetadataKey(v.Display), v)
|
||||
}
|
||||
|
||||
listValue := NewGenericList(tc.Proto)
|
||||
screens, err := vr.Format(ctx, protoreflect.ValueOf(listValue))
|
||||
screens, err := vrr.FormatRepeated(ctx, protoreflect.ValueOf(listValue))
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(screens))
|
||||
require.Equal(t, tc.Text, screens[0].Text)
|
||||
|
||||
// Round trip.
|
||||
parsedValue := NewGenericList([]*basev1beta1.Coin{})
|
||||
err = vrr.ParseRepeated(ctx, screens, parsedValue)
|
||||
if tc.Error {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(screens))
|
||||
require.Equal(t, tc.Text, screens[0].Text)
|
||||
checkCoinsEqual(t, listValue, parsedValue)
|
||||
}
|
||||
|
||||
// TODO Add parsing tests
|
||||
// https://github.com/cosmos/cosmos-sdk/issues/13153
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// checkCoinsEqual checks that the 2 lists of Coins contain the same
|
||||
// **set** of coins. It does not check that the order of coins are
|
||||
// equal, because in Textual, we sort the coins alphabetically after
|
||||
// rendering, so we lose initial Coins ordering. Instead, we just check
|
||||
// set equality using a map.
|
||||
func checkCoinsEqual(t *testing.T, l1, l2 protoreflect.List) {
|
||||
require.Equal(t, l1.Len(), l2.Len())
|
||||
var coinsMap = make(map[string]*basev1beta1.Coin, l1.Len())
|
||||
|
||||
for i := 0; i < l1.Len(); i++ {
|
||||
coin, ok := l1.Get(i).Message().Interface().(*basev1beta1.Coin)
|
||||
require.True(t, ok)
|
||||
coinsMap[coin.Denom] = coin
|
||||
}
|
||||
|
||||
for i := 0; i < l2.Len(); i++ {
|
||||
coin, ok := l2.Get(i).Message().Interface().(*basev1beta1.Coin)
|
||||
require.True(t, ok)
|
||||
|
||||
coin1 := coinsMap[coin.Denom]
|
||||
checkCoinEqual(t, coin, coin1)
|
||||
}
|
||||
}
|
||||
|
||||
func checkCoinEqual(t *testing.T, coin, coin1 *basev1beta1.Coin) {
|
||||
require.Equal(t, coin1.Denom, coin.Denom)
|
||||
v, err := math.LegacyNewDecFromStr(coin.Amount)
|
||||
require.NoError(t, err)
|
||||
v1, err := math.LegacyNewDecFromStr(coin1.Amount)
|
||||
require.NoError(t, err)
|
||||
require.True(t, v.Equal(v1))
|
||||
}
|
||||
|
||||
// coinsJsonTest is the type of test cases in the testdata file.
|
||||
// If the test case has a Proto, try to Format() it. If Error is set, expect
|
||||
// an error, otherwise match Text, then Parse() the text and expect it to
|
||||
|
||||
@@ -2,6 +2,8 @@ package valuerenderer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
@@ -27,5 +29,32 @@ func (vr decValueRenderer) Format(_ context.Context, v protoreflect.Value) ([]Sc
|
||||
}
|
||||
|
||||
func (vr decValueRenderer) Parse(_ context.Context, screens []Screen) (protoreflect.Value, error) {
|
||||
panic("implement me")
|
||||
if n := len(screens); n != 1 {
|
||||
return nilValue, fmt.Errorf("expected 1 screen, got: %d", n)
|
||||
}
|
||||
|
||||
parsed, err := parseDec(screens[0].Text)
|
||||
if err != nil {
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
return protoreflect.ValueOfString(parsed), nil
|
||||
}
|
||||
|
||||
func parseDec(v string) (string, error) {
|
||||
parts := strings.Split(v, ".")
|
||||
if len(parts) > 2 {
|
||||
return "", fmt.Errorf("invalid decimal: too many points in %s", v)
|
||||
}
|
||||
|
||||
intPart, err := parseInt(parts[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
return intPart, nil
|
||||
}
|
||||
|
||||
return intPart + "." + parts[1], nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package valuerenderer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
@@ -9,11 +12,13 @@ import (
|
||||
|
||||
// NewIntValueRenderer returns a ValueRenderer for uint32, uint64, int32 and
|
||||
// int64, and sdk.Int scalars.
|
||||
func NewIntValueRenderer() ValueRenderer {
|
||||
return intValueRenderer{}
|
||||
func NewIntValueRenderer(fd protoreflect.FieldDescriptor) ValueRenderer {
|
||||
return intValueRenderer{fd}
|
||||
}
|
||||
|
||||
type intValueRenderer struct{}
|
||||
type intValueRenderer struct {
|
||||
fd protoreflect.FieldDescriptor
|
||||
}
|
||||
|
||||
var _ ValueRenderer = intValueRenderer{}
|
||||
|
||||
@@ -26,5 +31,62 @@ func (vr intValueRenderer) Format(_ context.Context, v protoreflect.Value) ([]Sc
|
||||
}
|
||||
|
||||
func (vr intValueRenderer) Parse(_ context.Context, screens []Screen) (protoreflect.Value, error) {
|
||||
panic("implement me")
|
||||
if n := len(screens); n != 1 {
|
||||
return nilValue, fmt.Errorf("expected 1 screen, got: %d", n)
|
||||
}
|
||||
|
||||
parsedInt, err := parseInt(screens[0].Text)
|
||||
if err != nil {
|
||||
return nilValue, err
|
||||
}
|
||||
|
||||
switch vr.fd.Kind() {
|
||||
case protoreflect.Uint32Kind:
|
||||
value, err := strconv.ParseUint(parsedInt, 10, 32)
|
||||
if err != nil {
|
||||
return nilValue, err
|
||||
}
|
||||
return protoreflect.ValueOfUint32(uint32(value)), nil //nolint:gosec
|
||||
|
||||
case protoreflect.Uint64Kind:
|
||||
value, err := strconv.ParseUint(parsedInt, 10, 64)
|
||||
if err != nil {
|
||||
return nilValue, err
|
||||
}
|
||||
return protoreflect.ValueOfUint64(value), nil
|
||||
|
||||
case protoreflect.Int32Kind:
|
||||
value, err := strconv.ParseInt(parsedInt, 10, 32)
|
||||
if err != nil {
|
||||
return nilValue, err
|
||||
}
|
||||
return protoreflect.ValueOfInt32(int32(value)), nil //nolint:gosec
|
||||
|
||||
case protoreflect.Int64Kind:
|
||||
value, err := strconv.ParseInt(parsedInt, 10, 64)
|
||||
if err != nil {
|
||||
return nilValue, err
|
||||
}
|
||||
return protoreflect.ValueOfInt64(value), nil
|
||||
|
||||
case protoreflect.StringKind:
|
||||
return protoreflect.ValueOfString(parsedInt), nil
|
||||
|
||||
default:
|
||||
return nilValue, fmt.Errorf("parsing integers into a %s field is not supported", vr.fd.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
// parseInt parses a value-rendered string into an integer
|
||||
func parseInt(v string) (string, error) {
|
||||
sign := ""
|
||||
if v[0] == '-' {
|
||||
sign = "-"
|
||||
v = v[1:]
|
||||
}
|
||||
|
||||
// remove the 1000 separators (ex: 1'000'000 -> 1000000)
|
||||
v = strings.ReplaceAll(v, "'", "")
|
||||
|
||||
return sign + v, nil
|
||||
}
|
||||
|
||||
@@ -25,32 +25,34 @@ func TestIntJsonTestcases(t *testing.T) {
|
||||
textual := valuerenderer.NewTextual(nil)
|
||||
|
||||
for _, tc := range testcases {
|
||||
// Parse test case strings as protobuf uint64
|
||||
i, err := strconv.ParseUint(tc[0], 10, 64)
|
||||
if err == nil {
|
||||
r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("UINT64"))
|
||||
require.NoError(t, err)
|
||||
t.Run(tc[0], func(t *testing.T) {
|
||||
// Parse test case strings as protobuf uint64
|
||||
i, err := strconv.ParseUint(tc[0], 10, 64)
|
||||
if err == nil {
|
||||
r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("UINT64"))
|
||||
require.NoError(t, err)
|
||||
|
||||
checkNumberTest(t, r, protoreflect.ValueOf(i), tc[1])
|
||||
}
|
||||
checkNumberTest(t, r, protoreflect.ValueOf(i), tc[1])
|
||||
}
|
||||
|
||||
// Parse test case strings as protobuf uint32
|
||||
i, err = strconv.ParseUint(tc[0], 10, 32)
|
||||
if err == nil {
|
||||
r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("UINT32"))
|
||||
require.NoError(t, err)
|
||||
// Parse test case strings as protobuf uint32
|
||||
i, err = strconv.ParseUint(tc[0], 10, 32)
|
||||
if err == nil {
|
||||
r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("UINT32"))
|
||||
require.NoError(t, err)
|
||||
|
||||
checkNumberTest(t, r, protoreflect.ValueOf(i), tc[1])
|
||||
}
|
||||
checkNumberTest(t, r, protoreflect.ValueOf(i), tc[1])
|
||||
}
|
||||
|
||||
// Parse test case strings as sdk.Ints
|
||||
_, ok := math.NewIntFromString(tc[0])
|
||||
if ok {
|
||||
r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("SDKINT"))
|
||||
require.NoError(t, err)
|
||||
// Parse test case strings as sdk.Ints
|
||||
_, ok := math.NewIntFromString(tc[0])
|
||||
if ok {
|
||||
r, err := textual.GetFieldValueRenderer(fieldDescriptorFromName("SDKINT"))
|
||||
require.NoError(t, err)
|
||||
|
||||
checkNumberTest(t, r, protoreflect.ValueOf(tc[0]), tc[1])
|
||||
}
|
||||
checkNumberTest(t, r, protoreflect.ValueOf(tc[0]), tc[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,4 +66,16 @@ func checkNumberTest(t *testing.T, r valuerenderer.ValueRenderer, pv protoreflec
|
||||
require.Equal(t, false, screens[0].Expert)
|
||||
|
||||
require.Equal(t, expected, screens[0].Text)
|
||||
|
||||
// Round trip.
|
||||
value, err := r.Parse(context.Background(), screens)
|
||||
require.NoError(t, err)
|
||||
|
||||
v, err := math.LegacyNewDecFromStr(pv.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
v1, err := math.LegacyNewDecFromStr(value.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, v.Equal(v1))
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ import (
|
||||
// metadata. It is meant to be passed as an argument into `NewTextual`.
|
||||
type CoinMetadataQueryFn func(ctx context.Context, denom string) (*bankv1beta1.Metadata, error)
|
||||
|
||||
// ValueRendererCreator is a function returning a ValueRenderer.
|
||||
type ValueRendererCreator func(protoreflect.FieldDescriptor) ValueRenderer
|
||||
|
||||
// Textual holds the configuration for dispatching
|
||||
// to specific value renderers for SIGN_MODE_TEXTUAL.
|
||||
type Textual struct {
|
||||
@@ -28,9 +31,9 @@ type Textual struct {
|
||||
// server-side code) or a gRPC query client (for client-side code).
|
||||
coinMetadataQuerier CoinMetadataQueryFn
|
||||
// scalars defines a registry for Cosmos scalars.
|
||||
scalars map[string]ValueRenderer
|
||||
// messages defines a registry for custom message renderers, as defined in
|
||||
// point #9 in the spec. Note that we also use this same registry for the
|
||||
scalars map[string]ValueRendererCreator
|
||||
// messages defines a registry for custom message renderers.
|
||||
// Note that we also use this same registry for the
|
||||
// following messages, as they can be thought of custom message rendering:
|
||||
// - SDK coin and coins
|
||||
// - Protobuf timestamp
|
||||
@@ -62,7 +65,7 @@ func (r *Textual) GetFieldValueRenderer(fd protoreflect.FieldDescriptor) (ValueR
|
||||
return nil, fmt.Errorf("got empty value renderer for scalar %s", scalar)
|
||||
}
|
||||
|
||||
return vr, nil
|
||||
return vr(fd), nil
|
||||
}
|
||||
return NewStringValueRenderer(), nil
|
||||
|
||||
@@ -74,7 +77,7 @@ func (r *Textual) GetFieldValueRenderer(fd protoreflect.FieldDescriptor) (ValueR
|
||||
fd.Kind() == protoreflect.Uint64Kind ||
|
||||
fd.Kind() == protoreflect.Int32Kind ||
|
||||
fd.Kind() == protoreflect.Int64Kind:
|
||||
return NewIntValueRenderer(), nil
|
||||
return NewIntValueRenderer(fd), nil
|
||||
|
||||
case fd.Kind() == protoreflect.EnumKind:
|
||||
return NewEnumValueRenderer(fd), nil
|
||||
@@ -87,6 +90,7 @@ func (r *Textual) GetFieldValueRenderer(fd protoreflect.FieldDescriptor) (ValueR
|
||||
if found {
|
||||
return vr, nil
|
||||
}
|
||||
|
||||
if fd.IsMap() {
|
||||
return nil, fmt.Errorf("value renderers cannot format value of type map")
|
||||
}
|
||||
@@ -111,9 +115,9 @@ func (r *Textual) GetMessageValueRenderer(md protoreflect.MessageDescriptor) (Va
|
||||
|
||||
func (r *Textual) init() {
|
||||
if r.scalars == nil {
|
||||
r.scalars = map[string]ValueRenderer{}
|
||||
r.scalars["cosmos.Int"] = NewIntValueRenderer()
|
||||
r.scalars["cosmos.Dec"] = NewDecValueRenderer()
|
||||
r.scalars = map[string]ValueRendererCreator{}
|
||||
r.scalars["cosmos.Int"] = func(fd protoreflect.FieldDescriptor) ValueRenderer { return NewIntValueRenderer(fd) }
|
||||
r.scalars["cosmos.Dec"] = func(_ protoreflect.FieldDescriptor) ValueRenderer { return NewDecValueRenderer() }
|
||||
}
|
||||
if r.messages == nil {
|
||||
r.messages = map[protoreflect.FullName]ValueRenderer{}
|
||||
@@ -125,7 +129,7 @@ func (r *Textual) init() {
|
||||
}
|
||||
|
||||
// DefineScalar adds a value renderer to the given Cosmos scalar.
|
||||
func (r *Textual) DefineScalar(scalar string, vr ValueRenderer) {
|
||||
func (r *Textual) DefineScalar(scalar string, vr ValueRendererCreator) {
|
||||
r.init()
|
||||
r.scalars[scalar] = vr
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ func TestDispatcher(t *testing.T) {
|
||||
expErr bool
|
||||
expValueRenderer valuerenderer.ValueRenderer
|
||||
}{
|
||||
{"UINT32", false, valuerenderer.NewIntValueRenderer()},
|
||||
{"UINT64", false, valuerenderer.NewIntValueRenderer()},
|
||||
{"SDKINT", false, valuerenderer.NewIntValueRenderer()},
|
||||
{"UINT32", false, valuerenderer.NewIntValueRenderer(fieldDescriptorFromName("UINT32"))},
|
||||
{"UINT64", false, valuerenderer.NewIntValueRenderer(fieldDescriptorFromName("UINT64"))},
|
||||
{"SDKINT", false, valuerenderer.NewIntValueRenderer(fieldDescriptorFromName("SDKINT"))},
|
||||
{"SDKDEC", false, valuerenderer.NewDecValueRenderer()},
|
||||
{"BYTES", false, valuerenderer.NewBytesValueRenderer()},
|
||||
{"TIMESTAMP", false, valuerenderer.NewTimestampValueRenderer()},
|
||||
|
||||
Reference in New Issue
Block a user