diff --git a/CHANGELOG.md b/CHANGELOG.md index f17ad52c476a..abc6f483130b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,6 +163,7 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa ### Features +* [\#6755](https://github.com/cosmos/cosmos-sdk/pull/6755) Add custom regex validation for `Coin` denom by overwriting `CoinDenomRegex` when using `/types/coin.go`. * [\#7265](https://github.com/cosmos/cosmos-sdk/pull/7265) Support Tendermint block pruning through a new `min-retain-blocks` configuration that can be set in either `app.toml` or via the CLI. This parameter is used in conjunction with other criteria to determine the height at which Tendermint should prune blocks. * (vesting) [\#7209](https://github.com/cosmos/cosmos-sdk/pull/7209) Create new `MsgCreateVestingAccount` message type along with CLI handler that allows for the creation of delayed and continuous vesting types. * (events) [\#7121](https://github.com/cosmos/cosmos-sdk/pull/7121) The application now drives what events are indexed by Tendermint via the `index-events` configuration in `app.toml`, which is a list of events taking the form `{eventType}.{attributeKey}`. diff --git a/types/coin.go b/types/coin.go index a2d7364a0f2e..8ac9a8843d4a 100644 --- a/types/coin.go +++ b/types/coin.go @@ -602,15 +602,32 @@ var ( reAmt = `[[:digit:]]+` reDecAmt = `[[:digit:]]*\.[[:digit:]]+` reSpc = `[[:space:]]*` - reDnm = regexp.MustCompile(fmt.Sprintf(`^%s$`, reDnmString)) - reCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, reDnmString)) - reDecCoin = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, reDnmString)) + reDnm = returnReDnm + reCoin = returnReCoin + reDecCoin = returnDecCoin ) -// ValidateDenom validates a denomination string returning an error if it is -// invalid. +func returnDecCoin() *regexp.Regexp { + return regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reDecAmt, reSpc, CoinDenomRegex())) +} +func returnReCoin() *regexp.Regexp { + return regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, reAmt, reSpc, CoinDenomRegex())) +} +func returnReDnm() *regexp.Regexp { + return regexp.MustCompile(fmt.Sprintf(`^%s$`, CoinDenomRegex())) +} + +// DefaultCoinDenomRegex returns the default regex string +func DefaultCoinDenomRegex() string { + return reDnmString +} + +// CoinDenomRegex returns the current regex string and can be overwritten for custom validation +var CoinDenomRegex = DefaultCoinDenomRegex + +// ValidateDenom is the default validation function for Coin.Denom. func ValidateDenom(denom string) error { - if !reDnm.MatchString(denom) { + if !reDnm().MatchString(denom) { return fmt.Errorf("invalid denom: %s", denom) } return nil @@ -628,7 +645,7 @@ func mustValidateDenom(denom string) { func ParseCoin(coinStr string) (coin Coin, err error) { coinStr = strings.TrimSpace(coinStr) - matches := reCoin.FindStringSubmatch(coinStr) + matches := reCoin().FindStringSubmatch(coinStr) if matches == nil { return Coin{}, fmt.Errorf("invalid coin expression: %s", coinStr) } diff --git a/types/coin_test.go b/types/coin_test.go index f3cf0647a7f3..9d4130576b91 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -98,6 +98,30 @@ func (s *coinTestSuite) TestCoinIsValid() { } } +func (s *coinTestSuite) TestCustomValidation() { + + newDnmRegex := `[\x{1F600}-\x{1F6FF}]` + sdk.CoinDenomRegex = func() string { + return newDnmRegex + } + + cases := []struct { + coin sdk.Coin + expectPass bool + }{ + {sdk.Coin{"🙂", sdk.NewInt(1)}, true}, + {sdk.Coin{"🙁", sdk.NewInt(1)}, true}, + {sdk.Coin{"🌶", sdk.NewInt(1)}, false}, // outside the unicode range listed above + {sdk.Coin{"asdf", sdk.NewInt(1)}, false}, + {sdk.Coin{"", sdk.NewInt(1)}, false}, + } + + for i, tc := range cases { + s.Require().Equal(tc.expectPass, tc.coin.IsValid(), "unexpected result for IsValid, tc #%d", i) + } + sdk.CoinDenomRegex = sdk.DefaultCoinDenomRegex +} + func (s *coinTestSuite) TestAddCoin() { cases := []struct { inputOne sdk.Coin diff --git a/types/dec_coin.go b/types/dec_coin.go index 6e400d7a0557..a8ad465c6122 100644 --- a/types/dec_coin.go +++ b/types/dec_coin.go @@ -615,7 +615,7 @@ func (coins DecCoins) Sort() DecCoins { func ParseDecCoin(coinStr string) (coin DecCoin, err error) { coinStr = strings.TrimSpace(coinStr) - matches := reDecCoin.FindStringSubmatch(coinStr) + matches := reDecCoin().FindStringSubmatch(coinStr) if matches == nil { return DecCoin{}, fmt.Errorf("invalid decimal coin expression: %s", coinStr) }