feat(textual): `Coin` and `Coins` value renderers (#12729)
* wip coins * Make coin test pass * Remove useless file * wip * Fix tests * Small tweaks * reviews * Add comment * Add back go mod * Add more coins test * Update coins test * Add more coins tests * Reference todo issue * Add metadata querier test * add more tests * Fix test build * Improve comments * Update tx/textual/internal/testdata/coin.json Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com> * json formatting * add more test cases * go mod tidy * address review Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com>
This commit is contained in:
parent
9dacaa4a19
commit
fd028db79a
|
@ -13,9 +13,15 @@ require (
|
|||
require (
|
||||
github.com/cosmos/gogoproto v1.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.1 // indirect
|
||||
golang.org/x/net v0.0.0-20220726230323-06994584191e // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b // indirect
|
||||
google.golang.org/grpc v1.49.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
13
tx/go.sum
13
tx/go.sum
|
@ -11,6 +11,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
|
@ -34,8 +36,19 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
golang.org/x/net v0.0.0-20220726230323-06994584191e h1:wOQNKh1uuDGRnmgF0jDxh7ctgGy/3P4rYWQRVJD4/Yg=
|
||||
golang.org/x/net v0.0.0-20220726230323-06994584191e/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b h1:SfSkJugek6xm7lWywqth4r2iTrYLpD8lOj1nMIIhMNM=
|
||||
google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
|
||||
google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
|
||||
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
[
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "0.000001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "0.0001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "0.01 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "1 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"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}]},
|
||||
"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}]},
|
||||
"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}]},
|
||||
"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}]},
|
||||
"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}]},
|
||||
"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}]},
|
||||
"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}]},
|
||||
"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}]},
|
||||
"text": "10 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "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}]},
|
||||
"text": "1 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.00001", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "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}]},
|
||||
"text": "100 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.001", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "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}]},
|
||||
"text": "10'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0.1", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "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}]},
|
||||
"text": "1'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "COSM"},
|
||||
"metadata": {"display": "ucosm", "denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "0 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "100 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "100'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "1'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "ucosm"},
|
||||
"metadata": {"denom_units": [{"denom": "COSM", "exponent": 6}, {"denom": "ucosm", "exponent": 0}]},
|
||||
"text": "10'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"text": "0 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "ucosm"},
|
||||
"text": "1 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"text": "10 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "ucosm"},
|
||||
"text": "100 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"text": "1'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "ucosm"},
|
||||
"text": "10'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"text": "100'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "ucosm"},
|
||||
"text": "1'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "ucosm"},
|
||||
"text": "10'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "0 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "1 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "10 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "100 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "1'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "10'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "denom_units": [{"denom": "COSM", "exponent": 2}]},
|
||||
"text": "100'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "10'000'000 ucosm"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "0", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "0.01 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "1 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "100 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "10'000 COSM"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000000", "denom": "ucosm"},
|
||||
"metadata": {"display": "COSM", "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}]},
|
||||
"text": "0 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1", "denom": "point"},
|
||||
"metadata": {"display": "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}]},
|
||||
"text": "10 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "100", "denom": "point"},
|
||||
"metadata": {"display": "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}]},
|
||||
"text": "1'000 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "10000", "denom": "point"},
|
||||
"metadata": {"display": "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}]},
|
||||
"text": "100'000 POINT"
|
||||
},
|
||||
{
|
||||
"proto": {"amount": "1000000", "denom": "point"},
|
||||
"metadata": {"display": "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}]},
|
||||
"text": "10'000'000 POINT"
|
||||
},
|
||||
{"text":"", "error": true},
|
||||
{"text":"1COSM", "error": true},
|
||||
{"text":"1 COSM", "error": true},
|
||||
{"text":" 1 COSM", "error": true}
|
||||
]
|
|
@ -0,0 +1,72 @@
|
|||
[
|
||||
{
|
||||
"proto": [],
|
||||
"metadata":{},
|
||||
"text": ""
|
||||
},
|
||||
{
|
||||
"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}]}
|
||||
},
|
||||
"text": "0.000001 COSM"
|
||||
},
|
||||
{
|
||||
"proto": [
|
||||
{ "amount": "1", "denom": "ucosm" },
|
||||
{ "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}]}
|
||||
},
|
||||
"text": "0.000001 COSM, 0.000003 STAKE"
|
||||
},
|
||||
{
|
||||
"proto": [
|
||||
{ "amount": "3", "denom": "ustake" },
|
||||
{ "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}]}
|
||||
},
|
||||
"text": "0.000001 COSM, 0.000003 STAKE"
|
||||
},
|
||||
{
|
||||
"proto": [
|
||||
{ "amount": "1", "denom": "uaa" },
|
||||
{ "amount": "2", "denom": "ubb" },
|
||||
{ "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}]}
|
||||
},
|
||||
"text": "0.000001 AA, 0.000002 BB, 0.000003 atom"
|
||||
},
|
||||
{
|
||||
"proto": [
|
||||
{ "amount": "4", "denom": "uxc1" },
|
||||
{ "amount": "3", "denom": "uxc" },
|
||||
{ "amount": "2", "denom": "uxb" },
|
||||
{ "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}]}
|
||||
},
|
||||
"text": "0.000001 xA, 0.000002 xB, 0.000003 xC, 0.000004 xC1"
|
||||
},
|
||||
{"text": "0.000001AA, 0.000002 BB, 0.000003 atom", "error": true},
|
||||
{"text": "0.000001 AA, 0.000002 BB, 0.000003 atom", "error": true},
|
||||
{"text": "0.000001 AA, 0.000002 BB, 0.000003 atom", "error": true},
|
||||
{"text": " 0.000001 AA, 0.000002 BB, 0.000003 atom", "error": true},
|
||||
{"text": "0.000001 AA, 0.000002 BB, 0.000003 atom ", "error": true}
|
||||
]
|
|
@ -12,32 +12,28 @@ enum Enumeration {
|
|||
Two = 1;
|
||||
}
|
||||
|
||||
// A contains fields that are parseable by SIGN_MODE_TEXTUAL.
|
||||
// A is used for testing value renderers.
|
||||
message A {
|
||||
uint32 UINT32 = 1;
|
||||
uint64 UINT64 = 2;
|
||||
int32 INT32 = 3;
|
||||
int64 INT64 = 4;
|
||||
string SDKINT = 5 [(cosmos_proto.scalar) = "cosmos.Int"];
|
||||
string SDKDEC = 6 [(cosmos_proto.scalar) = "cosmos.Dec"];
|
||||
cosmos.base.v1beta1.Coin COIN = 7;
|
||||
repeated cosmos.base.v1beta1.Coin COINS = 8;
|
||||
bytes BYTES = 9;
|
||||
google.protobuf.Timestamp TIMESTAMP = 10;
|
||||
}
|
||||
// Fields that are parseable by SIGN_MODE_TEXTUAL.
|
||||
uint32 UINT32 = 1;
|
||||
uint64 UINT64 = 2;
|
||||
int32 INT32 = 3;
|
||||
int64 INT64 = 4;
|
||||
string SDKINT = 5 [(cosmos_proto.scalar) = "cosmos.Int"];
|
||||
string SDKDEC = 6 [(cosmos_proto.scalar) = "cosmos.Dec"];
|
||||
cosmos.base.v1beta1.Coin COIN = 7;
|
||||
repeated cosmos.base.v1beta1.Coin COINS = 8;
|
||||
bytes BYTES = 9;
|
||||
google.protobuf.Timestamp TIMESTAMP = 10;
|
||||
|
||||
// B contains fields that are not parseable by SIGN_MODE_TEXTUAL, some fields
|
||||
// may be moved to A at some point.
|
||||
message B {
|
||||
int32 INT32 = 1;
|
||||
sint32 SINT32 = 2;
|
||||
int64 INT64 = 3;
|
||||
sint64 SING64 = 4;
|
||||
sfixed32 SFIXED32 = 5;
|
||||
fixed32 FIXED32 = 6;
|
||||
float FLOAT = 7;
|
||||
sfixed64 SFIXED64 = 8;
|
||||
fixed64 FIXED64 = 9;
|
||||
double DOUBLE = 10;
|
||||
map<string, B> MAP = 11;
|
||||
// Fields that are not handled by SIGN_MODE_TEXTUAL.
|
||||
sint32 SINT32 = 101;
|
||||
sint64 SINT64 = 102;
|
||||
sfixed32 SFIXED32 = 105;
|
||||
fixed32 FIXED32 = 106;
|
||||
float FLOAT = 107;
|
||||
sfixed64 SFIXED64 = 108;
|
||||
fixed64 FIXED64 = 109;
|
||||
double DOUBLE = 110;
|
||||
map<string, A> MAP = 111;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,12 @@ import (
|
|||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
// bytesValueRenderer implements ValueRenderer for bytes
|
||||
// NewBytesValueRenderer returns a ValueRenderer for Protobuf bytes, which are
|
||||
// encoded as capital-letter hexadecimal, without the '0x' prefix.
|
||||
func NewBytesValueRenderer() ValueRenderer {
|
||||
return bytesValueRenderer{}
|
||||
}
|
||||
|
||||
type bytesValueRenderer struct{}
|
||||
|
||||
var _ ValueRenderer = bytesValueRenderer{}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/tx/textual/valuerenderer"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
@ -18,15 +19,16 @@ func TestBytesJsonTestCases(t *testing.T) {
|
|||
// their expected results in hex.
|
||||
raw, err := os.ReadFile("../internal/testdata/bytes.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(raw, &testcases)
|
||||
require.NoError(t, err)
|
||||
|
||||
textual := valuerenderer.NewTextual(mockCoinMetadataQuerier)
|
||||
|
||||
for _, tc := range testcases {
|
||||
data, err := base64.StdEncoding.DecodeString(tc.base64)
|
||||
require.NoError(t, err)
|
||||
|
||||
valrend, err := valueRendererOf(data)
|
||||
valrend, err := textual.GetValueRenderer(fieldDescriptorFromName("BYTES"))
|
||||
require.NoError(t, err)
|
||||
|
||||
b := new(strings.Builder)
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package valuerenderer_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
"cosmossdk.io/tx/textual/valuerenderer"
|
||||
)
|
||||
|
||||
// mockCoinMetadataKey is used in the mock coin metadata querier.
|
||||
func mockCoinMetadataKey(denom string) string {
|
||||
return fmt.Sprintf("%s-%s", "coin-metadata", denom)
|
||||
}
|
||||
|
||||
// mockCoinMetadataQuerier is a mock querier for coin metadata used for test
|
||||
// purposes.
|
||||
func mockCoinMetadataQuerier(ctx context.Context, denom string) (*bankv1beta1.Metadata, error) {
|
||||
v := ctx.Value(mockCoinMetadataKey(denom))
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return v.(*bankv1beta1.Metadata), nil
|
||||
}
|
||||
|
||||
func TestMetadataQuerier(t *testing.T) {
|
||||
b := new(strings.Builder)
|
||||
|
||||
// Errors on nil metadata querier
|
||||
textual := valuerenderer.NewTextual(nil)
|
||||
vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COIN"))
|
||||
require.NoError(t, err)
|
||||
err = vr.Format(context.Background(), protoreflect.ValueOf((&basev1beta1.Coin{}).ProtoReflect()), b)
|
||||
require.Error(t, err)
|
||||
|
||||
// Errors if metadata querier returns an error
|
||||
expErr := fmt.Errorf("mock error")
|
||||
textual = valuerenderer.NewTextual(func(_ context.Context, _ string) (*bankv1beta1.Metadata, error) {
|
||||
return nil, expErr
|
||||
})
|
||||
vr, err = textual.GetValueRenderer(fieldDescriptorFromName("COIN"))
|
||||
require.NoError(t, err)
|
||||
err = vr.Format(context.Background(), protoreflect.ValueOf((&basev1beta1.Coin{}).ProtoReflect()), b)
|
||||
require.ErrorIs(t, err, expErr)
|
||||
err = vr.Format(context.Background(), protoreflect.ValueOf(NewGenericList([]*basev1beta1.Coin{{}})), b)
|
||||
require.ErrorIs(t, err, expErr)
|
||||
}
|
||||
|
||||
func TestCoinJsonTestcases(t *testing.T) {
|
||||
var testcases []coinJsonTest
|
||||
raw, err := os.ReadFile("../internal/testdata/coin.json")
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(raw, &testcases)
|
||||
require.NoError(t, err)
|
||||
|
||||
textual := valuerenderer.NewTextual(mockCoinMetadataQuerier)
|
||||
vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COIN"))
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.Text, func(t *testing.T) {
|
||||
if tc.Proto != nil {
|
||||
ctx := context.WithValue(context.Background(), mockCoinMetadataKey(tc.Proto.Denom), tc.Metadata)
|
||||
b := new(strings.Builder)
|
||||
err = vr.Format(ctx, protoreflect.ValueOf(tc.Proto.ProtoReflect()), b)
|
||||
|
||||
if tc.Error {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.Text, b.String())
|
||||
}
|
||||
|
||||
// TODO Add parsing tests
|
||||
// https://github.com/cosmos/cosmos-sdk/issues/13153
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// coinJsonTest 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
|
||||
// match (via proto.Equals()) the original Proto. If the test case has no
|
||||
// Proto, try to Parse() the Text and expect an error if Error is set.
|
||||
type coinJsonTest struct {
|
||||
Proto *basev1beta1.Coin
|
||||
Metadata *bankv1beta1.Metadata
|
||||
Error bool
|
||||
Text string
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package valuerenderer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
"cosmossdk.io/math"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
// NewCoinsValueRenderer returns a ValueRenderer for SDK Coin and Coins.
|
||||
func NewCoinsValueRenderer(q CoinMetadataQueryFn) ValueRenderer {
|
||||
return coinsValueRenderer{q}
|
||||
}
|
||||
|
||||
type coinsValueRenderer struct {
|
||||
// coinMetadataQuerier defines a function to query the coin metadata from
|
||||
// state. It should use bank module's `DenomsMetadata` gRPC query to fetch
|
||||
// each denom's associated metadata, either using the bank keeper (for
|
||||
// server-side code) or a gRPC query client (for client-side code).
|
||||
coinMetadataQuerier CoinMetadataQueryFn
|
||||
}
|
||||
|
||||
var _ ValueRenderer = coinsValueRenderer{}
|
||||
|
||||
func (vr coinsValueRenderer) Format(ctx context.Context, v protoreflect.Value, w io.Writer) error {
|
||||
if vr.coinMetadataQuerier == nil {
|
||||
return 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 err
|
||||
}
|
||||
}
|
||||
|
||||
formatted, err := formatCoins(coins, metadatas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(formatted))
|
||||
return err
|
||||
}
|
||||
// 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 err
|
||||
}
|
||||
|
||||
formatted, err := formatCoin(coin, metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte(formatted))
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("got invalid type %t for coins", v.Interface())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (vr coinsValueRenderer) Parse(_ context.Context, r io.Reader) (protoreflect.Value, error) {
|
||||
// ref: https://github.com/cosmos/cosmos-sdk/issues/13153
|
||||
panic("implement me, see #13153")
|
||||
}
|
||||
|
||||
// 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 := formatDecimal(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 := formatInteger(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 := formatDecimal(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) {
|
||||
panic(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
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package valuerenderer_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
"cosmossdk.io/tx/textual/valuerenderer"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
func TestCoinsJsonTestcases(t *testing.T) {
|
||||
var testcases []coinsJsonTest
|
||||
raw, err := os.ReadFile("../internal/testdata/coins.json")
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(raw, &testcases)
|
||||
require.NoError(t, err)
|
||||
|
||||
textual := valuerenderer.NewTextual(mockCoinMetadataQuerier)
|
||||
vr, err := textual.GetValueRenderer(fieldDescriptorFromName("COINS"))
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.Text, func(t *testing.T) {
|
||||
if tc.Proto != nil {
|
||||
// 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])
|
||||
}
|
||||
|
||||
b := new(strings.Builder)
|
||||
listValue := NewGenericList(tc.Proto)
|
||||
err = vr.Format(ctx, protoreflect.ValueOf(listValue), b)
|
||||
|
||||
if tc.Error {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.Text, b.String())
|
||||
}
|
||||
|
||||
// TODO Add parsing tests
|
||||
// https://github.com/cosmos/cosmos-sdk/issues/13153
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// match (via proto.Equals()) the original Proto. If the test case has no
|
||||
// Proto, try to Parse() the Text and expect an error if Error is set.
|
||||
type coinsJsonTest struct {
|
||||
Proto []*basev1beta1.Coin
|
||||
Metadata map[string]*bankv1beta1.Metadata
|
||||
Text string
|
||||
Error bool
|
||||
}
|
|
@ -11,6 +11,12 @@ import (
|
|||
|
||||
const thousandSeparator string = "'"
|
||||
|
||||
// NewDecValueRenderer returns a ValueRenderer for encoding sdk.Dec cosmos
|
||||
// scalars.
|
||||
func NewDecValueRenderer() ValueRenderer {
|
||||
return decValueRenderer{}
|
||||
}
|
||||
|
||||
type decValueRenderer struct{}
|
||||
|
||||
var _ ValueRenderer = decValueRenderer{}
|
||||
|
|
|
@ -9,6 +9,12 @@ import (
|
|||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
// NewIntValueRenderer returns a ValueRenderer for uint32, uint64, int32 and
|
||||
// int64, and sdk.Int scalars.
|
||||
func NewIntValueRenderer() ValueRenderer {
|
||||
return intValueRenderer{}
|
||||
}
|
||||
|
||||
type intValueRenderer struct{}
|
||||
|
||||
var _ ValueRenderer = intValueRenderer{}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package valuerenderer_test
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
)
|
||||
|
||||
var _ protoreflect.List = (*genericList[proto.Message])(nil)
|
||||
|
||||
// NewGenericList creates a empty list that satisfies the protoreflect.List
|
||||
// interface.
|
||||
func NewGenericList[T proto.Message](list []T) protoreflect.List {
|
||||
return &genericList[T]{&list}
|
||||
}
|
||||
|
||||
// genericList is an implementation of protoreflect.List for a generic
|
||||
type genericList[T proto.Message] struct {
|
||||
list *[]T
|
||||
}
|
||||
|
||||
func (x *genericList[T]) Len() int {
|
||||
if x.list == nil {
|
||||
return 0
|
||||
}
|
||||
return len(*x.list)
|
||||
}
|
||||
|
||||
func (x *genericList[T]) Get(i int) protoreflect.Value {
|
||||
return protoreflect.ValueOfMessage((*x.list)[i].ProtoReflect())
|
||||
}
|
||||
|
||||
func (x *genericList[T]) Set(i int, value protoreflect.Value) {
|
||||
valueUnwrapped := value.Message()
|
||||
concreteValue := valueUnwrapped.Interface().(T)
|
||||
(*x.list)[i] = concreteValue
|
||||
}
|
||||
|
||||
func (x *genericList[T]) Append(value protoreflect.Value) {
|
||||
valueUnwrapped := value.Message()
|
||||
concreteValue := valueUnwrapped.Interface().(T)
|
||||
*x.list = append(*x.list, concreteValue)
|
||||
}
|
||||
|
||||
func (x *genericList[T]) AppendMutable() protoreflect.Value {
|
||||
v := *new(T)
|
||||
*x.list = append(*x.list, v)
|
||||
return protoreflect.ValueOfMessage(v.ProtoReflect())
|
||||
}
|
||||
|
||||
func (x *genericList[T]) Truncate(n int) {
|
||||
for i := n; i < len(*x.list); i++ {
|
||||
(*x.list)[i] = *new(T)
|
||||
}
|
||||
*x.list = (*x.list)[:n]
|
||||
}
|
||||
|
||||
func (x *genericList[T]) NewElement() protoreflect.Value {
|
||||
v := *new(T)
|
||||
return protoreflect.ValueOfMessage(v.ProtoReflect())
|
||||
}
|
||||
|
||||
func (x *genericList[T]) IsValid() bool {
|
||||
return x.list != nil
|
||||
}
|
|
@ -1,25 +1,45 @@
|
|||
package valuerenderer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
cosmos_proto "github.com/cosmos/cosmos-proto"
|
||||
)
|
||||
|
||||
// CoinMetadataQueryFn defines a function that queries state for the coin denom
|
||||
// metadata. It is meant to be passed as an argument into `NewTextual`.
|
||||
type CoinMetadataQueryFn func(ctx context.Context, denom string) (*bankv1beta1.Metadata, error)
|
||||
|
||||
// Textual holds the configuration for dispatching
|
||||
// to specific value renderers for SIGN_MODE_TEXTUAL.
|
||||
type Textual struct {
|
||||
scalars map[string]ValueRenderer
|
||||
// coinMetadataQuerier defines a function to query the coin metadata from
|
||||
// state. It should use bank module's `DenomsMetadata` gRPC query to fetch
|
||||
// each denom's associated metadata, either using the bank keeper (for
|
||||
// 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
|
||||
// following messages, as they can be thought of custom message rendering:
|
||||
// - SDK coin and coins
|
||||
// - Protobuf timestamp
|
||||
// - Protobuf duration
|
||||
messages map[protoreflect.FullName]ValueRenderer
|
||||
}
|
||||
|
||||
// NewTextual returns a new Textual which provides
|
||||
// value renderers.
|
||||
func NewTextual() Textual {
|
||||
t := Textual{}
|
||||
func NewTextual(q CoinMetadataQueryFn) Textual {
|
||||
t := Textual{coinMetadataQuerier: q}
|
||||
t.init()
|
||||
return t
|
||||
}
|
||||
|
@ -43,7 +63,7 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere
|
|||
return vr, nil
|
||||
}
|
||||
case fd.Kind() == protoreflect.BytesKind:
|
||||
return bytesValueRenderer{}, nil
|
||||
return NewBytesValueRenderer(), nil
|
||||
|
||||
// Integers
|
||||
case fd.Kind() == protoreflect.Uint32Kind ||
|
||||
|
@ -51,7 +71,7 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere
|
|||
fd.Kind() == protoreflect.Int32Kind ||
|
||||
fd.Kind() == protoreflect.Int64Kind:
|
||||
{
|
||||
return intValueRenderer{}, nil
|
||||
return NewIntValueRenderer(), nil
|
||||
}
|
||||
|
||||
case fd.Kind() == protoreflect.MessageKind:
|
||||
|
@ -73,12 +93,13 @@ func (r Textual) GetValueRenderer(fd protoreflect.FieldDescriptor) (ValueRendere
|
|||
func (r *Textual) init() {
|
||||
if r.scalars == nil {
|
||||
r.scalars = map[string]ValueRenderer{}
|
||||
r.scalars["cosmos.Int"] = intValueRenderer{}
|
||||
r.scalars["cosmos.Dec"] = decValueRenderer{}
|
||||
r.scalars["cosmos.Int"] = NewIntValueRenderer()
|
||||
r.scalars["cosmos.Dec"] = NewDecValueRenderer()
|
||||
}
|
||||
if r.messages == nil {
|
||||
r.messages = map[protoreflect.FullName]ValueRenderer{}
|
||||
r.messages["google.protobuf.Timestamp"] = NewTimestampValueRenderer()
|
||||
r.messages[(&basev1beta1.Coin{}).ProtoReflect().Descriptor().FullName()] = NewCoinsValueRenderer(r.coinMetadataQuerier)
|
||||
r.messages[(×tamppb.Timestamp{}).ProtoReflect().Descriptor().FullName()] = NewTimestampValueRenderer()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
tspb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
"cosmossdk.io/tx/textual/internal/testpb"
|
||||
|
@ -26,11 +25,13 @@ func TestFormatInteger(t *testing.T) {
|
|||
err = json.Unmarshal(raw, &testcases)
|
||||
require.NoError(t, err)
|
||||
|
||||
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 := valueRendererOf(i)
|
||||
r, err := textual.GetValueRenderer(fieldDescriptorFromName("UINT64"))
|
||||
require.NoError(t, err)
|
||||
b := new(strings.Builder)
|
||||
err = r.Format(context.Background(), protoreflect.ValueOf(i), b)
|
||||
|
@ -42,7 +43,7 @@ func TestFormatInteger(t *testing.T) {
|
|||
// Parse test case strings as protobuf uint32
|
||||
i, err = strconv.ParseUint(tc[0], 10, 32)
|
||||
if err == nil {
|
||||
r, err := valueRendererOf(i)
|
||||
r, err := textual.GetValueRenderer(fieldDescriptorFromName("UINT32"))
|
||||
require.NoError(t, err)
|
||||
b := new(strings.Builder)
|
||||
err = r.Format(context.Background(), protoreflect.ValueOf(i), b)
|
||||
|
@ -52,9 +53,9 @@ func TestFormatInteger(t *testing.T) {
|
|||
}
|
||||
|
||||
// Parse test case strings as sdk.Ints
|
||||
sdkInt, ok := math.NewIntFromString(tc[0])
|
||||
_, ok := math.NewIntFromString(tc[0])
|
||||
if ok {
|
||||
r, err := valueRendererOf(sdkInt)
|
||||
r, err := textual.GetValueRenderer(fieldDescriptorFromName("SDKINT"))
|
||||
require.NoError(t, err)
|
||||
b := new(strings.Builder)
|
||||
err = r.Format(context.Background(), protoreflect.ValueOf(tc[0]), b)
|
||||
|
@ -73,12 +74,12 @@ func TestFormatDecimal(t *testing.T) {
|
|||
err = json.Unmarshal(raw, &testcases)
|
||||
require.NoError(t, err)
|
||||
|
||||
textual := valuerenderer.NewTextual(nil)
|
||||
|
||||
for _, tc := range testcases {
|
||||
tc := tc
|
||||
t.Run(tc[0], func(t *testing.T) {
|
||||
d, err := math.LegacyNewDecFromStr(tc[0])
|
||||
require.NoError(t, err)
|
||||
r, err := valueRendererOf(d)
|
||||
r, err := textual.GetValueRenderer(fieldDescriptorFromName("SDKDEC"))
|
||||
require.NoError(t, err)
|
||||
b := new(strings.Builder)
|
||||
err = r.Format(context.Background(), protoreflect.ValueOf(tc[0]), b)
|
||||
|
@ -89,74 +90,47 @@ func TestFormatDecimal(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetADR050ValueRenderer(t *testing.T) {
|
||||
func TestDispatcher(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
v interface{}
|
||||
expErr bool
|
||||
name string
|
||||
expErr bool
|
||||
expValueRenderer valuerenderer.ValueRenderer
|
||||
}{
|
||||
{"uint32", uint32(1), false},
|
||||
{"uint64", uint64(1), false},
|
||||
{"sdk.Int", math.NewInt(1), false},
|
||||
{"sdk.Dec", math.LegacyNewDec(1), false},
|
||||
{"[]byte", []byte{1}, false},
|
||||
{"float32", float32(1), true},
|
||||
{"float64", float64(1), true},
|
||||
{"UINT32", false, valuerenderer.NewIntValueRenderer()},
|
||||
{"UINT64", false, valuerenderer.NewIntValueRenderer()},
|
||||
{"SDKINT", false, valuerenderer.NewIntValueRenderer()},
|
||||
{"SDKDEC", false, valuerenderer.NewDecValueRenderer()},
|
||||
{"BYTES", false, valuerenderer.NewBytesValueRenderer()},
|
||||
{"TIMESTAMP", false, valuerenderer.NewTimestampValueRenderer()},
|
||||
{"COIN", false, valuerenderer.NewCoinsValueRenderer(nil)},
|
||||
{"COINS", false, valuerenderer.NewCoinsValueRenderer(nil)},
|
||||
{"FLOAT", true, nil},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := valueRendererOf(tc.v)
|
||||
textual := valuerenderer.NewTextual(nil)
|
||||
rend, err := textual.GetValueRenderer(fieldDescriptorFromName(tc.name))
|
||||
|
||||
if tc.expErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, tc.expValueRenderer, rend)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampDispatch(t *testing.T) {
|
||||
a := (&testpb.A{}).ProtoReflect().Descriptor().Fields()
|
||||
textual := valuerenderer.NewTextual()
|
||||
rend, err := textual.GetValueRenderer(a.ByName(protoreflect.Name("TIMESTAMP")))
|
||||
require.NoError(t, err)
|
||||
require.IsType(t, valuerenderer.NewTimestampValueRenderer(), rend)
|
||||
}
|
||||
|
||||
// valueRendererOf is like GetADR050ValueRenderer, but taking a Go type
|
||||
// fieldDescriptorFromName is like GetADR050ValueRenderer, but taking a Go type
|
||||
// as input instead of a protoreflect.FieldDescriptor.
|
||||
func valueRendererOf(v interface{}) (valuerenderer.ValueRenderer, error) {
|
||||
a, b := (&testpb.A{}).ProtoReflect().Descriptor().Fields(), (&testpb.B{}).ProtoReflect().Descriptor().Fields()
|
||||
|
||||
textual := valuerenderer.NewTextual()
|
||||
switch v := v.(type) {
|
||||
// Valid types for SIGN_MODE_TEXTUAL
|
||||
case uint32:
|
||||
return textual.GetValueRenderer(a.ByName(protoreflect.Name("UINT32")))
|
||||
case uint64:
|
||||
return textual.GetValueRenderer(a.ByName(protoreflect.Name("UINT64")))
|
||||
case int32:
|
||||
return textual.GetValueRenderer(a.ByName(protoreflect.Name("INT32")))
|
||||
case int64:
|
||||
return textual.GetValueRenderer(a.ByName(protoreflect.Name("INT64")))
|
||||
case []byte:
|
||||
return textual.GetValueRenderer(a.ByName(protoreflect.Name("BYTES")))
|
||||
case math.Int:
|
||||
return textual.GetValueRenderer(a.ByName(protoreflect.Name("SDKINT")))
|
||||
case math.LegacyDec:
|
||||
return textual.GetValueRenderer(a.ByName(protoreflect.Name("SDKDEC")))
|
||||
case tspb.Timestamp:
|
||||
return textual.GetValueRenderer(a.ByName(protoreflect.Name("TIMESTAMP")))
|
||||
|
||||
// Invalid types for SIGN_MODE_TEXTUAL
|
||||
case float32:
|
||||
return textual.GetValueRenderer(b.ByName(protoreflect.Name("FLOAT")))
|
||||
case float64:
|
||||
return textual.GetValueRenderer(b.ByName(protoreflect.Name("FLOAT")))
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("value %s of type %T not recognized", v, v)
|
||||
func fieldDescriptorFromName(name string) protoreflect.FieldDescriptor {
|
||||
a := (&testpb.A{}).ProtoReflect().Descriptor().Fields()
|
||||
fd := a.ByName(protoreflect.Name(name))
|
||||
if fd == nil {
|
||||
panic(fmt.Errorf("no field descriptor for %s", name))
|
||||
}
|
||||
|
||||
return fd
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue