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: add std.PrevRealm #667

Merged
merged 26 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions examples/gno.land/p/demo/tests/subtests/subtests.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package subtests

import (
"std"
)

func CurrentRealmPath() string {
return std.CurrentRealmPath()
moul marked this conversation as resolved.
Show resolved Hide resolved
}

func GetPrevRealm() std.Realm {
return std.PrevRealm()
}

func Exec(fn func()) {
fn()
}
19 changes: 19 additions & 0 deletions examples/gno.land/p/demo/tests/tests.gno
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package tests
import (
"std"

psubtests "gno.land/p/demo/tests/subtests"
"gno.land/r/demo/tests"
rtests "gno.land/r/demo/tests"
)

// IncCounter demonstrates that it's possible to call a realm function from
Expand Down Expand Up @@ -51,3 +53,20 @@ func ModifyTestRealmObject2b() {
func ModifyTestRealmObject2c() {
SomeValue3.Field = "modified"
}

func GetPrevRealm() std.Realm {
return std.PrevRealm()
}

func GetPSubtestsPrevRealm() std.Realm {
return psubtests.GetPrevRealm()
}

func GetRTestsGetPrevRealm() std.Realm {
return rtests.GetPrevRealm()
}

// Warning: unsafe pattern.
func Exec(fn func()) {
fn()
}
17 changes: 17 additions & 0 deletions examples/gno.land/r/demo/tests/subtests/subtests.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package subtests

import (
"std"
)

func CurrentRealmPath() string {
return std.CurrentRealmPath()
}

func GetPrevRealm() std.Realm {
return std.PrevRealm()
}

func Exec(fn func()) {
fn()
}
18 changes: 17 additions & 1 deletion examples/gno.land/r/demo/tests/tests.gno
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package tests

import "std"
import (
"std"

"gno.land/r/demo/tests/subtests"
)

var counter int

Expand Down Expand Up @@ -69,3 +73,15 @@ func ModTestNodes() {
func PrintTestNodes() {
println(gTestNode2.Child.Name)
}

func GetPrevRealm() std.Realm {
return std.PrevRealm()
}

func GetPSubtestsPrevRealm() std.Realm {
return subtests.GetPrevRealm()
}

func Exec(fn func()) {
fn()
}
4 changes: 3 additions & 1 deletion gnovm/pkg/gnolang/frame.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package gnolang

import "fmt"
import (
"fmt"
)

//----------------------------------------
// (runtime) Frame
Expand Down
8 changes: 8 additions & 0 deletions gnovm/stdlibs/frame.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package stdlibs

import "github.com/gnolang/gno/tm2/pkg/crypto"

type Realm struct {
Addr crypto.Bech32Address
PkgPath string
}
10 changes: 10 additions & 0 deletions gnovm/stdlibs/std/frame.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package std

type Realm struct {
Addr Address
moul marked this conversation as resolved.
Show resolved Hide resolved
PkgPath string
}

func (r Realm) IsUser() bool {
return r.PkgPath == ""
}
54 changes: 54 additions & 0 deletions gnovm/stdlibs/stdlibs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func InjectNativeMappings(store gno.Store) {
store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address")
store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins")
store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin")
store.AddGo2GnoMapping(reflect.TypeOf(Realm{}), "std", "Realm")
}

func InjectPackage(store gno.Store, pn *gno.PackageNode) {
Expand Down Expand Up @@ -286,6 +287,59 @@ func InjectPackage(store gno.Store, pn *gno.PackageNode) {
m.PushValue(res0)
},
)
pn.DefineNative("PrevRealm",
gno.Flds( // params
),
gno.Flds( // results
"", "Realm",
),
func(m *gno.Machine) {
var (
ctx = m.Context.(ExecContext)
// Default lastCaller is OrigCaller, the signer of the tx
lastCaller = ctx.OrigCaller
lastPkgPath = ""
)

for i := m.NumFrames() - 1; i > 0; i-- {
fr := m.Frames[i]
if fr.LastPackage == nil || !fr.LastPackage.IsRealm() {
// Ignore non-realm frame
continue
}
pkgPath := fr.LastPackage.PkgPath
// The first realm we encounter will be the one calling
// this function; to get the calling realm determine the first frame
// where fr.LastPackage changes.
if lastPkgPath != "" && lastPkgPath != pkgPath {
lastCaller = fr.LastPackage.GetPkgAddr().Bech32()
lastPkgPath = pkgPath
break
}

lastPkgPath = pkgPath
albttx marked this conversation as resolved.
Show resolved Hide resolved
}

// Empty the pkgPath if we return a user
if ctx.OrigCaller == lastCaller {
lastPkgPath = ""
Copy link
Contributor

@jaekwon jaekwon Jun 2, 2023

Choose a reason for hiding this comment

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

i don't understand why it needs to be empty. what happens without this line?
Maybe it's solved by the if-else chain i suggested above. it seemed like it was unnecessarily setting lastPkgPath before.

Copy link
Member Author

Choose a reason for hiding this comment

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

std.Realm return a Realm{}

type Realm struct {
	Addr    crypto.Bech32Address
	PkgPath string
}

in the case we return a user, we want PkgPath to be empty.

Without this line, we could return a Realm containing a user address and gno.land/r/demo/xxx which is the "Current realm".

This condition won't be present in the next function std.Realm

Copy link
Member

Choose a reason for hiding this comment

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

For accounts, it's a good idea to have an empty variable. However, if default realm implementation is desired later, it could change to gno.land/r/moul. What is the current value before erasing it?

I like using if PkgPath != "" for distinction, similar to if err != nil. We can update the method or choose a new variable when accounts have a path.

Waiting for your response, but currently inclined to maintain the current behavior.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hi @moul I'm not sure i understand your question, but here is a better explanation

user1.gno -> gno.land/r/moul/sayhi

The Frames will be

  • gno.land/r/moul/sayhi
  • user1.gno

so when we will finish the iteration loop, lastCaller will be user1.gno, and pkgPath will be gno.land/r/moul/sayhi, ( frame[i].LastPackage.PkgPath)

So we use if ctx.OrigCaller == lastCaller to empty it

Is it more clear ?

Copy link
Member

Choose a reason for hiding this comment

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

I was asking what was the raw value before erasing it. Checking if it makes sense to be kept somehow.

}

// Return the result
res0 := gno.Go2GnoValue(
m.Alloc,
m.Store,
reflect.ValueOf(Realm{
lastCaller,
lastPkgPath,
}),
)

realmT := store.GetType(gno.DeclaredTypeID("std", "Realm"))
res0.T = realmT
m.PushValue(res0)
},
)
pn.DefineNative("GetOrigPkgAddr",
gno.Flds( // params
),
Expand Down
6 changes: 6 additions & 0 deletions gnovm/stdlibs/stdshim/frame.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package std

type Realm struct {
Addr Address
PkgPath string
}
8 changes: 8 additions & 0 deletions gnovm/stdlibs/stdshim/stdshim.gno
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ func GetOrigSend() Coins {
return Coins{}
}

func PrevRealm() Realm {
panic(shimWarn)
return Realm{
Addr: Address(""),
PkgPath: "",
}
}

func GetOrigCaller() Address {
panic(shimWarn)
return Address("")
Expand Down
3 changes: 1 addition & 2 deletions gnovm/tests/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ import (
"strconv"
"strings"

"github.com/pmezard/go-difflib/difflib"

gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/gnovm/stdlibs"
"github.com/gnolang/gno/tm2/pkg/crypto"
osm "github.com/gnolang/gno/tm2/pkg/os"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/pmezard/go-difflib/difflib"
)

type loggerFunc func(args ...interface{})
Expand Down
149 changes: 149 additions & 0 deletions gnovm/tests/files/zrealm_crossrealm11.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// PKGPATH: gno.land/r/crossrealm_test
package crossrealm_test

import (
"std"
"strings"

ptests "gno.land/p/demo/tests"
"gno.land/p/demo/ufmt"
rtests "gno.land/r/demo/tests"
testfoo "gno.land/r/demo/tests_foo"
)

func getPrevRealm() std.Realm {
return std.PrevRealm()
}

func Exec(fn func()) {
fn()
}

func main() {
// Create a map of the potential callers, this will give more understandable
// output than the bech32 addresses.
callersByAddr := make(map[std.Address]string)
for _, caller := range []string{
"user1.gno", "gno.land/r/crossrealm_test", "gno.land/r/demo/tests",
} {
addr := std.DerivePkgAddr(caller)
callersByAddr[addr] = caller
}

assertRealm := func(r std.Realm) {
pkgPath := callersByAddr[r.Addr]
if r.IsUser() && pkgPath != "user1.gno" {
panic(ufmt.Sprintf("ERROR: expected: 'user1.gno', got:'%s'", pkgPath))
} else if !r.IsUser() && pkgPath != r.PkgPath {
panic(ufmt.Sprintf("ERROR: expected: '%s', got: '%s'", pkgPath, r.PkgPath))
}
}

tests := []struct {
callStackAdd string
callerFn func() std.Realm
}{
{
callStackAdd: "",
callerFn: std.PrevRealm,
},
{
callStackAdd: " -> r/crossrealm_test.getPrevRealm",
callerFn: getPrevRealm,
},
{
callStackAdd: " -> p/demo/tests",
callerFn: ptests.GetPrevRealm,
},
{
callStackAdd: " -> p/demo/tests -> p/demo/tests/subtests",
callerFn: ptests.GetPSubtestsPrevRealm,
},
{
callStackAdd: " -> r/demo/tests",
callerFn: rtests.GetPrevRealm,
},
{
callStackAdd: " -> r/demo/tests -> r/demo/tests/subtests",
callerFn: rtests.GetPSubtestsPrevRealm,
},
{
callStackAdd: " -> p/demo/tests -> r/demo/tests",
callerFn: ptests.GetRTestsGetPrevRealm,
},
}

println("---") // needed to have space prefixes
printColumns("STACK", "std.PrevRealm")
printColumns("-----", "------------------")

baseCallStack := "user1.gno -> r/crossrealm_test.main"
for i, tt := range tests {
printColumns(baseCallStack+tt.callStackAdd, callersByAddr[tt.callerFn().Addr])
Exec(func() {
r := tt.callerFn()
assertRealm(r)
printColumns(baseCallStack+" -> r/crossrealm_test.Exec"+tt.callStackAdd, callersByAddr[r.Addr])
})
rtests.Exec(func() {
r := tt.callerFn()
assertRealm(r)
printColumns(baseCallStack+" -> r/demo/tests.Exec"+tt.callStackAdd, callersByAddr[tt.callerFn().Addr])
})
ptests.Exec(func() {
r := tt.callerFn()
assertRealm(r)
printColumns(baseCallStack+" -> p/demo/tests.Exec"+tt.callStackAdd, callersByAddr[tt.callerFn().Addr])
})
}
}

func printColumns(left, right string) {
const w = 105

output := ""
padding := w - len(left)

// strings.Repeat is not always available when using various imports modes.
for i := 0; i < padding; i++ {
output += " "
}

output += left
output += " = "
output += right
println(output)
}

// Output:
moul marked this conversation as resolved.
Show resolved Hide resolved
// ---
// STACK = std.PrevRealm
// ----- = ------------------
// user1.gno -> r/crossrealm_test.main = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec = gno.land/r/demo/tests
// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.getPrevRealm = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/crossrealm_test.getPrevRealm = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/crossrealm_test.getPrevRealm = gno.land/r/demo/tests
// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/crossrealm_test.getPrevRealm = user1.gno
// user1.gno -> r/crossrealm_test.main -> p/demo/tests = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> p/demo/tests = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> p/demo/tests = gno.land/r/demo/tests
// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> p/demo/tests = user1.gno
// user1.gno -> r/crossrealm_test.main -> p/demo/tests -> p/demo/tests/subtests = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> p/demo/tests -> p/demo/tests/subtests = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> p/demo/tests -> p/demo/tests/subtests = gno.land/r/demo/tests
// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> p/demo/tests -> p/demo/tests/subtests = user1.gno
// user1.gno -> r/crossrealm_test.main -> r/demo/tests = gno.land/r/crossrealm_test
// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/demo/tests = gno.land/r/crossrealm_test
// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/demo/tests = gno.land/r/crossrealm_test
// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/demo/tests = gno.land/r/crossrealm_test
// user1.gno -> r/crossrealm_test.main -> r/demo/tests -> r/demo/tests/subtests = gno.land/r/demo/tests
// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> r/demo/tests -> r/demo/tests/subtests = gno.land/r/demo/tests
// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> r/demo/tests -> r/demo/tests/subtests = gno.land/r/demo/tests
// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> r/demo/tests -> r/demo/tests/subtests = gno.land/r/demo/tests
// user1.gno -> r/crossrealm_test.main -> p/demo/tests -> r/demo/tests = gno.land/r/crossrealm_test
// user1.gno -> r/crossrealm_test.main -> r/crossrealm_test.Exec -> p/demo/tests -> r/demo/tests = gno.land/r/crossrealm_test
// user1.gno -> r/crossrealm_test.main -> r/demo/tests.Exec -> p/demo/tests -> r/demo/tests = gno.land/r/crossrealm_test
// user1.gno -> r/crossrealm_test.main -> p/demo/tests.Exec -> p/demo/tests -> r/demo/tests = gno.land/r/crossrealm_test
Loading