gno icon indicating copy to clipboard operation
gno copied to clipboard

feat(gnovm): add `Coin` constructor and more functionality

Open leohhhn opened this issue 1 year ago • 12 comments

Description

This PR ports more functionality from coin.go, into Gno (std & stdshim). It will also update the concept & reference docs for Coins.

Superseding https://github.com/gnolang/gno/pull/1696

Contributors' checklist...
  • [ ] Added new tests, or not needed, or not feasible
  • [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory
  • [ ] Updated the official documentation or not needed
  • [ ] No breaking changes were made, or a BREAKING CHANGE: xxx message was included in the description
  • [ ] Added references to related issues and PRs
  • [ ] Provided any useful hints for running manual tests
  • [ ] Added new benchmarks to generated graphs, if any. More info here.

leohhhn avatar May 14 '24 15:05 leohhhn

Codecov Report

Attention: Patch coverage is 0% with 2 lines in your changes are missing coverage. Please review.

Project coverage is 49.74%. Comparing base (ff61f86) to head (2b6fb4a).

Files Patch % Lines
gnovm/cmd/gno/transpile.go 0.00% 2 Missing :warning:
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2104      +/-   ##
==========================================
+ Coverage   49.10%   49.74%   +0.64%     
==========================================
  Files         576      576              
  Lines       77821    77822       +1     
==========================================
+ Hits        38211    38712     +501     
+ Misses      36515    35981     -534     
- Partials     3095     3129      +34     
Flag Coverage Δ
gno.land 61.62% <ø> (ø)
gnovm 44.28% <0.00%> (+2.06%) :arrow_up:

Flags with carried forward coverage won't be shown. Click here to find out more.

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

codecov[bot] avatar May 15 '24 13:05 codecov[bot]

I have some thoughts about this:

Coins and their denoms are validated in the TM2 layer. The question is - should we also implement this validation in Gno, for example:

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

    return Coin{
       Denom:  denom,
       Amount: amount,
    }
}

or, is it maybe a better option to not do "double" validation since it spends gas? basically leave the validation and panicking to TM2 instead of the VM?

So, a constructor in Gno would be:

func NewCoin(denom string, amount int64) Coin {
    return Coin{
       Denom:  denom,
       Amount: amount,
    }
}

Need some input.

leohhhn avatar May 15 '24 15:05 leohhhn

While we're on this topic, I came across this issue I made some time ago: https://github.com/gnolang/gno/issues/1676

Thinking about this a bit, we should really make a good, smooth constructor for the Coins struct.

One approach that I have is the following (in gnovm/stdlibs/std):

func NewCoins(val ...interface{}) Coins {
	if len(val)%2 != 0 {
		panic("invalid number of arguments")
	}

	cz := Coins{}
	for i := 0; i < len(val)-1; i++ {
		denom := val[i].(string) // todo validate?
		amt := val[i+1].(int64) // todo validate?
		cz = append(cz, NewCoin(denom, amt))
	}

	return cz
}

I'm aware that this is not the safest approach, but it's probably the most comfortable to use. With this, you can just do

coins := std.NewCoins("ugnot", 100, "othercoin", 200)

and its very painless, instead of coins:= std.Coins{std.Coin{ "ugnot", coinToSend }}).

WDYT?

leohhhn avatar May 15 '24 16:05 leohhhn

I have some thoughts about this:

Coins and their denoms are validated in the TM2 layer. The question is - should we also implement this validation in Gno, for example:

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

    return Coin{
       Denom:  denom,
       Amount: amount,
    }
}

or, is it maybe a better option to not do "double" validation since it spends gas? basically leave the validation and panicking to TM2 instead of the VM?

So, a constructor in Gno would be:

func NewCoin(denom string, amount int64) Coin {
    return Coin{
       Denom:  denom,
       Amount: amount,
    }
}

Need some input.

If validation isn't conducted within constructor of coin.gno, it'll be deferred to tm2 coin.go, leading to additional gas consumption due to extra operations. However, the efficiency depends on how frequently invalid denominations or amounts are set. This consideration is pivotal in determining the optimal approach. These are just initial thoughts, so I'm not entirely certain.

ltzmaxwell avatar May 16 '24 03:05 ltzmaxwell

@ltzmaxwell thank you for your answer - it's possible that I wasn't fully clear - currently, there is no validation in the coins.gno file as it doesn't exist. So all validation is done in TM2 currently. The question is the following:

If we choose to add validation in coins.gno, will that not introduce double validation (on both the TM2 and Gno level) and thus use up more (double) the gas?

