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

feat(examples): grc20 refactor #2983

Merged
merged 30 commits into from
Nov 12, 2024
Merged

Conversation

moul
Copy link
Member

@moul moul commented Oct 19, 2024

This PR extracts the grc20 refactor from #2551, which is a meta PR containing several contract improvements and additions that depend on new Gnovm features that haven't been merged yet.

Please review this grc20 refactor with a focus on its API. Several valuable comments can be found in #2551. Additionally, you can discover new contracts using grc20 in #2551, such as minidex, atomicswap, grc20reg, test20, and vault.

Addresses #1832

@moul moul self-assigned this Oct 19, 2024
@moul moul mentioned this pull request Oct 19, 2024
@github-actions github-actions bot added the 🧾 package/realm Tag used for new Realms or Packages. label Oct 19, 2024
Copy link

codecov bot commented Oct 19, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 63.76%. Comparing base (60304df) to head (b40f6fb).
Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #2983   +/-   ##
=======================================
  Coverage   63.76%   63.76%           
=======================================
  Files         548      548           
  Lines       78681    78681           
=======================================
+ Hits        50171    50172    +1     
- Misses      25128    25129    +1     
+ Partials     3382     3380    -2     
Flag Coverage Δ
contribs/gnodev 61.16% <ø> (ø)
contribs/gnofaucet 14.82% <ø> (ø)
gno.land 73.62% <ø> (ø)
gnovm 67.92% <ø> (ø)
tm2 62.41% <ø> (-0.01%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

moul added 2 commits October 18, 2024 21:23
Signed-off-by: moul <[email protected]>
Signed-off-by: moul <[email protected]>
@moul moul marked this pull request as ready for review October 19, 2024 01:32
@moul moul requested review from a team as code owners October 19, 2024 01:32
@moul moul requested review from jaekwon and gfanton and removed request for a team October 19, 2024 01:32
@leohhhn
Copy link
Contributor

leohhhn commented Oct 19, 2024

cc @MikaelVallenet @n0izn0iz, any thoughts?

@MikaelVallenet
Copy link
Member

cc @MikaelVallenet @n0izn0iz, any thoughts?

looks good to me, i like the teller pattern & add metadata in token level instead of ledger/bank
i'm just thinking if it should take another pkg as an extension of primitive grc20 pkg

Copy link
Member

@thehowl thehowl left a comment

Choose a reason for hiding this comment

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

Still mostly naming considerations.

examples/gno.land/p/demo/grc/grc20/tellers.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/tellers.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/tellers.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/types.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/types.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/types.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/token.gno Outdated Show resolved Hide resolved
@Kouteki Kouteki added this to the 🚀 Mainnet launch milestone Oct 29, 2024
@Kouteki Kouteki removed the request for review from a team October 29, 2024 16:16
@github-actions github-actions bot added the 📦 🌐 tendermint v2 Issues or PRs tm2 related label Oct 29, 2024
@moul
Copy link
Member Author

moul commented Oct 29, 2024

Merged #3046 over this one to make the CI green.

Depends on #3046.

Copy link
Member

@thehowl thehowl left a comment

Choose a reason for hiding this comment

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

Overall implementation and naming LGTM.

examples/gno.land/p/demo/grc/grc20/tellers.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/tellers.gno Outdated Show resolved Hide resolved
examples/gno.land/r/demo/bar20/bar20.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/types.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/token.gno Show resolved Hide resolved
Copy link
Contributor

@n0izn0iz n0izn0iz left a comment

Choose a reason for hiding this comment

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

@moul
Copy link
Member Author

moul commented Oct 31, 2024

@leohhhn do you want to update the doc in this PR or in your big refactor?

Copy link
Contributor

@leohhhn leohhhn left a comment

Choose a reason for hiding this comment

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

LGTM; I feel like showcases and more examples will come after we merge this PR.

I'll also port my constant product dex example afterwards.

As for the doc, I can push changes to your branch here to update it, or make a PR when we merge this. Let me know how you want to go about it.

examples/gno.land/p/demo/grc/grc20/types.gno Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/tellers.gno Outdated Show resolved Hide resolved
examples/gno.land/p/demo/grc/grc20/tellers.gno Outdated Show resolved Hide resolved
@thehowl thehowl merged commit 3bb666c into gnolang:master Nov 12, 2024
132 of 134 checks passed
r3v4s pushed a commit to gnoswap-labs/gno that referenced this pull request Dec 10, 2024
This PR extracts the grc20 refactor from gnolang#2551, which is a meta PR
containing several contract improvements and additions that depend on
new Gnovm features that haven't been merged yet.

Please review this grc20 refactor with a focus on its API. Several
valuable comments can be found in gnolang#2551. Additionally, you can discover
new contracts using grc20 in gnolang#2551, such as `minidex`, `atomicswap`,
`grc20reg`, `test20`, and `vault`.

Addresses gnolang#1832

---------

Signed-off-by: moul <[email protected]>
Co-authored-by: Morgan <[email protected]>
Co-authored-by: Morgan Bazalgette <[email protected]>
Co-authored-by: Leon Hudak <[email protected]>
mvertes added a commit that referenced this pull request Jan 9, 2025
I propose that we implement overflow checking directly in gnovm opcodes,
and that gnovm always enforces overflow checking. Overflow checking
becomes a capacity of the Gno language and the Gno virtual machine.

It's important for a smart contract platform to offer by default, and
without user or developer effort, the strongest guarantees on numerical
operations.

In that topic, Gno would be superior to the standard Go runtime which,
like C and most other languages, don't address this internally beside
constants (to preserve the best possible native performances), and rely
on external user code.

It would also simplify the user code and avoid to use specific
libraries.
For example, in `gnovm/stdlibs/std/coins.go`, for the `Coin.Add` method:

Before:

```go
import "math/overflow"

func (c Coin) Add(other Coin) Coin {
    mustMatchDenominations(c.Denom, other.Denom)
 
    sum, ok := overflow.Add64(c.Amount, other.Amount)
    if !ok {
        panic("coin add overflow/underflow: " +
              strconv.Itoa(int(c.Amount)) + " +/- " +
              strconv.Itoa(int(other.Amount)))
    }

    c.Amount = sum
    return c
}
```

After:

```go
func (c Coin) Add(other Coin) Coin {
    mustMatchDenominations(c.Denom, other.Denom)
    c.Amount += other.Amount
    return c
} 
```
with the same behaviour for overflow checking. Note also that the new
version, is not only simpler, but also faster, because overflow checking
is performed natively, and not interpreted.

Integer overflow handling is only implemented for signed integers.
Unsigned integers, on purpose, just wrap around when reaching their
maximum or minimum values. This is intended to support all crypto, hash
and bitwise operations which may rely on that wrap around property.
Division by zero is still handled both in signed and unsigned integers.

Note: from now, on security level, the use of unsigned integers for
standard numeric operations should be probably considered suspicious.

## Benchmark

To measure the impact of overflow, I execute the following benchmarks:

First a micro benchmark comparing an addition of 2 ints, with and
without overflow:


```go
//go:noinline
func AddNoOverflow(x, y int) int { return x + y }

func BenchmarkAddNoOverflow(b *testing.B) {
    x, y := 4, 3
    c := 0
    for range b.N {
        c = AddNoOverflow(x, y)
    }
    if c != 7 {
        b.Error("invalid result")
    }
}

func BenchmarkAddOverflow(b *testing.B) {
    x, y := 4, 3
    c := 0
    for range b.N {
        c = overflow.Addp(x, y)
    }
    if c != 7 {
        b.Error("invalid result")
    }
}
```

The implementation of overflow checking is taken from
http://github.com/gnolang/overflow, already used in tm2.

It gives the following results:

```console
$ go test -v- run=^# -benchmem -bench=Overflow
goos: darwin
goarch: arm64
pkg: github.com/gnolang/gno/gnovm/pkg/gnolang
cpu: Apple M1
BenchmarkAddNoOverflow
BenchmarkAddNoOverflow-8    1000000000           0.9392 ns/op          0 B/op          0 allocs/op
BenchmarkAddOverflow
BenchmarkAddOverflow-8      568881582            2.101 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/gnolang/gno/gnovm/pkg/gnolang    2.640s
```

Checking overflow doubles the execution time of an addition from 1 ns/op
to 2 ns/op.

But at 2 ns, the total time is still an order of magnitude lower than
the cost of running the VM.
The impact of overflow check doesn't even appear when benchmarking at VM
level with the following:

```go
func BenchmarkOpAdd(b *testing.B) {
    m := NewMachine("bench", nil)
    x := TypedValue{T: IntType}
    x.SetInt(4)
    y := TypedValue{T: IntType}
    y.SetInt(3)

    b.ResetTimer()

    for range b.N {
        m.PushOp(OpHalt)
        m.PushExpr(&BinaryExpr{})
        m.PushValue(x)
        m.PushValue(y)
        m.PushOp(OpAdd)
        m.Run()
    }
}
```

Which gives something like:

```console
$ go test -v -benchmem -bench=OpAdd -run=^#
goos: darwin
goarch: arm64
pkg: github.com/gnolang/gno/gnovm/pkg/gnolang
cpu: Apple M1
BenchmarkOpAdd
BenchmarkOpAdd-8    16069832            74.41 ns/op      163 B/op          1 allocs/op
PASS
ok      github.com/gnolang/gno/gnovm/pkg/gnolang    1.526
```

Where the execution time varie from 60 ns/op to 100 ns/op for both
versions of addition, with or without overflow.

## Related PRs and issues

- PRs: 
    - #3197 
    - #3192
    - #3117
    - #2983
    - #2905 
    - #2698
- Issues: 
    - #2873
    - #1844
    - #1729


<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] 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
</details>
albttx pushed a commit that referenced this pull request Jan 10, 2025
I propose that we implement overflow checking directly in gnovm opcodes,
and that gnovm always enforces overflow checking. Overflow checking
becomes a capacity of the Gno language and the Gno virtual machine.

