Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: Added IsValid to Coin #4558

Merged
merged 8 commits into from
Jun 18, 2019
Merged

R4R: Added IsValid to Coin #4558

merged 8 commits into from
Jun 18, 2019

Conversation

colin-axner
Copy link
Contributor

closes #4556

  • Targeted PR against correct branch (see CONTRIBUTING.md)

  • Linked to github-issue with discussion and accepted design OR link to spec that describes this work.

  • Wrote tests

  • Updated relevant documentation (docs/)

  • Added a relevant changelog entry: clog add [section] [stanza] [message]

  • rereviewed Files changed in the github PR explorer


For Admin Use:

  • Added appropriate labels to PR (ex. wip, ready-for-review, docs)
  • Reviewers Assigned
  • Squashed all commits, uses message "Merge pull request #XYZ: [title]" (coding standards)

types/coin.go Outdated Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Jun 14, 2019

Codecov Report

Merging #4558 into master will increase coverage by 0.45%.
The diff coverage is 100%.

@@            Coverage Diff            @@
##           master   #4558      +/-   ##
=========================================
+ Coverage   53.04%   53.5%   +0.45%     
=========================================
  Files         258     257       -1     
  Lines       16288   16177     -111     
=========================================
+ Hits         8640    8655      +15     
+ Misses       7002    6876     -126     
  Partials      646     646

Copy link
Collaborator

@fedekunze fedekunze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s add a blank string test with more than 3 spaces

Copy link
Collaborator

@fedekunze fedekunze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet ! Thanks Colin !

Copy link
Contributor

@alessio alessio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsValid() was removed for two reasons:

  1. developers must use NewCoin() to initialise coins
  2. we wanted validation to live in one place only

I believe this PR requires further discussion before merging.

@fedekunze
Copy link
Collaborator

@alessio I agree that users should use the constructors, but we can't enforce them to do it. We should then support these sorts of functions to increase the security of other chains on the SDK. Also, this might be useful to verify single coin parameters such as #4472 .

types/coin.go Outdated
@@ -51,6 +51,15 @@ func (coin Coin) String() string {
return fmt.Sprintf("%v%v", coin.Amount, coin.Denom)
}

// IsValid asserts that the Coin has a positive amount and the Denom does not contain
// upper case characters and has a length of 3 ~ 16 characters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simply update this godoc to state:

// IsValid asserts that the Coin has a positive amount and the Denom is valid.

Copy link
Contributor

@alexanderbez alexanderbez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK

@alessio
Copy link
Contributor

alessio commented Jun 16, 2019

but we can't enforce them to do it.

We can by not providing IsValid(). The Coin type is guaranteed to be safe as long as you comply with the interface (and dev docs). In other words, if you don't initialise a Coin structure via a NewCoin call then you are on your own and can't blame us :)

@alessio
Copy link
Contributor

alessio commented Jun 16, 2019

Plus, validation should really live in one place only. It does not feel right to duplicate validation logic in multiple places...

@alessio
Copy link
Contributor

alessio commented Jun 16, 2019

func NewCoin(denom string, amount Int) Coin {
	if err := validate(denom, amount); err != nil {
		panic(err)
	} 
}


func validate(denom string, amount Int) error {
	if err := validateDenom(c.Denom); err != nil {
		return err
	}

	if amount.LT(ZeroInt()) {
		return fmt.Errorf("negative coin amount: %v", amount)
	}

	return nil
}

func (c Coin) IsValid() bool {
	if err := validate(denom, amount); err != nil {
		return true
	}
	return false
}

@fedekunze
Copy link
Collaborator

@colin-axner just had a quick chat with @alessio. What he's saying is that for sdk.NewCoin a 0 amount coin is valid but on the IsValid it's not. Can we change it so it's consistent in both implementations?

@alexanderbez and I agree on having the 0 amount coin to be invalid in both funcs.

@alexanderbez
Copy link
Contributor

Agreed. We should not allow 0 coins as input anywhere. We should have a single public validation function that we use.

Copy link
Collaborator

@fedekunze fedekunze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also change the coins.IsValid() func to just iterate over the coins and call the coin.IsValid func() ?

EDIT: completely forgot about the sort, please ignore the comment

@colin-axner
Copy link
Contributor Author

Should validateDenom be made public? I could see use cases where a Msg requires a valid denom but no coin(s) and therefore would require a public validation function separate from the one used for Coin

@colin-axner
Copy link
Contributor Author

I have been going through trying to update tests and code to reflect 0 amount coin as invalid, but the usage of 0 coins seems to be deeply ingrained in the code/tests. For example: in minter it appears it should be valid to have block provisions of zero. Abiding by the updated constructor for Coin would force me to use Coin{} which seems risky/messy.

A lot of the simulations/tests also use empty/zero coins to test various functionality, originally I was changing these to use Coin{} to bypass the panic but would it be better to eliminate the usage of zero coins entirely?

Also DecCoin should also probably go through the same validation as Coin but to get all validation to live under the same func, I would probably need to add some type of interface for Int and Dec and pass that in as the amount param in Validate.

This is starting to be a time sink and causing me a lot of trouble. What direction should I take this? I still think it is very important to introduce some sort of public validation function coin(s)/denoms can use, though I starting to wonder if removal of zero coins should be done in a separate pr.

Thoughts? @fedekunze @alexanderbez @alessio

@alessio
Copy link
Contributor

alessio commented Jun 18, 2019

