Skip to content

Commit 4a73a1e

Browse files
mergify[bot]JulianToledanojulienrbrt
authored
fix(client/v2/autocli): add CoinDec flag (backport #22817) (#22821)
Co-authored-by: Julián Toledano <[email protected]> Co-authored-by: Julien Robert <[email protected]>
1 parent 96a3016 commit 4a73a1e

File tree

7 files changed

+209
-6
lines changed

7 files changed

+209
-6
lines changed

Diff for: client/v2/CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ Ref: https://keepachangelog.com/en/1.0.0/
3636

3737
## [Unreleased]
3838

39+
## [v2.0.0-beta.7] - 2024-12-10
40+
41+
### Bug Fixes
42+
43+
* [#22817](https://github.com/cosmos/cosmos-sdk/pull/22817) Add DecCoin support in autocli flag builder.
44+
3945
## [v2.0.0-beta.6] - 2024-11-21
4046

4147
### Improvements

Diff for: client/v2/autocli/flag/builder.go

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ func (b *Builder) init() {
6161
b.messageFlagTypes["google.protobuf.Timestamp"] = timestampType{}
6262
b.messageFlagTypes["google.protobuf.Duration"] = durationType{}
6363
b.messageFlagTypes["cosmos.base.v1beta1.Coin"] = coinType{}
64+
b.messageFlagTypes["cosmos.base.v1beta1.DecCoin"] = decCoinType{}
6465
}
6566

6667
if b.scalarFlagTypes == nil {

Diff for: client/v2/autocli/flag/coin.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ package flag
22

33
import (
44
"context"
5-
"fmt"
5+
"errors"
66
"strings"
77

88
"google.golang.org/protobuf/reflect/protoreflect"
99

1010
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
11-
"cosmossdk.io/core/coins"
11+
"cosmossdk.io/client/v2/internal/coins"
1212
)
1313

1414
type coinType struct{}
@@ -22,8 +22,7 @@ func (c coinType) NewValue(*context.Context, *Builder) Value {
2222
}
2323

2424
func (c coinType) DefaultValue() string {
25-
stringCoin, _ := coins.FormatCoins([]*basev1beta1.Coin{}, nil)
26-
return stringCoin
25+
return "zero"
2726
}
2827

2928
func (c *coinValue) Get(protoreflect.Value) (protoreflect.Value, error) {
@@ -34,12 +33,16 @@ func (c *coinValue) Get(protoreflect.Value) (protoreflect.Value, error) {
3433
}
3534

3635
func (c *coinValue) String() string {
36+
if c.value == nil {
37+
return ""
38+
}
39+
3740
return c.value.String()
3841
}
3942

4043
func (c *coinValue) Set(stringValue string) error {
4144
if strings.Contains(stringValue, ",") {
42-
return fmt.Errorf("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
45+
return errors.New("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
4346
}
4447

4548
coin, err := coins.ParseCoin(stringValue)

Diff for: client/v2/autocli/flag/dec_coin.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package flag
2+
3+
import (
4+
"context"
5+
"errors"
6+
"strings"
7+
8+
"google.golang.org/protobuf/reflect/protoreflect"
9+
10+
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
11+
"cosmossdk.io/client/v2/internal/coins"
12+
)
13+
14+
type decCoinType struct{}
15+
16+
type decCoinValue struct {
17+
value *basev1beta1.DecCoin
18+
}
19+
20+
func (c decCoinType) NewValue(*context.Context, *Builder) Value {
21+
return &decCoinValue{}
22+
}
23+
24+
func (c decCoinType) DefaultValue() string {
25+
return "zero"
26+
}
27+
28+
func (c *decCoinValue) Get(protoreflect.Value) (protoreflect.Value, error) {
29+
if c.value == nil {
30+
return protoreflect.Value{}, nil
31+
}
32+
return protoreflect.ValueOfMessage(c.value.ProtoReflect()), nil
33+
}
34+
35+
func (c *decCoinValue) String() string {
36+
if c.value == nil {
37+
return ""
38+
}
39+
40+
return c.value.String()
41+
}
42+
43+
func (c *decCoinValue) Set(stringValue string) error {
44+
if strings.Contains(stringValue, ",") {
45+
return errors.New("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
46+
}
47+
48+
coin, err := coins.ParseDecCoin(stringValue)
49+
if err != nil {
50+
return err
51+
}
52+
c.value = coin
53+
return nil
54+
}
55+
56+
func (c *decCoinValue) Type() string {
57+
return "cosmos.base.v1beta1.DecCoin"
58+
}

Diff for: client/v2/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/cosmos/cosmos-sdk v0.50.6
1414
github.com/spf13/cobra v1.8.1
1515
github.com/spf13/pflag v1.0.5
16+
github.com/stretchr/testify v1.9.0
1617
google.golang.org/grpc v1.63.2
1718
google.golang.org/protobuf v1.34.2
1819
gotest.tools/v3 v3.5.1
@@ -131,7 +132,6 @@ require (
131132
github.com/spf13/afero v1.11.0 // indirect
132133
github.com/spf13/cast v1.6.0 // indirect
133134
github.com/spf13/viper v1.19.0 // indirect
134-
github.com/stretchr/testify v1.9.0 // indirect
135135
github.com/subosito/gotenv v1.6.0 // indirect
136136
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
137137
github.com/tendermint/go-amino v0.16.0 // indirect

Diff for: client/v2/internal/coins/format.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package coins
2+
3+
import (
4+
"errors"
5+
"regexp"
6+
"strings"
7+
8+
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
9+
)
10+
11+
// Amount can be a whole number or a decimal number. Denominations can be 3 ~ 128
12+
// characters long and support letters, followed by either a letter, a number or
13+
// a separator ('/', ':', '.', '_' or '-').
14+
var coinRegex = regexp.MustCompile(`^(\d+(\.\d+)?)([a-zA-Z][a-zA-Z0-9\/\:\._\-]{2,127})$`)
15+
16+
// ParseCoin parses a coin from a string. The string must be in the format
17+
// <amount><denom>, where <amount> is a number and <denom> is a valid denom.
18+
func ParseCoin(input string) (*basev1beta1.Coin, error) {
19+
amount, denom, err := parseCoin(input)
20+
if err != nil {
21+
return nil, err
22+
}
23+
24+
return &basev1beta1.Coin{
25+
Amount: amount,
26+
Denom: denom,
27+
}, nil
28+
}
29+
30+
// ParseDecCoin parses a decCoin from a string. The string must be in the format
31+
// <amount><denom>, where <amount> is a number and <denom> is a valid denom.
32+
func ParseDecCoin(input string) (*basev1beta1.DecCoin, error) {
33+
amount, denom, err := parseCoin(input)
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
return &basev1beta1.DecCoin{
39+
Amount: amount,
40+
Denom: denom,
41+
}, nil
42+
}
43+
44+
// parseCoin parses a coin string into its amount and denom components.
45+
// The input string must be in the format <amount><denom>.
46+
// It returns the amount string, denom string, and any error encountered.
47+
// Returns an error if the input is empty or doesn't match the expected format.
48+
func parseCoin(input string) (amount, denom string, err error) {
49+
input = strings.TrimSpace(input)
50+
51+
if input == "" {
52+
return "", "", errors.New("empty input when parsing coin")
53+
}
54+
55+
matches := coinRegex.FindStringSubmatch(input)
56+
57+
if len(matches) == 0 {
58+
return "", "", errors.New("invalid input format")
59+
}
60+
61+
return matches[1], matches[3], nil
62+
}

Diff for: client/v2/internal/coins/format_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package coins
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func Test_parseCoin(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
input string
13+
amount string
14+
denom string
15+
err string
16+
}{
17+
{
18+
name: "ok",
19+
input: "1000stake",
20+
amount: "1000",
21+
denom: "stake",
22+
},
23+
{
24+
name: "empty",
25+
input: "",
26+
err: "empty input when parsing coin",
27+
},
28+
{
29+
name: "empty denom",
30+
input: "1000",
31+
err: "invalid input format",
32+
},
33+
{
34+
name: "empty amount",
35+
input: "stake",
36+
err: "invalid input format",
37+
},
38+
{
39+
name: "<denom><amount> format",
40+
input: "stake1000",
41+
err: "invalid input format",
42+
},
43+
}
44+
for _, tt := range tests {
45+
t.Run(tt.name, func(t *testing.T) {
46+
amount, denom, err := parseCoin(tt.input)
47+
if tt.err != "" {
48+
require.Error(t, err)
49+
require.Contains(t, err.Error(), tt.err)
50+
} else {
51+
require.NoError(t, err)
52+
require.Equal(t, tt.amount, amount)
53+
require.Equal(t, tt.denom, denom)
54+
}
55+
})
56+
}
57+
}
58+
59+
func TestParseCoin(t *testing.T) {
60+
encodedCoin := "1000000000foo"
61+
coin, err := ParseCoin(encodedCoin)
62+
require.NoError(t, err)
63+
require.Equal(t, "1000000000", coin.Amount)
64+
require.Equal(t, "foo", coin.Denom)
65+
}
66+
67+
func TestParseDecCoin(t *testing.T) {
68+
encodedCoin := "1000000000foo"
69+
coin, err := ParseDecCoin(encodedCoin)
70+
require.NoError(t, err)
71+
require.Equal(t, "1000000000", coin.Amount)
72+
require.Equal(t, "foo", coin.Denom)
73+
}

0 commit comments

Comments
 (0)