Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(examples): add {p,r}/n2p5/loci #3338

Merged
merged 2 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
`
Loading