I did remember that in few places we do take 0 as valid Coin amount.
All Coins operations remove zero coins before doing work, so 0-amount Coin are already taken into account.
IMHO we should continue accepting 0 as valid coin amount for both the constructor and IsValid() function. IsZero() is the function that clients can call that to check against zero.

Copy link
Contributor

@alexanderbez alexanderbez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK, I think the changes are fine for now @colin-axner.

types/coin.go Outdated
return err
}

if amount.LTE(ZeroInt()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@colin-axner lets go with LT for now so we can get the build passing and as this was the original behavior.

types/coin.go Outdated
@@ -51,6 +49,28 @@ func (coin Coin) String() string {
return fmt.Sprintf("%v%v", coin.Amount, coin.Denom)
}

// Validate returns an error if the Coin has a nonpositive amount or if
// the Denom is invalid.
func Validate(denom string, amount Int) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not export this one please

types/coin.go Outdated
@@ -51,6 +49,28 @@ func (coin Coin) String() string {
return fmt.Sprintf("%v%v", coin.Amount, coin.Denom)
}

// Validate returns an error if the Coin has a nonpositive amount or if
// the Denom is invalid.
func Validate(denom string, amount Int) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func Validate(denom string, amount Int) error {
func validate(denom string, amount Int) error {

types/coin.go Outdated

if amount.LT(ZeroInt()) {
panic(fmt.Errorf("negative coin amount: %v", amount))
if err := Validate(denom, amount); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err := Validate(denom, amount); err != nil {
if err := validate(denom, amount); err != nil {

types/coin.go Outdated

// IsValid asserts that the Coin has a positive amount and the Denom is vaild.
func (coin Coin) IsValid() bool {
if err := Validate(coin.Denom, coin.Amount); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if err := Validate(coin.Denom, coin.Amount); err != nil {
if err := validate(coin.Denom, coin.Amount); err != nil {

@@ -56,7 +77,7 @@ func TestAddCoin(t *testing.T) {
shouldPanic bool
}{
{NewInt64Coin(testDenom1, 1), NewInt64Coin(testDenom1, 1), NewInt64Coin(testDenom1, 2), false},
{NewInt64Coin(testDenom1, 1), NewInt64Coin(testDenom1, 0), NewInt64Coin(testDenom1, 1), false},
{NewInt64Coin(testDenom1, 1), Coin{testDenom1, NewInt(0)}, NewInt64Coin(testDenom1, 1), false},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd revert this

@@ -80,7 +101,7 @@ func TestSubCoin(t *testing.T) {
{NewInt64Coin(testDenom1, 1), NewInt64Coin(testDenom2, 1), NewInt64Coin(testDenom1, 1), true},
{NewInt64Coin(testDenom1, 10), NewInt64Coin(testDenom1, 1), NewInt64Coin(testDenom1, 9), false},
{NewInt64Coin(testDenom1, 5), NewInt64Coin(testDenom1, 3), NewInt64Coin(testDenom1, 2), false},
{NewInt64Coin(testDenom1, 5), NewInt64Coin(testDenom1, 0), NewInt64Coin(testDenom1, 5), false},
{NewInt64Coin(testDenom1, 5), Coin{testDenom1, NewInt(0)}, NewInt64Coin(testDenom1, 5), false},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto - I'd revert this

@@ -133,7 +154,7 @@ func TestIsLTCoin(t *testing.T) {
}{
{NewInt64Coin(testDenom1, 1), NewInt64Coin(testDenom1, 1), false, false},
{NewInt64Coin(testDenom1, 2), NewInt64Coin(testDenom1, 1), false, false},
{NewInt64Coin(testDenom1, 0), NewInt64Coin(testDenom2, 1), false, true},
{Coin{testDenom1, NewInt(0)}, NewInt64Coin(testDenom2, 1), false, true},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -150,7 +171,7 @@ func TestIsLTCoin(t *testing.T) {
}

func TestCoinIsZero(t *testing.T) {
coin := NewInt64Coin(testDenom1, 0)
coin := Coin{testDenom1, NewInt(0)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto - and so on

types/coin.go Outdated
return err
}

if amount.LTE(ZeroInt()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if amount.LTE(ZeroInt()) {
if amount.LT(ZeroInt()) {

types/coin.go Outdated
return nil
}

// IsValid asserts that the Coin has a positive amount and the Denom is vaild.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// IsValid asserts that the Coin has a positive amount and the Denom is vaild.
// IsValid returns true if the Coin has a non-negative amount and the denom is valid.

@alessio alessio dismissed fedekunze’s stale review June 18, 2019 16:01

Comments addressed or taken back (#4558 (review))

Copy link
Contributor

@alessio alessio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good one!

@alessio alessio merged commit 314af42 into cosmos:master Jun 18, 2019
@codecov
Copy link

codecov bot commented Jun 18, 2019

Codecov Report

Merging #4558 into master will increase coverage by 0.45%.
The diff coverage is 100%.

@@            Coverage Diff            @@
##           master   #4558      +/-   ##
=========================================
+ Coverage   53.04%   53.5%   +0.45%     
=========================================
  Files         258     257       -1     
  Lines       16288   16177     -111     
=========================================
+ Hits         8640    8655      +15     
+ Misses       7002    6876     -126     
  Partials      646     646

@colin-axner colin-axner mentioned this pull request Sep 16, 2020
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add IsValid function to Coin
5 participants