Skip to content

Commit

Permalink
feat: add {p,r}/n2p5/loci
Browse files Browse the repository at this point in the history
  • Loading branch information
n2p5 committed Dec 14, 2024
1 parent c48219a commit 6082bd4
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/gno.land/p/n2p5/loci/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/n2p5/loci
44 changes: 44 additions & 0 deletions examples/gno.land/p/n2p5/loci/loci.gno
Original file line number Diff line number Diff line change
@@ -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)
}
84 changes: 84 additions & 0 deletions examples/gno.land/p/n2p5/loci/loci_test.gno
Original file line number Diff line number Diff line change
@@ -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)
}
})

}
1 change: 1 addition & 0 deletions examples/gno.land/r/n2p5/loci/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/n2p5/loci
68 changes: 68 additions & 0 deletions examples/gno.land/r/n2p5/loci/loci.gno
Original file line number Diff line number Diff line change
@@ -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.
`

0 comments on commit 6082bd4

Please sign in to comment.