diff --git a/examples/gno.land/r/x/test_prev/gno.mod b/examples/gno.land/r/x/test_prev/gno.mod deleted file mode 100644 index a1c1eb4f4d5..00000000000 --- a/examples/gno.land/r/x/test_prev/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/x/test_prev - -require ( - gno.land/p/demo/users v0.0.0-latest - gno.land/r/demo/foo20 v0.0.0-latest -) diff --git a/examples/gno.land/r/x/test_prev/test_prev.gno b/examples/gno.land/r/x/test_prev/test_prev.gno deleted file mode 100644 index 9466e5f96b9..00000000000 --- a/examples/gno.land/r/x/test_prev/test_prev.gno +++ /dev/null @@ -1,18 +0,0 @@ -package test_prev - -import ( - "std" - - pusers "gno.land/p/demo/users" - "gno.land/r/demo/foo20" -) - -func DoSomeWithUserBalance() string { - caller := std.GetOrigCaller() - balance := foo20.BalanceOf(pusers.AddressOrName(caller)) - - if balance > 100 { - return "rich user" - } - return "poor user" -} diff --git a/examples/gno.land/r/x/test_prev/test_prev_test.gno b/examples/gno.land/r/x/test_prev/test_prev_test.gno deleted file mode 100644 index a6b86eafcb6..00000000000 --- a/examples/gno.land/r/x/test_prev/test_prev_test.gno +++ /dev/null @@ -1,32 +0,0 @@ -package test_prev - -import ( - "std" - "testing" - - "gno.land/r/demo/foo20" -) - -func TestDoSomeWithUserBalancePoor(t *testing.T) { - std.TestSetOrigCaller("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - if DoSomeWithUserBalance() != "poor user" { - t.Error("expected poor user") - } -} - -func TestDoSomeWithUserBalanceRichButPoor(t *testing.T) { - std.TestSetOrigCaller("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - foo20.Faucet() // foo20 will mint tokens to this realm(std.PrevRealm) not user - if DoSomeWithUserBalance() != "poor user" { - t.Error("expected poor user") - } -} - -func TestDoSomeWithUserBalanceRich(t *testing.T) { - std.TestSetPrevAddr("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") - - foo20.Faucet() // foo20 will mint tokens to this realm not user - if DoSomeWithUserBalance() != "rich user" { - t.Error("expected rich user") - } -} diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 68eb44290e2..a644b967d54 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -2058,19 +2058,19 @@ func (m *Machine) String() string { builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Values[i])) } - builder.WriteString(`Exprs: `) + builder.WriteString(" Exprs:\n") for i := len(m.Exprs) - 1; i >= 0; i-- { builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Exprs[i])) } - builder.WriteString(`Stmts: `) + builder.WriteString(" Stmts:\n") for i := len(m.Stmts) - 1; i >= 0; i-- { builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Stmts[i])) } - builder.WriteString(`Blocks: `) + builder.WriteString(" Blocks:\n") for b := m.LastBlock(); b != nil; { gen := builder.Len()/3 + 1 @@ -2107,7 +2107,7 @@ func (m *Machine) String() string { } } - builder.WriteString(`Blocks (other): `) + builder.WriteString(" Blocks (other):\n") for i := len(m.Blocks) - 2; i >= 0; i-- { b := m.Blocks[i] @@ -2119,17 +2119,17 @@ func (m *Machine) String() string { if _, ok := b.Source.(*PackageNode); ok { break // done, skip *PackageNode. } else { - builder.WriteString(fmt.Sprintf(" #%d %s", i, + builder.WriteString(fmt.Sprintf(" #%d %s\n", i, b.StringIndented(" "))) if b.Source != nil { sb := b.GetSource(m.Store).GetStaticBlock().GetBlock() - builder.WriteString(fmt.Sprintf(" (static) #%d %s", i, + builder.WriteString(fmt.Sprintf(" (static) #%d %s\n", i, sb.StringIndented(" "))) } } } - builder.WriteString(`Frames: `) + builder.WriteString(" Frames:\n") for i := len(m.Frames) - 1; i >= 0; i-- { builder.WriteString(fmt.Sprintf(" #%d %s\n", i, m.Frames[i])) @@ -2139,7 +2139,7 @@ func (m *Machine) String() string { builder.WriteString(fmt.Sprintf(" Realm:\n %s\n", m.Realm.Path)) } - builder.WriteString(`Exceptions: `) + builder.WriteString(" Exceptions:\n") for _, ex := range m.Exceptions { builder.WriteString(fmt.Sprintf(" %s\n", ex.Sprint(m))) diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 6eb7e720b87..c7747d2a5a6 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -37,14 +37,14 @@ const ( var reDenom = regexp.MustCompile("[a-z][a-z0-9]{2,15}") func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { - coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address(addr)) + coins := GetContext(m).Banker.GetCoins(crypto.Bech32Address(addr)) return ExpandCoins(coins) } func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []string, amounts []int64) { // bt != BankerTypeReadonly (checked in gno) - ctx := m.Context.(ExecContext) + ctx := GetContext(m) amt := CompactCoins(denoms, amounts) from, to := crypto.Bech32Address(fromS), crypto.Bech32Address(toS) @@ -87,7 +87,7 @@ func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []str } func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { - return m.Context.(ExecContext).Banker.TotalCoin(denom) + return GetContext(m).Banker.TotalCoin(denom) } func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { @@ -104,7 +104,7 @@ func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amou // ibc_denom := 'ibc/' + hash('path' + 'base_denom') // gno_realm_denom := '/' + 'pkg_path' + ':' + 'base_denom' newDenom := "/" + m.Realm.Path + ":" + denom - m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), newDenom, amount) + GetContext(m).Banker.IssueCoin(crypto.Bech32Address(addr), newDenom, amount) } func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { @@ -117,5 +117,5 @@ func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amo } newDenom := "/" + m.Realm.Path + ":" + denom - m.Context.(ExecContext).Banker.RemoveCoin(crypto.Bech32Address(addr), newDenom, amount) + GetContext(m).Banker.RemoveCoin(crypto.Bech32Address(addr), newDenom, amount) } diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index 35aef7fbd62..ff5c91a14eb 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -1,6 +1,7 @@ package std import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/sdk" "github.com/gnolang/gno/tm2/pkg/std" @@ -19,3 +20,28 @@ type ExecContext struct { Banker BankerInterface EventLogger *sdk.EventLogger } + +// GetContext returns the execution context. +// This is used to allow extending the exec context using interfaces, +// for instance when testing. +func (e ExecContext) GetExecContext() ExecContext { + return e +} + +var _ ExecContexter = ExecContext{} + +// ExecContexter is a type capable of returning the parent [ExecContext]. When +// using these standard libraries, m.Context should always implement this +// interface. This can be obtained by embedding [ExecContext]. +type ExecContexter interface { + GetExecContext() ExecContext +} + +// NOTE: In order to make this work by simply embedding ExecContext in another +// context (like TestExecContext), the method needs to be something other than +// the field name. + +// GetContext returns the context from the Gno machine. +func GetContext(m *gno.Machine) ExecContext { + return m.Context.(ExecContexter).GetExecContext() +} diff --git a/gnovm/stdlibs/std/emit_event.go b/gnovm/stdlibs/std/emit_event.go index 8e61f67d58a..99433c1b054 100644 --- a/gnovm/stdlibs/std/emit_event.go +++ b/gnovm/stdlibs/std/emit_event.go @@ -26,7 +26,7 @@ func X_emit(m *gno.Machine, typ string, attrs []string) { Func: fnIdent, Attributes: eventAttrs, } - ctx := m.Context.(ExecContext) + ctx := GetContext(m) ctx.EventLogger.EmitEvent(evt) } diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 7148a4ba4f4..8eff5ebb397 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -18,11 +18,11 @@ func IsOriginCall(m *gno.Machine) bool { } func GetChainID(m *gno.Machine) string { - return m.Context.(ExecContext).ChainID + return GetContext(m).ChainID } func GetHeight(m *gno.Machine) int64 { - return m.Context.(ExecContext).Height + return GetContext(m).Height } // getPrevFunctionNameFromTarget returns the last called function name (identifier) from the call stack. @@ -57,16 +57,16 @@ func findPrevFuncName(m *gno.Machine, targetIndex int) string { } func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { - os := m.Context.(ExecContext).OrigSend + os := GetContext(m).OrigSend return ExpandCoins(os) } func X_origCaller(m *gno.Machine) string { - return string(m.Context.(ExecContext).OrigCaller) + return string(GetContext(m).OrigCaller) } func X_origPkgAddr(m *gno.Machine) string { - return string(m.Context.(ExecContext).OrigPkgAddr) + return string(GetContext(m).OrigPkgAddr) } func X_callerAt(m *gno.Machine, n int) string { @@ -85,22 +85,24 @@ func X_callerAt(m *gno.Machine, n int) string { } if n == m.NumFrames() { // This makes it consistent with GetOrigCaller. - ctx := m.Context.(ExecContext) + ctx := GetContext(m) return string(ctx.OrigCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } func X_getRealm(m *gno.Machine, height int) (address, pkgPath string) { + // NOTE: keep in sync with test/stdlibs/std.getRealm + var ( - ctx = m.Context.(ExecContext) + ctx = GetContext(m) currentCaller crypto.Bech32Address // Keeps track of the number of times currentCaller // has changed. changes int ) - for i := m.NumFrames() - 1; i > 0; i-- { + for i := m.NumFrames() - 1; i >= 0; i-- { fr := m.Frames[i] if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { continue diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 7939c0396d1..48e69f78253 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -9,6 +9,10 @@ import ( type ExecContext = libsstd.ExecContext +func GetContext(m *gno.Machine) ExecContext { + return libsstd.GetContext(m) +} + func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { for _, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { diff --git a/gnovm/stdlibs/time/time.go b/gnovm/stdlibs/time/time.go index 8c1c768715e..9ecd8a3b302 100644 --- a/gnovm/stdlibs/time/time.go +++ b/gnovm/stdlibs/time/time.go @@ -12,6 +12,6 @@ func X_now(m *gno.Machine) (sec int64, nsec int32, mono int64) { return 0, 0, 0 } - ctx := m.Context.(std.ExecContext) + ctx := std.GetContext(m) return ctx.Timestamp, int32(ctx.TimestampNano), ctx.Timestamp*int64(time.Second) + ctx.TimestampNano } diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 328d9e09299..9e458556860 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -15,6 +15,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" + teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/sdk" @@ -46,7 +47,7 @@ func testMachineCustom(store gno.Store, pkgPath string, stdout io.Writer, maxAll return m } -func testContext(pkgPath string, send std.Coins) stdlibs.ExecContext { +func testContext(pkgPath string, send std.Coins) *teststd.TestExecContext { // FIXME: create a better package to manage this, with custom constructors pkgAddr := gno.DerivePkgAddr(pkgPath) // the addr of the pkgPath called. caller := gno.DerivePkgAddr("user1.gno") @@ -65,7 +66,10 @@ func testContext(pkgPath string, send std.Coins) stdlibs.ExecContext { Banker: banker, EventLogger: sdk.NewEventLogger(), } - return ctx + return &teststd.TestExecContext{ + ExecContext: ctx, + RealmFrames: make(map[*gno.Frame]teststd.RealmOverride), + } } type runFileTestOptions struct { diff --git a/gnovm/tests/files/zrealm_crossrealm13.gno b/gnovm/tests/files/zrealm_crossrealm13.gno new file mode 100644 index 00000000000..4daeb6de366 --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm13.gno @@ -0,0 +1,48 @@ +package main + +import ( + "std" +) + +func main() { + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) + std.TestSetRealm(std.NewUserRealm("g1user")) + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) + std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) +} + +func pad(s string) string { + for len(s) < 26 { + s += " " + } + return s +} + +func PrintRealm() { + println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) + println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) +} + +// Because this is the context of a package, using PrintRealm() +// should not change the output of the main function. + +// Output: +// PrintRealm: CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// CurrentRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) +// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) +// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/files/zrealm_crossrealm13a.gno b/gnovm/tests/files/zrealm_crossrealm13a.gno new file mode 100644 index 00000000000..2fc37804fce --- /dev/null +++ b/gnovm/tests/files/zrealm_crossrealm13a.gno @@ -0,0 +1,46 @@ +// PKGPATH: gno.land/r/demo/groups +package groups + +import ( + "std" +) + +func main() { + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) + std.TestSetRealm(std.NewUserRealm("g1user")) + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) + std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/users")) + PrintRealm() + println(pad("CurrentRealm:"), std.CurrentRealm()) + println(pad("PrevRealm:"), std.PrevRealm()) +} + +func pad(s string) string { + for len(s) < 26 { + s += " " + } + return s +} + +func PrintRealm() { + println(pad("PrintRealm: CurrentRealm:"), std.CurrentRealm()) + println(pad("PrintRealm: PrevRealm:"), std.PrevRealm()) +} + +// Output: +// PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) +// PrintRealm: PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) +// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) +// PrintRealm: PrevRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// CurrentRealm: (struct{("g1user" std.Address),("" string)} std.Realm) +// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) +// PrintRealm: CurrentRealm: (struct{("g1r0mlnkc05z0fv49km99z60qnp95tengyqfdr02" std.Address),("gno.land/r/demo/groups" string)} std.Realm) +// PrintRealm: PrevRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) +// CurrentRealm: (struct{("g17m4ga9t9dxn8uf06p3cahdavzfexe33ecg8v2s" std.Address),("gno.land/r/demo/users" string)} std.Realm) +// PrevRealm: (struct{("g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm" std.Address),("" string)} std.Realm) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index d5e63532875..f2349580e63 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -38,8 +38,8 @@ import ( "unicode/utf8" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/stdlibs" teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" + teststd "github.com/gnolang/gno/gnovm/tests/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/db/memdb" osm "github.com/gnolang/gno/tm2/pkg/os" "github.com/gnolang/gno/tm2/pkg/std" @@ -186,7 +186,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri d := arg0.GetInt64() sec := d / int64(time.Second) nano := d % int64(time.Second) - ctx := m.Context.(stdlibs.ExecContext) + ctx := m.Context.(*teststd.TestExecContext) ctx.Timestamp += sec ctx.TimestampNano += nano if ctx.TimestampNano >= int64(time.Second) { diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 6d0e3caa1f1..0f33548054b 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -155,30 +155,10 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "testSetPrevRealm", - []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("string")}, - }, - []gno.FieldTypeExpr{}, - func(m *gno.Machine) { - b := m.LastBlock() - var ( - p0 string - rp0 = reflect.ValueOf(&p0).Elem() - ) - - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - - testlibs_std.X_testSetPrevRealm( - m, - p0) - }, - }, - { - "std", - "testSetPrevAddr", + "testSetRealm", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { @@ -186,13 +166,16 @@ var nativeFuncs = [...]nativeFunc{ var ( p0 string rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - testlibs_std.X_testSetPrevAddr( + testlibs_std.X_testSetRealm( m, - p0) + p0, p1) }, }, { @@ -257,6 +240,41 @@ var nativeFuncs = [...]nativeFunc{ p0, p1, p2) }, }, + { + "std", + "getRealm", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0, r1 := testlibs_std.X_getRealm( + m, + p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, { "testing", "unixNano", diff --git a/gnovm/tests/stdlibs/std/frame_testing.gno b/gnovm/tests/stdlibs/std/frame_testing.gno new file mode 100644 index 00000000000..171756c54f5 --- /dev/null +++ b/gnovm/tests/stdlibs/std/frame_testing.gno @@ -0,0 +1,12 @@ +package std + +// NewUserRealm creates a new "user" realm with the given address. +func NewUserRealm(user Address) Realm { + return Realm{addr: user} +} + +// NewCodeRealm creates a new realm for a published code realm with the given +// pkgPath. +func NewCodeRealm(pkgPath string) Realm { + return Realm{pkgPath: pkgPath, addr: DerivePkgAddr(pkgPath)} +} diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index 91c47fed2fd..e96be939eb5 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -7,8 +7,14 @@ func ClearStoreCache() // injected func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } -func TestSetPrevRealm(pkgPath string) { testSetPrevRealm(pkgPath) } -func TestSetPrevAddr(addr Address) { testSetPrevAddr(string(addr)) } + +// TestSetRealm sets the realm for the current frame. +// After calling TestSetRealm, calling CurrentRealm() will yield the value of +// rlm, while if a realm function is called, using PrevRealm() will yield rlm. +func TestSetRealm(rlm Realm) { + testSetRealm(string(rlm.addr), rlm.pkgPath) +} + func TestSetOrigSend(sent, spent Coins) { sentDenom, sentAmt := sent.expandNative() spentDenom, spentAmt := spent.expandNative() @@ -26,9 +32,9 @@ func callerAt(n int) string // native bindings func testSetOrigCaller(s string) func testSetOrigPkgAddr(s string) -func testSetPrevRealm(s string) -func testSetPrevAddr(s string) +func testSetRealm(addr, pkgPath string) func testSetOrigSend( sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64) func testIssueCoins(addr string, denom []string, amt []int64) +func getRealm(height int) (address string, pkgPath string) diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index f5c30a08868..ee17ccb2c15 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -5,13 +5,26 @@ import ( "strings" "testing" - "github.com/gnolang/gno/gnovm/stdlibs" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" ) +// TestExecContext is the testing extension of the exec context. +type TestExecContext struct { + std.ExecContext + + // These are used to set up the result of CurrentRealm() and PrevRealm(). + RealmFrames map[*gno.Frame]RealmOverride +} + +var _ std.ExecContexter = &TestExecContext{} + +type RealmOverride struct { + Addr crypto.Bech32Address + PkgPath string +} + func AssertOriginCall(m *gno.Machine) { if !IsOriginCall(m) { m.Panic(typedString("invalid non-origin call")) @@ -42,7 +55,7 @@ func IsOriginCall(m *gno.Machine) bool { } func TestSkipHeights(m *gno.Machine, count int64) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) ctx.Height += count m.Context = ctx } @@ -80,44 +93,90 @@ func X_callerAt(m *gno.Machine, n int) string { } if n == m.NumFrames()-1 { // This makes it consistent with GetOrigCaller and TestSetOrigCaller. - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) return string(ctx.OrigCaller) } return string(m.MustLastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } func X_testSetOrigCaller(m *gno.Machine, addr string) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) ctx.OrigCaller = crypto.Bech32Address(addr) m.Context = ctx } func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) ctx.OrigPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } -func X_testSetPrevRealm(m *gno.Machine, pkgPath string) { - m.Frames[m.NumFrames()-2].LastPackage = &gno.PackageValue{PkgPath: pkgPath} +func X_testSetRealm(m *gno.Machine, addr, pkgPath string) { + // Associate the given Realm with the caller's frame. + var frame *gno.Frame + // When calling this function from Gno, the two top frames are the following: + // #6 [FRAME FUNC:testSetRealm RECV:(undefined) (2 args) 17/6/0/10/8 LASTPKG:std ...] + // #5 [FRAME FUNC:TestSetRealm RECV:(undefined) (1 args) 14/5/0/8/7 LASTPKG:gno.land/r/tyZ1Vcsta ...] + // We want to set the Realm of the frame where TestSetRealm is being called, hence -3. + for i := m.NumFrames() - 3; i >= 0; i-- { + // Must be a frame from calling a function. + if fr := m.Frames[i]; fr.Func != nil { + frame = fr + break + } + } + + ctx := m.Context.(*TestExecContext) + ctx.RealmFrames[frame] = RealmOverride{ + Addr: crypto.Bech32Address(addr), + PkgPath: pkgPath, + } } -func X_testSetPrevAddr(m *gno.Machine, addr string) { - // clear all frames to return mocked origin caller - for i := m.NumFrames() - 1; i > 0; i-- { - m.Frames[i].LastPackage = nil +func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { + // NOTE: keep in sync with stdlibs/std.getRealm + + var ( + ctx = m.Context.(*TestExecContext) + currentCaller crypto.Bech32Address + // Keeps track of the number of times currentCaller + // has changed. + changes int + ) + + for i := m.NumFrames() - 1; i >= 0; i-- { + fr := m.Frames[i] + override, overridden := ctx.RealmFrames[m.Frames[max(i-1, 0)]] + if !overridden && + (fr.LastPackage == nil || !fr.LastPackage.IsRealm()) { + continue + } + + // LastPackage is a realm. Get caller and pkgPath, and compare against + // currentCaller. + caller, pkgPath := override.Addr, override.PkgPath + if !overridden { + caller = fr.LastPackage.GetPkgAddr().Bech32() + pkgPath = fr.LastPackage.PkgPath + } + if caller != currentCaller { + if changes == height { + return string(caller), pkgPath + } + currentCaller = caller + changes++ + } } - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigCaller = crypto.Bech32Address(addr) - m.Context = ctx + // Fallback case: return OrigCaller. + return string(ctx.OrigCaller), "" } func X_testSetOrigSend(m *gno.Machine, sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64, ) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) spent := std.CompactCoins(spentDenom, spentAmt) ctx.OrigSendSpent = &spent @@ -125,7 +184,7 @@ func X_testSetOrigSend(m *gno.Machine, } func X_testIssueCoins(m *gno.Machine, addr string, denom []string, amt []int64) { - ctx := m.Context.(std.ExecContext) + ctx := m.Context.(*TestExecContext) banker := ctx.Banker for i := range denom { banker.IssueCoin(crypto.Bech32Address(addr), denom[i], amt[i])