My question is the following: should we just not check at the Gno level, and leave the errors & panics to TM2 in this case, exactly to avoid using more gas than needed?

I will play around with this and measure the gas usage in both cases.

BTW, do you have any opinions on this comment?

leohhhn avatar May 16 '24 10:05 leohhhn

If we choose to add validation in coins.gno, will that not introduce double validation (on both the TM2 and Gno level) and thus use up more (double) the gas?

My question is the following: should we just not check at the Gno level, and leave the errors & panics to TM2 in this case, exactly to avoid using more gas than needed?

I will play around with this and measure the gas usage in both cases.

what I see in tm2/pkg/std.coin.go, the validation happens in Constructor and IsValid().

assume we are going to initiate a new coin with denominations of "Ugnot", which is obviously invalid,

  • If validation is implemented at the gno level, the system will immediately identify the error and halt execution;
  • Alternatively, if there is no gno level validation, the error will be caught later during the IsValid() check, leading to a halt in execution, albeit through a longer process.

On the other hand, I believe our priorities should be arranged as follows: security takes precedence, followed by readability and maintainability, and finally, gas consumption.

ltzmaxwell avatar May 17 '24 03:05 ltzmaxwell

While we're on this topic, I came across this issue I made some time ago: #1676

Thinking about this a bit, we should really make a good, smooth constructor for the Coins struct.

One approach that I have is the following (in gnovm/stdlibs/std):

func NewCoins(val ...interface{}) Coins {
	if len(val)%2 != 0 {
		panic("invalid number of arguments")
	}

	cz := Coins{}
	for i := 0; i < len(val)-1; i++ {
		denom := val[i].(string) // todo validate?
		amt := val[i+1].(int64) // todo validate?
		cz = append(cz, NewCoin(denom, amt))
	}

	return cz
}

I'm aware that this is not the safest approach, but it's probably the most comfortable to use. With this, you can just do

coins := std.NewCoins("ugnot", 100, "othercoin", 200)

and its very painless, instead of coins:= std.Coins{std.Coin{ "ugnot", coinToSend }}).

WDYT?

I like this.

ltzmaxwell avatar May 17 '24 07:05 ltzmaxwell

Marking as ready for review for visibility and inputs from the team.

leohhhn avatar May 17 '24 09:05 leohhhn

Ran into an issue with regexp: https://github.com/gnolang/gno/issues/2139

leohhhn avatar May 17 '24 12:05 leohhhn

@thehowl

Super weird thing I'm not understanding - since we have std & stdshim and both apparently need to exist right now, how is it possible that at 46a8061 commit, I've only added changes to the std and not to stdshim, and the txtar tests are passing and functions working properly? O_O

leohhhn avatar May 17 '24 13:05 leohhhn

Ok, the current state is this - we have:

  • Coin.Add,
  • Coin.Sub,
  • Coin.IsPositive,
  • Coin.IsNegative,
  • Coin.IsZero

And constructors for Coin, as well as two constructors for Coins - a "safe" one, and an "unsafe" one (see discussion)

leohhhn avatar May 17 '24 13:05 leohhhn

Will work on updating the docs when I have approvals for these functionalities from the team.

leohhhn avatar May 17 '24 13:05 leohhhn

I did some changes in this PR, starting from wanting to verify that the fix in #2168 did indeed fix the issue we were having here

I saw some build issues and started investigating, but essentially stdshim was using math/overflow, which cannot be used right now because it is not in the stdlibWhitelist. (The error was not printed due to a swallowed error; this is fixed by #1695, but that PR is still stuck in review limbo)

I changed the stdshim version not to use math/overflow, so we can safely merge this for now; it matters little anyway (only makes the code "unsafe" for transpiled code, used literally nowhere), and it won't matter entirely after #1695 is merged.

thehowl avatar May 24 '24 09:05 thehowl

There are still some failing tests, but these seem in "userland" so I'll leave you to fix them

thehowl avatar May 24 '24 09:05 thehowl

@thehowl thanks for helping out. Fixed the tests, as well as @zivkovicmilos's comments.

leohhhn avatar May 26 '24 17:05 leohhhn

Found a GnoVM bug: https://github.com/gnolang/gno/issues/2204

I implemented a duplicate check on the Coins ctor and added the docs: c82680a

leohhhn avatar May 26 '24 19:05 leohhhn

Not sure why CI is failing, txtar integration tests are all passing locally.

leohhhn avatar May 26 '24 19:05 leohhhn

Codecov broken? cc @ajnavarro

leohhhn avatar May 27 '24 10:05 leohhhn