diff --git a/examples/gno.land/p/demo/tests/subtests/subtests.gno b/examples/gno.land/p/demo/tests/subtests/subtests.gno new file mode 100644 index 00000000000..cdff70380ec --- /dev/null +++ b/examples/gno.land/p/demo/tests/subtests/subtests.gno @@ -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() +} diff --git a/examples/gno.land/p/demo/tests/tests.gno b/examples/gno.land/p/demo/tests/tests.gno index 858605b88f5..f0e4173f313 100644 --- a/examples/gno.land/p/demo/tests/tests.gno +++ b/examples/gno.land/p/demo/tests/tests.gno @@ -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 @@ -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() +} diff --git a/examples/gno.land/r/demo/tests/subtests/subtests.gno b/examples/gno.land/r/demo/tests/subtests/subtests.gno new file mode 100644 index 00000000000..cdff70380ec --- /dev/null +++ b/examples/gno.land/r/demo/tests/subtests/subtests.gno @@ -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() +} diff --git a/examples/gno.land/r/demo/tests/tests.gno b/examples/gno.land/r/demo/tests/tests.gno index b5ee9c0a301..dd2518e2e49 100644 --- a/examples/gno.land/r/demo/tests/tests.gno +++ b/examples/gno.land/r/demo/tests/tests.gno @@ -1,6 +1,10 @@ package tests -import "std" +import ( + "std" + + "gno.land/r/demo/tests/subtests" +) var counter int @@ -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() +} diff --git a/gnovm/pkg/gnolang/frame.go b/gnovm/pkg/gnolang/frame.go index 7e3dc92dd4a..7f87fa26097 100644 --- a/gnovm/pkg/gnolang/frame.go +++ b/gnovm/pkg/gnolang/frame.go @@ -1,6 +1,8 @@ package gnolang -import "fmt" +import ( + "fmt" +) //---------------------------------------- // (runtime) Frame diff --git a/gnovm/stdlibs/frame.go b/gnovm/stdlibs/frame.go new file mode 100644 index 00000000000..e428bb1776d --- /dev/null +++ b/gnovm/stdlibs/frame.go @@ -0,0 +1,20 @@ +package stdlibs + +import "github.com/gnolang/gno/tm2/pkg/crypto" + +type Realm struct { + addr crypto.Bech32Address + pkgPath string +} + +func (r Realm) Addr() crypto.Bech32Address { + return r.addr +} + +func (r Realm) PkgPath() string { + return r.pkgPath +} + +func (r Realm) IsUser() bool { + return r.pkgPath == "" +} diff --git a/gnovm/stdlibs/std/frame.gno b/gnovm/stdlibs/std/frame.gno new file mode 100644 index 00000000000..bc3a000f5a0 --- /dev/null +++ b/gnovm/stdlibs/std/frame.gno @@ -0,0 +1,18 @@ +package std + +type Realm struct { + addr Address + pkgPath string +} + +func (r Realm) Addr() Address { + return r.addr +} + +func (r Realm) PkgPath() string { + return r.pkgPath +} + +func (r Realm) IsUser() bool { + return r.pkgPath == "" +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index a9bda4bd4e7..9ac790c2b4e 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -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) { @@ -262,6 +263,61 @@ 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 + } else if lastPkgPath == pkgPath { + continue + } else { + lastCaller = fr.LastPackage.GetPkgAddr().Bech32() + lastPkgPath = pkgPath + break + } + } + + // Empty the pkgPath if we return a user + if ctx.OrigCaller == lastCaller { + lastPkgPath = "" + } + + // Return the result + res0 := gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(Realm{ + addr: lastCaller, + pkgPath: lastPkgPath, + }), + ) + + realmT := store.GetType(gno.DeclaredTypeID("std", "Realm")) + res0.T = realmT + m.PushValue(res0) + }, + ) pn.DefineNative("GetOrigPkgAddr", gno.Flds( // params ), diff --git a/gnovm/stdlibs/stdshim/frame.gno b/gnovm/stdlibs/stdshim/frame.gno new file mode 100644 index 00000000000..bc3a000f5a0 --- /dev/null +++ b/gnovm/stdlibs/stdshim/frame.gno @@ -0,0 +1,18 @@ +package std + +type Realm struct { + addr Address + pkgPath string +} + +func (r Realm) Addr() Address { + return r.addr +} + +func (r Realm) PkgPath() string { + return r.pkgPath +} + +func (r Realm) IsUser() bool { + return r.pkgPath == "" +} diff --git a/gnovm/stdlibs/stdshim/stdshim.gno b/gnovm/stdlibs/stdshim/stdshim.gno index 5156793bbb3..8e1fdc0da75 100644 --- a/gnovm/stdlibs/stdshim/stdshim.gno +++ b/gnovm/stdlibs/stdshim/stdshim.gno @@ -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("") diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 51917633158..451bf0677dc 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -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{}) diff --git a/gnovm/tests/files/zrealm_crossrealm11.gno b/gnovm/tests/files/zrealm_crossrealm11.gno new file mode 100644 index 00000000000..ecb3555c29c --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm11.gno @@ -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[r.Addr()]) + }) + ptests.Exec(func() { + r := tt.callerFn() + assertRealm(r) + printColumns(baseCallStack+" -> p/demo/tests.Exec"+tt.callStackAdd, callersByAddr[r.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: +// --- +// 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 diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index 9eaf7fb9a96..bc07acea1b4 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -245,7 +245,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "38", +// "Line": "42", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -301,7 +301,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "38", +// "Line": "42", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -523,7 +523,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "7", +// "Line": "11", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -567,7 +567,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "11", +// "Line": "15", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -621,7 +621,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "15", +// "Line": "19", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -665,7 +665,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "19", +// "Line": "23", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -709,7 +709,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "23", +// "Line": "27", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -766,7 +766,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "34", +// "Line": "38", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -813,7 +813,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "56", +// "Line": "60", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -847,7 +847,7 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "61", +// "Line": "65", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -881,7 +881,105 @@ func main() { // "BlockNode": null, // "Location": { // "File": "tests.gno", -// "Line": "69", +// "Line": "73", +// "Nonce": "0", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Realm" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "GetPrevRealm", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "tests.gno", +// "Line": "77", +// "Nonce": "0", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Realm" +// } +// } +// ] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Realm" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "GetPSubtestsPrevRealm", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "tests.gno", +// "Line": "81", // "Nonce": "0", // "PkgPath": "gno.land/r/demo/tests" // } @@ -889,6 +987,72 @@ func main() { // "Type": { // "@type": "/gno.FuncType", // "Params": [], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Realm" +// } +// } +// ] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "fn", +// "Tag": "", +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// ], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "0ffe7732b4d549b4cf9ec18bd68641cd2c75ad0a:4" +// }, +// "FileName": "tests.gno", +// "IsMethod": false, +// "Name": "Exec", +// "PkgPath": "gno.land/r/demo/tests", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "tests.gno", +// "Line": "85", +// "Nonce": "0", +// "PkgPath": "gno.land/r/demo/tests" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "fn", +// "Tag": "", +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// ], // "Results": [] // } // } diff --git a/misc/docker-integration/integration_test.go b/misc/docker-integration/integration_test.go index ac9d5443c3e..f4065f8cf0e 100644 --- a/misc/docker-integration/integration_test.go +++ b/misc/docker-integration/integration_test.go @@ -57,6 +57,17 @@ func runSuite(t *testing.T, tempdir string) { require.True(t, acc.Coins.IsAllGTE(minCoins), "test1 account coins expected at least %s, got %s", minCoins, acc.Coins) + // add gno.land/r/demo/tests/subtests package + dockerExec(t, + `echo 'pass' | gnokey maketx addpkg -insecure-password-stdin \ + -gas-fee 1000000ugnot -gas-wanted 2000000 \ + -broadcast -chainid dev \ + -pkgdir /opt/gno/src/examples/gno.land/r/demo/tests/subtests \ + -pkgpath gno.land/r/demo/tests/subtests \ + -deposit 100000000ugnot \ + test1`, + ) + // add gno.land/r/demo/tests package dockerExec(t, `echo 'pass' | gnokey maketx addpkg -insecure-password-stdin \