diff --git a/examples/gno.land/p/n2p5/loci/gno.mod b/examples/gno.land/p/n2p5/loci/gno.mod new file mode 100644 index 00000000000..ec30d72d752 --- /dev/null +++ b/examples/gno.land/p/n2p5/loci/gno.mod @@ -0,0 +1 @@ +module gno.land/p/n2p5/loci diff --git a/examples/gno.land/p/n2p5/loci/loci.gno b/examples/gno.land/p/n2p5/loci/loci.gno new file mode 100644 index 00000000000..7bd5c29c3af --- /dev/null +++ b/examples/gno.land/p/n2p5/loci/loci.gno @@ -0,0 +1,44 @@ +// loci is a single purpose datastore keyed by the caller's address. It has two +// functions: Set and Get. loci is plural for locus, which is a central or core +// place where something is found or from which it originates. In this case, +// it's a simple key-value store where an address (the key) can store exactly +// one value (in the form of a byte slice). Only the caller can set the value +// for their address, but anyone can retrieve the value for any address. +package loci + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// LociStore is a simple key-value store that uses +// an AVL tree to store the data. +type LociStore struct { + internal *avl.Tree +} + +// New creates a reference to a new LociStore. +func New() *LociStore { + return &LociStore{ + internal: avl.NewTree(), + } +} + +// Set stores a byte slice in the AVL tree using the `std.PrevRealm().Addr()` +// string as the key. +func (s *LociStore) Set(value []byte) { + key := string(std.PrevRealm().Addr()) + s.internal.Set(key, value) +} + +// Get retrieves a byte slice from the AVL tree using the provided address. +// The return values are the byte slice value and a boolean indicating +// whether the value exists. +func (s *LociStore) Get(addr std.Address) []byte { + value, exists := s.internal.Get(string(addr)) + if !exists { + return nil + } + return value.([]byte) +} diff --git a/examples/gno.land/p/n2p5/loci/loci_test.gno b/examples/gno.land/p/n2p5/loci/loci_test.gno new file mode 100644 index 00000000000..bb216a8539e --- /dev/null +++ b/examples/gno.land/p/n2p5/loci/loci_test.gno @@ -0,0 +1,84 @@ +package loci + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" +) + +func TestLociStore(t *testing.T) { + t.Parallel() + + u1 := testutils.TestAddress("u1") + u2 := testutils.TestAddress("u1") + + t.Run("TestSet", func(t *testing.T) { + t.Parallel() + store := New() + u1 := testutils.TestAddress("u1") + + m1 := []byte("hello") + m2 := []byte("world") + std.TestSetOrigCaller(u1) + + // Ensure that the value is nil before setting it. + r1 := store.Get(u1) + if r1 != nil { + t.Errorf("expected value to be nil, got '%s'", r1) + } + store.Set(m1) + // Ensure that the value is correct after setting it. + r2 := store.Get(u1) + if string(r2) != "hello" { + t.Errorf("expected value to be 'hello', got '%s'", r2) + } + store.Set(m2) + // Ensure that the value is correct after overwriting it. + r3 := store.Get(u1) + if string(r3) != "world" { + t.Errorf("expected value to be 'world', got '%s'", r3) + } + }) + t.Run("TestGet", func(t *testing.T) { + t.Parallel() + store := New() + u1 := testutils.TestAddress("u1") + u2 := testutils.TestAddress("u2") + u3 := testutils.TestAddress("u3") + u4 := testutils.TestAddress("u4") + + m1 := []byte("hello") + m2 := []byte("world") + m3 := []byte("goodbye") + + std.TestSetOrigCaller(u1) + store.Set(m1) + std.TestSetOrigCaller(u2) + store.Set(m2) + std.TestSetOrigCaller(u3) + store.Set(m3) + + // Ensure that the value is correct after setting it. + r0 := store.Get(u4) + if r0 != nil { + t.Errorf("expected value to be nil, got '%s'", r0) + } + // Ensure that the value is correct after setting it. + r1 := store.Get(u1) + if string(r1) != "hello" { + t.Errorf("expected value to be 'hello', got '%s'", r1) + } + // Ensure that the value is correct after setting it. + r2 := store.Get(u2) + if string(r2) != "world" { + t.Errorf("expected value to be 'world', got '%s'", r2) + } + // Ensure that the value is correct after setting it. + r3 := store.Get(u3) + if string(r3) != "goodbye" { + t.Errorf("expected value to be 'goodbye', got '%s'", r3) + } + }) + +} diff --git a/examples/gno.land/r/n2p5/loci/gno.mod b/examples/gno.land/r/n2p5/loci/gno.mod new file mode 100644 index 00000000000..131e0d73467 --- /dev/null +++ b/examples/gno.land/r/n2p5/loci/gno.mod @@ -0,0 +1 @@ +module gno.land/r/n2p5/loci diff --git a/examples/gno.land/r/n2p5/loci/loci.gno b/examples/gno.land/r/n2p5/loci/loci.gno new file mode 100644 index 00000000000..36f282e729f --- /dev/null +++ b/examples/gno.land/r/n2p5/loci/loci.gno @@ -0,0 +1,68 @@ +package loci + +import ( + "encoding/base64" + "std" + + "gno.land/p/demo/ufmt" + "gno.land/p/n2p5/loci" +) + +var store *loci.LociStore + +func init() { + store = loci.New() +} + +// Set takes a base64 encoded string and stores it in the Loci store. +// Keyed by the address of the caller. It also emits a "set" event with +// the address of the caller. +func Set(value string) { + b, err := base64.StdEncoding.DecodeString(value) + if err != nil { + panic(err) + } + store.Set(b) + std.Emit("SetValue", "ForAddr", string(std.PrevRealm().Addr())) +} + +// Get retrieves the value stored at the provided address and +// returns it as a base64 encoded string. +func Get(addr std.Address) string { + return base64.StdEncoding.EncodeToString(store.Get(addr)) +} + +func Render(path string) string { + if path == "" { + return about + } + return renderGet(std.Address(path)) +} + +func renderGet(addr std.Address) string { + value := "```\n" + Get(addr) + "\n```" + + return ufmt.Sprintf(` +# Loci Value Viewer + +**Address:** %s + +%s + +`, addr, value) +} + +const about = ` +# Welcome to Loci + +Loci is a simple key-value store keyed by the caller's gno.land address. +Only the caller can set the value for their address, but anyone can +retrieve the value for any address. There are only two functions: Set and Get. +If you'd like to set a value, simply base64 encode any message you'd like and +it will be stored in in Loci. If you'd like to retrieve a value, simply provide +the address of the value you'd like to retrieve. + +For convenience, you can also use gnoweb to view the value for a given address, +if one exists. For instance append :g1j39fhg29uehm7twwnhvnpz3ggrm6tprhq65t0t to +this URL to view the value stored at that address. +`