Skip to content

Commit

Permalink
feat(stdlibs/std): make AssertOriginCall always panic with MsgRun (
Browse files Browse the repository at this point in the history
…gnolang#1665)

Fix gnolang#1663

`AssertOriginCall` used to panic on `MsgRun` because it involves more
than 2 frames. But we want to reinforce that property, with an
additional check.

Added an improved version of the unit and txtar tests from gnolang#1048. In
particular, the txtar adds 2 more cases :

19) MsgCall invokes std.AssertOriginCall directly: pass
20) MsgRun invokes std.AssertOriginCall directly: PANIC

Note that 19) involves a change in the AssertOriginCall algorithm,
because in that situation there's only a single frame. Even if there's
no reason to call `std.AssertOriginCall()` directly from the command
line, I think it's logic to make it pass, because that's a origin call.

To run the txtar test:
```
$ go test ./gno.land/cmd/gnoland/ -v -run TestTestdata/assert
```

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

- [x] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com>
Co-authored-by: Morgan Bazalgette <morgan@morganbaz.com>
  • Loading branch information
3 people authored and omarsy committed Jun 3, 2024
1 parent 35c8dff commit d62b24c
Show file tree
Hide file tree
Showing 10 changed files with 614 additions and 41 deletions.
8 changes: 8 additions & 0 deletions examples/gno.land/r/demo/tests/subtests/subtests.gno
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ func GetPrevRealm() std.Realm {
func Exec(fn func()) {
fn()
}

func CallAssertOriginCall() {
std.AssertOriginCall()
}

func CallIsOriginCall() bool {
return std.IsOriginCall()
}
12 changes: 10 additions & 2 deletions examples/gno.land/r/demo/tests/tests.gno
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,22 @@ func InitOrigCaller() std.Address {
return initOrigCaller
}

func AssertOriginCall() {
func CallAssertOriginCall() {
std.AssertOriginCall()
}

func IsOriginCall() bool {
func CallIsOriginCall() bool {
return std.IsOriginCall()
}

func CallSubtestsAssertOriginCall() {
rsubtests.CallAssertOriginCall()
}

func CallSubtestsIsOriginCall() bool {
return rsubtests.CallIsOriginCall()
}

//----------------------------------------
// Test structure to ensure cross-realm modification is prevented.

Expand Down
36 changes: 24 additions & 12 deletions examples/gno.land/r/demo/tests/tests_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,40 @@ import (
)

func TestAssertOriginCall(t *testing.T) {
// No-panic case
AssertOriginCall()
if !IsOriginCall() {
// CallAssertOriginCall(): no panic
CallAssertOriginCall()
if !CallIsOriginCall() {
t.Errorf("expected IsOriginCall=true but got false")
}

// Panic case
// CallAssertOriginCall() from a block: panic
expectedReason := "invalid non-origin call"
func() {
defer func() {
r := recover()
if r == nil || r.(string) != expectedReason {
t.Errorf("expected panic with '%v', got '%v'", expectedReason, r)
}
}()
// if called inside a function literal, this is no longer an origin call
// because there's one additional frame (the function literal block).
if CallIsOriginCall() {
t.Errorf("expected IsOriginCall=false but got true")
}
CallAssertOriginCall()
}()

// CallSubtestsAssertOriginCall(): panic
defer func() {
r := recover()
if r == nil || r.(string) != expectedReason {
t.Errorf("expected panic with '%v', got '%v'", expectedReason, r)
}
}()
func() {
// if called inside a function literal, this is no longer an origin call
// because there's one additional frame (the function literal).
if IsOriginCall() {
t.Errorf("expected IsOriginCall=false but got true")
}
AssertOriginCall()
}()
if CallSubtestsIsOriginCall() {
t.Errorf("expected IsOriginCall=false but got true")
}
CallSubtestsAssertOriginCall()
}

func TestPrevRealm(t *testing.T) {
Expand Down
28 changes: 14 additions & 14 deletions examples/gno.land/r/demo/tests/z0_filetest.gno
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ import (
)

func main() {
println("IsOriginCall:", tests.IsOriginCall())
tests.AssertOriginCall()
println("AssertOriginCall doesn't panic when called directly")
println("tests.CallIsOriginCall:", tests.CallIsOriginCall())
tests.CallAssertOriginCall()
println("tests.CallAssertOriginCall doesn't panic when called directly")

func() {
// if called inside a function literal, this is no longer an origin call
// because there's one additional frame (the function literal).
println("IsOriginCall:", tests.IsOriginCall())
{
// if called inside a block, this is no longer an origin call because
// there's one additional frame (the block).
println("tests.CallIsOriginCall:", tests.CallIsOriginCall())
defer func() {
r := recover()
println("AssertOriginCall panics if when called inside a function literal:", r)
println("tests.AssertOriginCall panics if when called inside a function literal:", r)
}()
tests.AssertOriginCall()
}()
tests.CallAssertOriginCall()
}
}

// Output:
// IsOriginCall: true
// AssertOriginCall doesn't panic when called directly
// IsOriginCall: false
// AssertOriginCall panics if when called inside a function literal: invalid non-origin call
// tests.CallIsOriginCall: true
// tests.CallAssertOriginCall doesn't panic when called directly
// tests.CallIsOriginCall: true
// tests.AssertOriginCall panics if when called inside a function literal: undefined
245 changes: 245 additions & 0 deletions gno.land/cmd/gnoland/testdata/assertorigincall.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# This test ensures the consistency of the std.AssertOriginCall function, in
# the following situations:
#
# | Num | Msg Type | Call from | Entry Point | Result |
# |-----|:--------:|:-------------------:|:----------------------:|:------:|
# | 1 | MsgCall | wallet direct | myrealm.A() | PANIC |
# | 2 | | | myrealm.B() | pass |
# | 3 | | | myrealm.C() | pass |
# | 4 | | through /r/foo | myrealm.A() | PANIC |
# | 5 | | | myrealm.B() | pass |
# | 6 | | | myrealm.C() | PANIC |
# | 7 | | through /p/demo/bar | myrealm.A() | PANIC |
# | 8 | | | myrealm.B() | pass |
# | 9 | | | myrealm.C() | PANIC |
# | 10 | MsgRun | wallet direct | myrealm.A() | PANIC |
# | 11 | | | myrealm.B() | pass |
# | 12 | | | myrealm.C() | PANIC |
# | 13 | | through /r/foo | myrealm.A() | PANIC |
# | 14 | | | myrealm.B() | pass |
# | 15 | | | myrealm.C() | PANIC |
# | 16 | | through /p/demo/bar | myrealm.A() | PANIC |
# | 17 | | | myrealm.B() | pass |
# | 18 | | | myrealm.C() | PANIC |
# | 19 | MsgCall | wallet direct | std.AssertOriginCall() | pass |
# | 20 | MsgRun | wallet direct | std.AssertOriginCall() | PANIC |

# Init
## set up and start a new node
loadpkg gno.land/r/myrlm $WORK/r/myrlm
loadpkg gno.land/r/foo $WORK/r/foo
loadpkg gno.land/p/demo/bar $WORK/p/demo/bar
gnoland start

# Test cases
## 1. MsgCall -> myrlm.A: PANIC
! gnokey maketx call -pkgpath gno.land/r/myrlm -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stderr 'invalid non-origin call'

## 2. MsgCall -> myrlm.B: PASS
gnokey maketx call -pkgpath gno.land/r/myrlm -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stdout 'OK!'

## 3. MsgCall -> myrlm.C: PASS
gnokey maketx call -pkgpath gno.land/r/myrlm -func C -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stdout 'OK!'

## 4. MsgCall -> r/foo.A -> myrlm.A: PANIC
! gnokey maketx call -pkgpath gno.land/r/foo -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stderr 'invalid non-origin call'

## 5. MsgCall -> r/foo.B -> myrlm.B: PASS
gnokey maketx call -pkgpath gno.land/r/foo -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stdout 'OK!'

## 6. MsgCall -> r/foo.C -> myrlm.C: PANIC
! gnokey maketx call -pkgpath gno.land/r/foo -func C -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stderr 'invalid non-origin call'

## 7. MsgCall -> p/demo/bar.A -> myrlm.A: PANIC
! gnokey maketx call -pkgpath gno.land/p/demo/bar -func A -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stderr 'invalid non-origin call'

## 8. MsgCall -> p/demo/bar.B -> myrlm.B: PASS
gnokey maketx call -pkgpath gno.land/p/demo/bar -func B -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stdout 'OK!'

## 9. MsgCall -> p/demo/bar.C -> myrlm.C: PANIC
! gnokey maketx call -pkgpath gno.land/p/demo/bar -func C -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stderr 'invalid non-origin call'

## 10. MsgRun -> run.main -> myrlm.A: PANIC
! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmA.gno
stderr 'invalid non-origin call'

## 11. MsgRun -> run.main -> myrlm.B: PASS
gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmB.gno
stdout 'OK!'

## 12. MsgRun -> run.main -> myrlm.C: PANIC
! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/myrlmC.gno
stderr 'invalid non-origin call'

## 13. MsgRun -> run.main -> foo.A: PANIC
! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooA.gno
stderr 'invalid non-origin call'

## 14. MsgRun -> run.main -> foo.B: PASS
gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooB.gno
stdout 'OK!'

## 15. MsgRun -> run.main -> foo.C: PANIC
! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/fooC.gno
stderr 'invalid non-origin call'

## 16. MsgRun -> run.main -> bar.A: PANIC
! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barA.gno
stderr 'invalid non-origin call'

## 17. MsgRun -> run.main -> bar.B: PASS
gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barB.gno
stdout 'OK!'

## 18. MsgRun -> run.main -> bar.C: PANIC
! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/barC.gno
stderr 'invalid non-origin call'

## 19. MsgCall -> std.AssertOriginCall: pass
gnokey maketx call -pkgpath std -func AssertOriginCall -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1
stdout 'OK!'

## 20. MsgRun -> std.AssertOriginCall: PANIC
! gnokey maketx run -gas-fee 100000ugnot -gas-wanted 2000000 -broadcast -chainid tendermint_test test1 $WORK/run/baz.gno
stderr 'invalid non-origin call'


-- r/myrlm/rlm.gno --
package myrlm

import "std"

func A() {
C()
}

func B() {
if false {
C()
}
}

func C() {
std.AssertOriginCall()
}
-- r/foo/foo.gno --
package foo

import "gno.land/r/myrlm"

func A() {
myrlm.A()
}

func B() {
myrlm.B()
}

func C() {
myrlm.C()
}
-- p/demo/bar/bar.gno --
package bar

import "gno.land/r/myrlm"

func A() {
myrlm.A()
}

func B() {
myrlm.B()
}

func C() {
myrlm.C()
}
-- run/myrlmA.gno --
package main

import myrlm "gno.land/r/myrlm"

func main() {
myrlm.A()
}
-- run/myrlmB.gno --
package main

import "gno.land/r/myrlm"

func main() {
myrlm.B()
}
-- run/myrlmC.gno --
package main

import "gno.land/r/myrlm"

func main() {
myrlm.C()
}
-- run/fooA.gno --
package main

import "gno.land/r/foo"

func main() {
foo.A()
}
-- run/fooB.gno --
package main

import "gno.land/r/foo"

func main() {
foo.B()
}
-- run/fooC.gno --
package main

import "gno.land/r/foo"

func main() {
foo.C()
}
-- run/barA.gno --
package main

import "gno.land/p/demo/bar"

func main() {
bar.A()
}
-- run/barB.gno --
package main

import "gno.land/p/demo/bar"

func main() {
bar.B()
}
-- run/barC.gno --
package main

import "gno.land/p/demo/bar"

func main() {
bar.C()
}
-- run/baz.gno --
package main

import "std"

func main() {
std.AssertOriginCall()
}
10 changes: 9 additions & 1 deletion gnovm/stdlibs/std/native.gno
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package std

func AssertOriginCall() // injected
// AssertOriginCall panics if [IsOriginCall] returns false.
func AssertOriginCall() // injected

// IsOriginCall returns true only if the calling method is invoked via a direct
// MsgCall. It returns false for all other cases, like if the calling method
// is invoked by another method (even from the same realm or package).
// It also returns false every time when the transaction is broadcasted via
// MsgRun.
func IsOriginCall() bool // injected

func GetChainID() string // injected
func GetHeight() int64 // injected

Expand Down
Loading

0 comments on commit d62b24c

Please sign in to comment.