It's important for a smart contract platform to offer by default, and
without user or developer effort, the strongest guarantees on numerical
operations.

In that topic, Gno would be superior to the standard Go runtime which,
like C and most other languages, don't address this internally beside
constants (to preserve the best possible native performances), and rely
on external user code.

It would also simplify the user code and avoid to use specific
libraries.
For example, in `gnovm/stdlibs/std/coins.go`, for the `Coin.Add` method:

Before:

```go
import "math/overflow"

func (c Coin) Add(other Coin) Coin {
    mustMatchDenominations(c.Denom, other.Denom)
 
    sum, ok := overflow.Add64(c.Amount, other.Amount)
    if !ok {
        panic("coin add overflow/underflow: " +
              strconv.Itoa(int(c.Amount)) + " +/- " +
              strconv.Itoa(int(other.Amount)))
    }

    c.Amount = sum
    return c
}
```

After:

```go
func (c Coin) Add(other Coin) Coin {
    mustMatchDenominations(c.Denom, other.Denom)
    c.Amount += other.Amount
    return c
} 
```
with the same behaviour for overflow checking. Note also that the new
version, is not only simpler, but also faster, because overflow checking
is performed natively, and not interpreted.

Integer overflow handling is only implemented for signed integers.
Unsigned integers, on purpose, just wrap around when reaching their
maximum or minimum values. This is intended to support all crypto, hash
and bitwise operations which may rely on that wrap around property.
Division by zero is still handled both in signed and unsigned integers.

