From b5a7ff8607c61a7f1e16c1cac681e0c601e365ad Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 22 May 2024 11:45:01 +0200 Subject: [PATCH 1/8] feat(stdlibs/std): change TestSetPrevRealm to TestSetRealm --- gnovm/pkg/gnolang/machine.go | 16 ++-- gnovm/stdlibs/std/banker.go | 10 +-- gnovm/stdlibs/std/context.go | 20 +++++ gnovm/stdlibs/std/emit_event.go | 2 +- gnovm/stdlibs/std/native.go | 16 ++-- gnovm/stdlibs/stdlibs.go | 4 + gnovm/stdlibs/time/time.go | 2 +- gnovm/tests/file.go | 5 +- gnovm/tests/imports.go | 2 +- gnovm/tests/stdlibs/native.go | 66 +++++++++++------ gnovm/tests/stdlibs/std/frame_testing.gno | 9 +++ gnovm/tests/stdlibs/std/std.gno | 9 ++- gnovm/tests/stdlibs/std/std.go | 89 +++++++++++++++++++---- test/a.gno | 8 ++ test/a_test.gno | 11 +++ 15 files changed, 200 insertions(+), 69 deletions(-) create mode 100644 gnovm/tests/stdlibs/std/frame_testing.gno create mode 100644 test/a.gno create mode 100644 test/a_test.gno 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..26dad44fb26 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,22 @@ type ExecContext struct { Banker BankerInterface EventLogger *sdk.EventLogger } + +// ExecContext returns itself. +// This is used to allow extending the exec context using interfaces, +// for instance when testing. +func (e ExecContext) ExecContext() ExecContext { + return e +} + +// 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 { + ExecContext() ExecContext +} + +// GetContext returns the context from the Gno machine. +func GetContext(m *gno.Machine) ExecContext { + return m.Context.(ExecContexter).ExecContext() +} diff --git a/gnovm/stdlibs/std/emit_event.go b/gnovm/stdlibs/std/emit_event.go index 46fea79d43c..a421c20d1bb 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 deb0f1268d2..bdd4fa56f3d 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -25,11 +25,11 @@ func CurrentRealmPath(m *gno.Machine) string { } 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. @@ -64,16 +64,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 { @@ -92,15 +92,17 @@ 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 string, 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. 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..58a54ad0b54 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,7 @@ func testContext(pkgPath string, send std.Coins) stdlibs.ExecContext { Banker: banker, EventLogger: sdk.NewEventLogger(), } - return ctx + return &teststd.TestExecContext{ExecContext: ctx} } type runFileTestOptions struct { diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index d5541fb0554..fea273434a1 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -187,7 +187,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 := stdlibs.GetContext(m) 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 81100838784..3f1926bba04 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -174,30 +174,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) { @@ -205,13 +185,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) }, }, { @@ -276,6 +259,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..27fbfe6469f --- /dev/null +++ b/gnovm/tests/stdlibs/std/frame_testing.gno @@ -0,0 +1,9 @@ +package std + +func NewUserRealm(user Address) Realm { + return Realm{addr: user} +} + +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 0a4f9cc6eff..f31725496c1 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -8,8 +8,9 @@ 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)) } +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 +27,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 72a2a7734ed..7f8128518b7 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -5,13 +5,21 @@ 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 PrevRealm(). + RealmFrame *gno.Frame + RealmAddr crypto.Bech32Address + RealmPkgPath string +} + func AssertOriginCall(m *gno.Machine) { if !IsOriginCall(m) { m.Panic(typedString("invalid non-origin call")) @@ -46,7 +54,7 @@ func TestCurrentRealm(m *gno.Machine) string { } func TestSkipHeights(m *gno.Machine, count int64) { - ctx := m.Context.(std.ExecContext) + ctx := std.GetContext(m) ctx.Height += count m.Context = ctx } @@ -84,44 +92,93 @@ 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 := std.GetContext(m) 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 := std.GetContext(m) ctx.OrigCaller = crypto.Bech32Address(addr) m.Context = ctx } func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { - ctx := m.Context.(std.ExecContext) + ctx := std.GetContext(m) 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.RealmFrame = frame + ctx.RealmAddr = crypto.Bech32Address(addr) + ctx.RealmPkgPath = pkgPath } -func X_testSetPrevAddr(m *gno.Machine, addr string) { - // clear all frames to return mocked origin caller +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-- { - m.Frames[i].LastPackage = nil + fr := m.Frames[i] + if fr != ctx.RealmFrame && + (fr.LastPackage == nil || !fr.LastPackage.IsRealm()) { + continue + } + + // LastPackage is a realm. Get caller and pkgPath, and compare against + // current* values. + var ( + caller crypto.Bech32Address + pkgPath string + ) + if fr == ctx.RealmFrame { + caller, pkgPath = ctx.RealmAddr, ctx.RealmPkgPath + } else { + 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 := std.GetContext(m) ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) spent := std.CompactCoins(spentDenom, spentAmt) ctx.OrigSendSpent = &spent @@ -129,7 +186,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 := std.GetContext(m) banker := ctx.Banker for i := range denom { banker.IssueCoin(crypto.Bech32Address(addr), denom[i], amt[i]) diff --git a/test/a.gno b/test/a.gno new file mode 100644 index 00000000000..44b0b11c13e --- /dev/null +++ b/test/a.gno @@ -0,0 +1,8 @@ +package main + +import "std" + +func Call() { + println(std.CurrentRealm()) + println(std.PrevRealm()) +} diff --git a/test/a_test.gno b/test/a_test.gno new file mode 100644 index 00000000000..4d6ac8f1439 --- /dev/null +++ b/test/a_test.gno @@ -0,0 +1,11 @@ +package main + +import ( + "std" + "testing" +) + +func TestHello(t *testing.T) { + std.TestSetRealm(std.NewUserRealm("gno.land/r/demo/users")) + Call() +} From 34403b23c42e3967a53006f4e3b049b6e9fd857d Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 22 May 2024 15:05:58 +0200 Subject: [PATCH 2/8] fixup --- examples/gno.land/r/x/test_prev/gno.mod | 6 --- examples/gno.land/r/x/test_prev/test_prev.gno | 18 ------- .../gno.land/r/x/test_prev/test_prev_test.gno | 32 ------------- gnovm/stdlibs/std/context.go | 14 ++++-- gnovm/stdlibs/std/native.go | 2 +- gnovm/tests/file.go | 5 +- gnovm/tests/files/zrealm_crossrealm13.gno | 48 +++++++++++++++++++ gnovm/tests/files/zrealm_crossrealm13a.gno | 46 ++++++++++++++++++ gnovm/tests/stdlibs/std/frame_testing.gno | 3 ++ gnovm/tests/stdlibs/std/std.gno | 5 ++ gnovm/tests/stdlibs/std/std.go | 36 +++++++------- test/a.gno | 8 ---- test/a_test.gno | 11 ----- 13 files changed, 136 insertions(+), 98 deletions(-) delete mode 100644 examples/gno.land/r/x/test_prev/gno.mod delete mode 100644 examples/gno.land/r/x/test_prev/test_prev.gno delete mode 100644 examples/gno.land/r/x/test_prev/test_prev_test.gno create mode 100644 gnovm/tests/files/zrealm_crossrealm13.gno create mode 100644 gnovm/tests/files/zrealm_crossrealm13a.gno delete mode 100644 test/a.gno delete mode 100644 test/a_test.gno 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/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index 26dad44fb26..ff5c91a14eb 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -21,21 +21,27 @@ type ExecContext struct { EventLogger *sdk.EventLogger } -// ExecContext returns itself. +// GetContext returns the execution context. // This is used to allow extending the exec context using interfaces, // for instance when testing. -func (e ExecContext) ExecContext() ExecContext { +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 { - ExecContext() ExecContext + 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).ExecContext() + return m.Context.(ExecContexter).GetExecContext() } diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index bdd4fa56f3d..a31c11000b3 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -109,7 +109,7 @@ func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { 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/tests/file.go b/gnovm/tests/file.go index 58a54ad0b54..9e458556860 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -66,7 +66,10 @@ func testContext(pkgPath string, send std.Coins) *teststd.TestExecContext { Banker: banker, EventLogger: sdk.NewEventLogger(), } - return &teststd.TestExecContext{ExecContext: 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/stdlibs/std/frame_testing.gno b/gnovm/tests/stdlibs/std/frame_testing.gno index 27fbfe6469f..171756c54f5 100644 --- a/gnovm/tests/stdlibs/std/frame_testing.gno +++ b/gnovm/tests/stdlibs/std/frame_testing.gno @@ -1,9 +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 f31725496c1..aa4c48d959d 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -8,9 +8,14 @@ func ClearStoreCache() // injected func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(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() diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 7f8128518b7..2d79d19705a 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -14,10 +14,15 @@ import ( type TestExecContext struct { std.ExecContext - // These are used to set up the result of PrevRealm(). - RealmFrame *gno.Frame - RealmAddr crypto.Bech32Address - RealmPkgPath string + // 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) { @@ -126,9 +131,10 @@ func X_testSetRealm(m *gno.Machine, addr, pkgPath string) { } ctx := m.Context.(*TestExecContext) - ctx.RealmFrame = frame - ctx.RealmAddr = crypto.Bech32Address(addr) - ctx.RealmPkgPath = pkgPath + ctx.RealmFrames[frame] = RealmOverride{ + Addr: crypto.Bech32Address(addr), + PkgPath: pkgPath, + } } func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { @@ -142,22 +148,18 @@ func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { changes int ) - for i := m.NumFrames() - 1; i > 0; i-- { + for i := m.NumFrames() - 1; i >= 0; i-- { fr := m.Frames[i] - if fr != ctx.RealmFrame && + 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 - // current* values. - var ( - caller crypto.Bech32Address - pkgPath string - ) - if fr == ctx.RealmFrame { - caller, pkgPath = ctx.RealmAddr, ctx.RealmPkgPath - } else { + // currentCaller. + caller, pkgPath := override.Addr, override.PkgPath + if !overridden { caller = fr.LastPackage.GetPkgAddr().Bech32() pkgPath = fr.LastPackage.PkgPath } diff --git a/test/a.gno b/test/a.gno deleted file mode 100644 index 44b0b11c13e..00000000000 --- a/test/a.gno +++ /dev/null @@ -1,8 +0,0 @@ -package main - -import "std" - -func Call() { - println(std.CurrentRealm()) - println(std.PrevRealm()) -} diff --git a/test/a_test.gno b/test/a_test.gno deleted file mode 100644 index 4d6ac8f1439..00000000000 --- a/test/a_test.gno +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "std" - "testing" -) - -func TestHello(t *testing.T) { - std.TestSetRealm(std.NewUserRealm("gno.land/r/demo/users")) - Call() -} From a71cdf9f16bc5f10bb8d9265cd0473c43f00004d Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 22 May 2024 15:52:45 +0200 Subject: [PATCH 3/8] fix type assertions --- gnovm/tests/imports.go | 3 +-- gnovm/tests/stdlibs/std/std.go | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index fea273434a1..f60da6a33d6 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -39,7 +39,6 @@ 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" "github.com/gnolang/gno/tm2/pkg/db/memdb" osm "github.com/gnolang/gno/tm2/pkg/os" @@ -187,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 := stdlibs.GetContext(m) + ctx := m.Context.(*TestExecContext) ctx.Timestamp += sec ctx.TimestampNano += nano if ctx.TimestampNano >= int64(time.Second) { diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 2d79d19705a..2d701cf5261 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -59,7 +59,7 @@ func TestCurrentRealm(m *gno.Machine) string { } func TestSkipHeights(m *gno.Machine, count int64) { - ctx := std.GetContext(m) + ctx := m.Context.(*TestExecContext) ctx.Height += count m.Context = ctx } @@ -97,20 +97,20 @@ func X_callerAt(m *gno.Machine, n int) string { } if n == m.NumFrames()-1 { // This makes it consistent with GetOrigCaller and TestSetOrigCaller. - ctx := std.GetContext(m) + 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 := std.GetContext(m) + ctx := m.Context.(*TestExecContext) ctx.OrigCaller = crypto.Bech32Address(addr) m.Context = ctx } func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { - ctx := std.GetContext(m) + ctx := m.Context.(*TestExecContext) ctx.OrigPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } @@ -180,7 +180,7 @@ func X_testSetOrigSend(m *gno.Machine, sentDenom []string, sentAmt []int64, spentDenom []string, spentAmt []int64, ) { - ctx := std.GetContext(m) + ctx := m.Context.(*TestExecContext) ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) spent := std.CompactCoins(spentDenom, spentAmt) ctx.OrigSendSpent = &spent @@ -188,7 +188,7 @@ func X_testSetOrigSend(m *gno.Machine, } func X_testIssueCoins(m *gno.Machine, addr string, denom []string, amt []int64) { - ctx := std.GetContext(m) + ctx := m.Context.(*TestExecContext) banker := ctx.Banker for i := range denom { banker.IssueCoin(crypto.Bech32Address(addr), denom[i], amt[i]) From 38d320472a6b37089fdf2a519252de2dccb063a4 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 22 May 2024 15:54:20 +0200 Subject: [PATCH 4/8] fixup --- gnovm/tests/imports.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index f60da6a33d6..20ed18cd2d1 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -40,6 +40,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" 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 +187,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.(*TestExecContext) + ctx := m.Context.(*teststd.TestExecContext) ctx.Timestamp += sec ctx.TimestampNano += nano if ctx.TimestampNano >= int64(time.Second) { From e5b1b35d60a0aab37662725270bf059f451cb179 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Fri, 24 May 2024 15:37:31 +0200 Subject: [PATCH 5/8] feat: add r/sys/teams Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/gnoland/home/home.gno | 8 ++ examples/gno.land/r/sys/teams/teams.gno | 89 ++++++++++++++++++++ examples/gno.land/r/sys/teams/teams_test.gno | 21 +++++ 3 files changed, 118 insertions(+) create mode 100644 examples/gno.land/r/sys/teams/teams.gno create mode 100644 examples/gno.land/r/sys/teams/teams_test.gno diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index d8bec8242c2..c7e7e0ffe28 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -6,8 +6,16 @@ import ( "gno.land/p/demo/ufmt" "gno.land/p/demo/ui" blog "gno.land/r/gnoland/blog" + "gno.land/r/sys/teams" ) +func init() { + teams.SetMembers( + "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", teams.AdminRole, // @manfred + // ... + ) +} + // XXX: p/demo/ui API is crappy, we need to make it more idiomatic // XXX: use an updatable block system to update content from a DAO // XXX: var blocks avl.Tree diff --git a/examples/gno.land/r/sys/teams/teams.gno b/examples/gno.land/r/sys/teams/teams.gno new file mode 100644 index 00000000000..b993a576031 --- /dev/null +++ b/examples/gno.land/r/sys/teams/teams.gno @@ -0,0 +1,89 @@ +package teams + +import ( + "errors" + "std" + "strings" + + "gno.land/p/demo/avl" +) + +type Team struct { + Admins []std.Address + Editors []std.Address +} + +var ( + ErrNotHomeCaller = errors.New("must be called by r//home userland logic") + ErrInvalidMembers = errors.New("invalid members argument") +) + +type Role uint + +const ( + RoleUnset = "" + RoleAdmin = "admin" // editor + right to upgrade r//home + RoleEditor = "editor" // can add package under {p,r}//... + // XXX: RoleReadonly = "readonly"? +) + +var teams avl.Tree // team -> []users + +// TODO: doc +func SetMembers(attrs ...string) { + // XXX: panic if members list is too big for now. + + attrsLen := len(attrs) + if attrsLen%2 != 0 { + panic(ErrInvalidMembers) + } + + prev := std.PrevRealm() + if prev.IsUser() { + panic(ErrNotHomeCaller) + } + + parts := strings.Split(prev.PkgPath(), "/") + if len(parts) != 3 || parts[0] != "r" || parts[2] != "home" { + panic(ErrNotHomeCaller) + } + teamName := parts[1] + + team := Team{ + Admins: []std.Address{}, + Editors: []std.Address{}, + } + + for i := 0; i < attrsLen-1; i += 2 { + addr := std.Address(attrs[i]) + role := attrs[i] + if role == RoleAdmin { + team.Admins = append(team.Admins, addr) + } + if role == RoleEditor { + team.Editors = append(team.Editors, addr) + } + } + + teams.Set(teamName, &team) +} + +func GetAdmins(teamName string) []std.Address { + t, ok := teams.Get(teamName) + if !ok { + return nil + } + team := t.(*Team) + return append(team.Admins, team.Editors...) +} + +func GetEditors(teamName string) []std.Address { + t, ok := teams.Get(teamName) + if !ok { + return nil + } + team := t.(*Team) + return append(team.Admins, team.Editors...) +} + +// XXX: func GetUserTeams(addr std.Address) diff --git a/examples/gno.land/r/sys/teams/teams_test.gno b/examples/gno.land/r/sys/teams/teams_test.gno new file mode 100644 index 00000000000..456d170f1bf --- /dev/null +++ b/examples/gno.land/r/sys/teams/teams_test.gno @@ -0,0 +1,21 @@ +package teams + +import ( + "std" + "testing" + + "gno.land/r/sys/teams" +) + +func TestPackage(t *testing.T) { + if teams.GetAdmins("gnoland") != nil { + t.Errorf("gnoland should have no admins") + } + if teams.GetEditors("gnoland") != nil { + t.Errorf("gnoland should have no admins") + } + std.TestSetPrevRealm("r/gnoland/home") + teams.SetMembers( + // "g123456789", teams.RoleAdmin, + ) +} From 8ce821e92ef456970ef33f7b8e7b6825c74962da Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Fri, 24 May 2024 16:21:56 +0200 Subject: [PATCH 6/8] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/sys/teams/teams.gno | 5 +++-- examples/gno.land/r/sys/teams/teams_test.gno | 15 ++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/gno.land/r/sys/teams/teams.gno b/examples/gno.land/r/sys/teams/teams.gno index b993a576031..2ae0d93f23d 100644 --- a/examples/gno.land/r/sys/teams/teams.gno +++ b/examples/gno.land/r/sys/teams/teams.gno @@ -39,6 +39,8 @@ func SetMembers(attrs ...string) { } prev := std.PrevRealm() + println("aaa", prev.IsUser(), prev.PkgPath(), prev.Addr()) + return if prev.IsUser() { panic(ErrNotHomeCaller) } @@ -53,7 +55,6 @@ func SetMembers(attrs ...string) { Admins: []std.Address{}, Editors: []std.Address{}, } - for i := 0; i < attrsLen-1; i += 2 { addr := std.Address(attrs[i]) role := attrs[i] @@ -74,7 +75,7 @@ func GetAdmins(teamName string) []std.Address { return nil } team := t.(*Team) - return append(team.Admins, team.Editors...) + return team.Admins } func GetEditors(teamName string) []std.Address { diff --git a/examples/gno.land/r/sys/teams/teams_test.gno b/examples/gno.land/r/sys/teams/teams_test.gno index 456d170f1bf..6062aa0f455 100644 --- a/examples/gno.land/r/sys/teams/teams_test.gno +++ b/examples/gno.land/r/sys/teams/teams_test.gno @@ -4,18 +4,23 @@ import ( "std" "testing" - "gno.land/r/sys/teams" + "gno.land/p/demo/testutils" ) func TestPackage(t *testing.T) { - if teams.GetAdmins("gnoland") != nil { + if GetAdmins("gnoland") != nil { t.Errorf("gnoland should have no admins") } - if teams.GetEditors("gnoland") != nil { + if GetEditors("gnoland") != nil { t.Errorf("gnoland should have no admins") } + std.TestSetPrevRealm("r/gnoland/home") - teams.SetMembers( - // "g123456789", teams.RoleAdmin, + // std.TestSetCurrentRealm("r/gnoland/home") + test1 := testutils.TestAddress("test1") + test2 := testutils.TestAddress("test2") + std.TestSetOrigCaller(test2) + SetMembers( + string(test1), string(RoleAdmin), ) } From 533c541b87e1ce9a67f1765fcc29016a7fb51ace Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Fri, 24 May 2024 16:33:49 +0200 Subject: [PATCH 7/8] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/sys/teams/teams.gno | 11 ++++--- examples/gno.land/r/sys/teams/teams_test.gno | 30 +++++++++++++++++--- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/examples/gno.land/r/sys/teams/teams.gno b/examples/gno.land/r/sys/teams/teams.gno index 2ae0d93f23d..882d26ca7e1 100644 --- a/examples/gno.land/r/sys/teams/teams.gno +++ b/examples/gno.land/r/sys/teams/teams.gno @@ -39,17 +39,16 @@ func SetMembers(attrs ...string) { } prev := std.PrevRealm() - println("aaa", prev.IsUser(), prev.PkgPath(), prev.Addr()) - return if prev.IsUser() { panic(ErrNotHomeCaller) } - parts := strings.Split(prev.PkgPath(), "/") - if len(parts) != 3 || parts[0] != "r" || parts[2] != "home" { + prevPath := prev.PkgPath() + parts := strings.Split(prevPath, "/") + if len(parts) != 4 || !strings.HasPrefix(prevPath, "gno.land/r/") || !strings.HasSuffix(prevPath, "/home") { panic(ErrNotHomeCaller) } - teamName := parts[1] + teamName := parts[2] team := Team{ Admins: []std.Address{}, @@ -57,7 +56,7 @@ func SetMembers(attrs ...string) { } for i := 0; i < attrsLen-1; i += 2 { addr := std.Address(attrs[i]) - role := attrs[i] + role := attrs[i+1] if role == RoleAdmin { team.Admins = append(team.Admins, addr) } diff --git a/examples/gno.land/r/sys/teams/teams_test.gno b/examples/gno.land/r/sys/teams/teams_test.gno index 6062aa0f455..87128afccc8 100644 --- a/examples/gno.land/r/sys/teams/teams_test.gno +++ b/examples/gno.land/r/sys/teams/teams_test.gno @@ -12,15 +12,37 @@ func TestPackage(t *testing.T) { t.Errorf("gnoland should have no admins") } if GetEditors("gnoland") != nil { - t.Errorf("gnoland should have no admins") + t.Errorf("gnoland should have no editors") } - std.TestSetPrevRealm("r/gnoland/home") - // std.TestSetCurrentRealm("r/gnoland/home") + std.TestSetRealm(std.NewCodeRealm("gno.land/r/gnoland/home")) test1 := testutils.TestAddress("test1") test2 := testutils.TestAddress("test2") - std.TestSetOrigCaller(test2) SetMembers( string(test1), string(RoleAdmin), + string(test2), string(RoleEditor), ) + + admins := GetAdmins("gnoland") + if !isEqualAddressSlice(admins, []std.Address{test1}) { + t.Errorf("gnoland should have test1 as admins") + } + editors := GetEditors("gnoland") + if !isEqualAddressSlice(editors, []std.Address{test1, test2}) { + t.Errorf("gnoland should have test1 and test2 as editors") + } +} + +func isEqualAddressSlice(a, b []std.Address) bool { + if len(a) != len(b) { + return false + } + + for i, v := range a { + if v != b[i] { + return false + } + } + + return true } From a3b67cf483bc3df7421b3ba39d43df10261dfc12 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Fri, 24 May 2024 16:38:15 +0200 Subject: [PATCH 8/8] chore: add comments Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/r/gnoland/home/home.gno | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno index c7e7e0ffe28..e1c5e07231f 100644 --- a/examples/gno.land/r/gnoland/home/home.gno +++ b/examples/gno.land/r/gnoland/home/home.gno @@ -12,10 +12,12 @@ import ( func init() { teams.SetMembers( "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", teams.AdminRole, // @manfred - // ... + // TODO: add more people in the init ) } +// TODO: add helpers to manage the team set from a DAO + // XXX: p/demo/ui API is crappy, we need to make it more idiomatic // XXX: use an updatable block system to update content from a DAO // XXX: var blocks avl.Tree