Skip to content

Commit

Permalink
fix(client/v2/autocli): add CoinDec flag (backport #22817) (#22821)
Browse files Browse the repository at this point in the history
Co-authored-by: Julián Toledano <[email protected]>
Co-authored-by: Julien Robert <[email protected]>
  • Loading branch information
3 people authored Dec 10, 2024
1 parent 96a3016 commit 4a73a1e
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 6 deletions.
6 changes: 6 additions & 0 deletions client/v2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

## [v2.0.0-beta.7] - 2024-12-10

### Bug Fixes

* [#22817](https://github.com/cosmos/cosmos-sdk/pull/22817) Add DecCoin support in autocli flag builder.

## [v2.0.0-beta.6] - 2024-11-21

### Improvements
Expand Down
1 change: 1 addition & 0 deletions client/v2/autocli/flag/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (b *Builder) init() {
b.messageFlagTypes["google.protobuf.Timestamp"] = timestampType{}
b.messageFlagTypes["google.protobuf.Duration"] = durationType{}
b.messageFlagTypes["cosmos.base.v1beta1.Coin"] = coinType{}
b.messageFlagTypes["cosmos.base.v1beta1.DecCoin"] = decCoinType{}
}

if b.scalarFlagTypes == nil {
Expand Down
13 changes: 8 additions & 5 deletions client/v2/autocli/flag/coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package flag

import (
"context"
"fmt"
"errors"
"strings"

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

basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/core/coins"
"cosmossdk.io/client/v2/internal/coins"
)

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

func (c coinType) DefaultValue() string {
stringCoin, _ := coins.FormatCoins([]*basev1beta1.Coin{}, nil)
return stringCoin
return "zero"
}

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

func (c *coinValue) String() string {
if c.value == nil {
return ""
}

return c.value.String()
}

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

coin, err := coins.ParseCoin(stringValue)
Expand Down
58 changes: 58 additions & 0 deletions client/v2/autocli/flag/dec_coin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package flag

import (
"context"
"errors"
"strings"

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

basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/client/v2/internal/coins"
)

type decCoinType struct{}

type decCoinValue struct {
value *basev1beta1.DecCoin
}

func (c decCoinType) NewValue(*context.Context, *Builder) Value {
return &decCoinValue{}
}

func (c decCoinType) DefaultValue() string {
return "zero"
}

func (c *decCoinValue) Get(protoreflect.Value) (protoreflect.Value, error) {
if c.value == nil {
return protoreflect.Value{}, nil
}
return protoreflect.ValueOfMessage(c.value.ProtoReflect()), nil
}

func (c *decCoinValue) String() string {
if c.value == nil {
return ""
}

return c.value.String()
}

func (c *decCoinValue) Set(stringValue string) error {
if strings.Contains(stringValue, ",") {
return errors.New("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
}

coin, err := coins.ParseDecCoin(stringValue)
if err != nil {
return err
}
c.value = coin
return nil
}

func (c *decCoinValue) Type() string {
return "cosmos.base.v1beta1.DecCoin"
}
2 changes: 1 addition & 1 deletion client/v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/cosmos/cosmos-sdk v0.50.6
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.34.2
gotest.tools/v3 v3.5.1
Expand Down Expand Up @@ -131,7 +132,6 @@ require (
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
Expand Down
62 changes: 62 additions & 0 deletions client/v2/internal/coins/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package coins

import (
"errors"
"regexp"
"strings"

basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
)

// Amount can be a whole number or a decimal number. Denominations can be 3 ~ 128
// characters long and support letters, followed by either a letter, a number or
// a separator ('/', ':', '.', '_' or '-').
var coinRegex = regexp.MustCompile(`^(\d+(\.\d+)?)([a-zA-Z][a-zA-Z0-9\/\:\._\-]{2,127})$`)

// ParseCoin parses a coin from a string. The string must be in the format
// <amount><denom>, where <amount> is a number and <denom> is a valid denom.
func ParseCoin(input string) (*basev1beta1.Coin, error) {
amount, denom, err := parseCoin(input)
if err != nil {
return nil, err
}

return &basev1beta1.Coin{
Amount: amount,
Denom: denom,
}, nil
}

// ParseDecCoin parses a decCoin from a string. The string must be in the format
// <amount><denom>, where <amount> is a number and <denom> is a valid denom.
func ParseDecCoin(input string) (*basev1beta1.DecCoin, error) {
amount, denom, err := parseCoin(input)
if err != nil {
return nil, err
}

return &basev1beta1.DecCoin{
Amount: amount,
Denom: denom,
}, nil
}

// parseCoin parses a coin string into its amount and denom components.
// The input string must be in the format <amount><denom>.
// It returns the amount string, denom string, and any error encountered.
// Returns an error if the input is empty or doesn't match the expected format.
func parseCoin(input string) (amount, denom string, err error) {
input = strings.TrimSpace(input)

if input == "" {
return "", "", errors.New("empty input when parsing coin")
}

matches := coinRegex.FindStringSubmatch(input)

if len(matches) == 0 {
return "", "", errors.New("invalid input format")
}

return matches[1], matches[3], nil
}
73 changes: 73 additions & 0 deletions client/v2/internal/coins/format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package coins

import (
"testing"

"github.com/stretchr/testify/require"
)

func Test_parseCoin(t *testing.T) {
tests := []struct {
name string
input string
amount string
denom string
err string
}{
{
name: "ok",
input: "1000stake",
amount: "1000",
denom: "stake",
},
{
name: "empty",
input: "",
err: "empty input when parsing coin",
},
{
name: "empty denom",
input: "1000",
err: "invalid input format",
},
{
name: "empty amount",
input: "stake",
err: "invalid input format",
},
{
name: "<denom><amount> format",
input: "stake1000",
err: "invalid input format",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
amount, denom, err := parseCoin(tt.input)
if tt.err != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.err)
} else {
require.NoError(t, err)
require.Equal(t, tt.amount, amount)
require.Equal(t, tt.denom, denom)
}
})
}
}

func TestParseCoin(t *testing.T) {
encodedCoin := "1000000000foo"
coin, err := ParseCoin(encodedCoin)
require.NoError(t, err)
require.Equal(t, "1000000000", coin.Amount)
require.Equal(t, "foo", coin.Denom)
}

func TestParseDecCoin(t *testing.T) {
encodedCoin := "1000000000foo"
coin, err := ParseDecCoin(encodedCoin)
require.NoError(t, err)
require.Equal(t, "1000000000", coin.Amount)
require.Equal(t, "foo", coin.Denom)
}

0 comments on commit 4a73a1e

Please sign in to comment.