Note: from now, on security level, the use of unsigned integers for
standard numeric operations should be probably considered suspicious.

## Benchmark

To measure the impact of overflow, I execute the following benchmarks:

First a micro benchmark comparing an addition of 2 ints, with and
without overflow:


```go
//go:noinline
func AddNoOverflow(x, y int) int { return x + y }

func BenchmarkAddNoOverflow(b *testing.B) {
    x, y := 4, 3
    c := 0
    for range b.N {
        c = AddNoOverflow(x, y)
    }
    if c != 7 {
        b.Error("invalid result")
    }
}

func BenchmarkAddOverflow(b *testing.B) {
    x, y := 4, 3
    c := 0
    for range b.N {
        c = overflow.Addp(x, y)
    }
    if c != 7 {
        b.Error("invalid result")
    }
}
```

The implementation of overflow checking is taken from
http://github.com/gnolang/overflow, already used in tm2.

It gives the following results:

```console
$ go test -v- run=^# -benchmem -bench=Overflow
goos: darwin
goarch: arm64
pkg: github.com/gnolang/gno/gnovm/pkg/gnolang
cpu: Apple M1
BenchmarkAddNoOverflow
BenchmarkAddNoOverflow-8    1000000000           0.9392 ns/op          0 B/op          0 allocs/op
BenchmarkAddOverflow
BenchmarkAddOverflow-8      568881582            2.101 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/gnolang/gno/gnovm/pkg/gnolang    2.640s
```

Checking overflow doubles the execution time of an addition from 1 ns/op
to 2 ns/op.

But at 2 ns, the total time is still an order of magnitude lower than
the cost of running the VM.
The impact of overflow check doesn't even appear when benchmarking at VM
level with the following:

```go
func BenchmarkOpAdd(b *testing.B) {
    m := NewMachine("bench", nil)
    x := TypedValue{T: IntType}
    x.SetInt(4)
    y := TypedValue{T: IntType}
    y.SetInt(3)

    b.ResetTimer()

    for range b.N {
        m.PushOp(OpHalt)
        m.PushExpr(&BinaryExpr{})
        m.PushValue(x)
        m.PushValue(y)
        m.PushOp(OpAdd)
        m.Run()
    }
}
```

Which gives something like:

```console
$ go test -v -benchmem -bench=OpAdd -run=^#
goos: darwin
goarch: arm64
pkg: github.com/gnolang/gno/gnovm/pkg/gnolang
cpu: Apple M1
BenchmarkOpAdd
BenchmarkOpAdd-8    16069832            74.41 ns/op      163 B/op          1 allocs/op
PASS
ok      github.com/gnolang/gno/gnovm/pkg/gnolang    1.526
```

Where the execution time varie from 60 ns/op to 100 ns/op for both
versions of addition, with or without overflow.

## Related PRs and issues

- PRs: 
    - #3197 
    - #3192
    - #3117
    - #2983
    - #2905 
    - #2698
- Issues: 
    - #2873
    - #1844
    - #1729


<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] 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
</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📦 🌐 tendermint v2 Issues or PRs tm2 related 🧾 package/realm Tag used for new Realms or Packages.
Projects
Status: Done
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

6 participants