Skip to content

Commit

Permalink
Merge branch 'master' into feat/fomo3d
Browse files Browse the repository at this point in the history
  • Loading branch information
stefann-01 authored Jan 16, 2025
2 parents 1322874 + 3fa9940 commit 8ee448d
Show file tree
Hide file tree
Showing 60 changed files with 3,470 additions and 6,733 deletions.
2 changes: 1 addition & 1 deletion contribs/gnodev/pkg/dev/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ func testingCallRealm(t *testing.T, node *Node, msgs ...vm.MsgCall) (*core_types

txcfg := gnoclient.BaseTxCfg{
GasFee: ugnot.ValueString(1000000), // Gas fee
GasWanted: 2_000_000, // Gas wanted
GasWanted: 3_000_000, // Gas wanted
}

// Set Caller in the msgs
Expand Down
16 changes: 16 additions & 0 deletions examples/gno.land/p/demo/avl/list/list.gno
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ import (
"gno.land/p/demo/seqid"
)

// IList defines the interface for list operations
type IList interface {
Len() int
Append(values ...interface{})
Get(index int) interface{}
Set(index int, value interface{}) bool
Delete(index int) (interface{}, bool)
Slice(startIndex, endIndex int) []interface{}
ForEach(fn func(index int, value interface{}) bool)
Clone() *List
DeleteRange(startIndex, endIndex int) int
}

// Verify List implements IList interface
var _ IList = (*List)(nil)

// List represents an ordered sequence of items backed by an AVL tree
type List struct {
tree avl.Tree
Expand Down
64 changes: 64 additions & 0 deletions examples/gno.land/p/demo/avl/list/list_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package list

import (
"testing"

"gno.land/p/demo/ufmt"
)

func TestList_Basic(t *testing.T) {
Expand Down Expand Up @@ -395,6 +397,68 @@ func TestList_IndexConsistency(t *testing.T) {
}
}

func TestList_RecursiveSafety(t *testing.T) {
// Create a new list
l := &List{}

// Add some initial values
l.Append("id1")
l.Append("id2")
l.Append("id3")

// Test deep list traversal
found := false
l.ForEach(func(i int, v interface{}) bool {
if str, ok := v.(string); ok {
if str == "id2" {
found = true
return true // stop iteration
}
}
return false // continue iteration
})

if !found {
t.Error("Failed to find expected value in list")
}

short := testing.Short()

// Test recursive safety by performing multiple operations
for i := 0; i < 1000; i++ {
// Add new value
l.Append(ufmt.Sprintf("id%d", i+4))

if !short {
// Search for a value
var lastFound bool
l.ForEach(func(j int, v interface{}) bool {
if str, ok := v.(string); ok {
if str == ufmt.Sprintf("id%d", i+3) {
lastFound = true
return true
}
}
return false
})

if !lastFound {
t.Errorf("Failed to find value id%d after insertion", i+3)
}
}
}

// Verify final length
expectedLen := 1003 // 3 initial + 1000 added
if l.Len() != expectedLen {
t.Errorf("Expected length %d, got %d", expectedLen, l.Len())
}

if short {
t.Skip("skipping extended recursive safety test in short mode")
}
}

// Helper function to compare slices
func sliceEqual(a, b []interface{}) bool {
if len(a) != len(b) {
Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/avl/rolist/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/avl/rolist
119 changes: 119 additions & 0 deletions examples/gno.land/p/demo/avl/rolist/rolist.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Package rolist provides a read-only wrapper for list.List with safe value transformation.
//
// It is useful when you want to expose a read-only view of a list while ensuring that
// the sensitive data cannot be modified.
//
// Example:
//
// // Define a user structure with sensitive data
// type User struct {
// Name string
// Balance int
// Internal string // sensitive field
// }
//
// // Create and populate the original list
// privateList := list.New()
// privateList.Append(&User{
// Name: "Alice",
// Balance: 100,
// Internal: "sensitive",
// })
//
// // Create a safe transformation function that copies the struct
// // while excluding sensitive data
// makeEntrySafeFn := func(v interface{}) interface{} {
// u := v.(*User)
// return &User{
// Name: u.Name,
// Balance: u.Balance,
// Internal: "", // omit sensitive data
// }
// }
//
// // Create a read-only view of the list
// publicList := rolist.Wrap(list, makeEntrySafeFn)
//
// // Safely access the data
// value := publicList.Get(0)
// user := value.(*User)
// // user.Name == "Alice"
// // user.Balance == 100
// // user.Internal == "" (sensitive data is filtered)
package rolist

import (
"gno.land/p/demo/avl/list"
)

// IReadOnlyList defines the read-only operations available on a list.
type IReadOnlyList interface {
Len() int
Get(index int) interface{}
Slice(startIndex, endIndex int) []interface{}
ForEach(fn func(index int, value interface{}) bool)
}

// ReadOnlyList wraps a list.List and provides read-only access.
type ReadOnlyList struct {
list *list.List
makeEntrySafeFn func(interface{}) interface{}
}

// Verify interface implementations
var _ IReadOnlyList = (*ReadOnlyList)(nil)
var _ IReadOnlyList = (interface{ list.IList })(nil) // is subset of list.IList

// Wrap creates a new ReadOnlyList from an existing list.List and a safety transformation function.
// If makeEntrySafeFn is nil, values will be returned as-is without transformation.
func Wrap(list *list.List, makeEntrySafeFn func(interface{}) interface{}) *ReadOnlyList {
return &ReadOnlyList{
list: list,
makeEntrySafeFn: makeEntrySafeFn,
}
}

// getSafeValue applies the makeEntrySafeFn if it exists, otherwise returns the original value
func (rol *ReadOnlyList) getSafeValue(value interface{}) interface{} {
if rol.makeEntrySafeFn == nil {
return value
}
return rol.makeEntrySafeFn(value)
}

// Len returns the number of elements in the list.
func (rol *ReadOnlyList) Len() int {
return rol.list.Len()
}

// Get returns the value at the specified index, converted to a safe format.
// Returns nil if index is out of bounds.
func (rol *ReadOnlyList) Get(index int) interface{} {
value := rol.list.Get(index)
if value == nil {
return nil
}
return rol.getSafeValue(value)
}

// Slice returns a slice of values from startIndex (inclusive) to endIndex (exclusive),
// with all values converted to a safe format.
func (rol *ReadOnlyList) Slice(startIndex, endIndex int) []interface{} {
values := rol.list.Slice(startIndex, endIndex)
if values == nil {
return nil
}

result := make([]interface{}, len(values))
for i, v := range values {
result[i] = rol.getSafeValue(v)
}
return result
}

// ForEach iterates through all elements in the list, providing safe versions of the values.
func (rol *ReadOnlyList) ForEach(fn func(index int, value interface{}) bool) {
rol.list.ForEach(func(index int, value interface{}) bool {
return fn(index, rol.getSafeValue(value))
})
}
162 changes: 162 additions & 0 deletions examples/gno.land/p/demo/avl/rolist/rolist_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package rolist

import (
"testing"

"gno.land/p/demo/avl/list"
)

func TestExample(t *testing.T) {
// User represents our internal data structure
type User struct {
ID string
Name string
Balance int
Internal string // sensitive internal data
}

// Create and populate the original list
l := &list.List{}
l.Append(
&User{
ID: "1",
Name: "Alice",
Balance: 100,
Internal: "sensitive_data_1",
},
&User{
ID: "2",
Name: "Bob",
Balance: 200,
Internal: "sensitive_data_2",
},
)

// Define a makeEntrySafeFn that:
// 1. Creates a defensive copy of the User struct
// 2. Omits sensitive internal data
makeEntrySafeFn := func(v interface{}) interface{} {
originalUser := v.(*User)
return &User{
ID: originalUser.ID,
Name: originalUser.Name,
Balance: originalUser.Balance,
Internal: "", // Omit sensitive data
}
}

// Create a read-only view of the list
roList := Wrap(l, makeEntrySafeFn)

// Test retrieving and verifying a user
t.Run("Get User", func(t *testing.T) {
// Get user from read-only list
value := roList.Get(0)
if value == nil {
t.Fatal("User at index 0 not found")
}

user := value.(*User)

// Verify user data is correct
if user.Name != "Alice" || user.Balance != 100 {
t.Errorf("Unexpected user data: got name=%s balance=%d", user.Name, user.Balance)
}

// Verify sensitive data is not exposed
if user.Internal != "" {
t.Error("Sensitive data should not be exposed")
}

// Verify it's a different instance than the original
originalUser := l.Get(0).(*User)
if user == originalUser {
t.Error("Read-only list should return a copy, not the original pointer")
}
})

// Test slice functionality
t.Run("Slice Users", func(t *testing.T) {
users := roList.Slice(0, 2)
if len(users) != 2 {
t.Fatalf("Expected 2 users, got %d", len(users))
}

for _, v := range users {
user := v.(*User)
if user.Internal != "" {
t.Error("Sensitive data exposed in slice")
}
}
})

// Test ForEach functionality
t.Run("ForEach Users", func(t *testing.T) {
count := 0
roList.ForEach(func(index int, value interface{}) bool {
user := value.(*User)
if user.Internal != "" {
t.Error("Sensitive data exposed during iteration")
}
count++
return false
})

if count != 2 {
t.Errorf("Expected 2 users, got %d", count)
}
})
}

func TestNilMakeEntrySafeFn(t *testing.T) {
// Create a list with some test data
l := &list.List{}
originalValue := []int{1, 2, 3}
l.Append(originalValue)

// Create a ReadOnlyList with nil makeEntrySafeFn
roList := Wrap(l, nil)

// Test that we get back the original value
value := roList.Get(0)
if value == nil {
t.Fatal("Value not found")
}

// Verify it's the exact same slice (not a copy)
retrievedSlice := value.([]int)
if &retrievedSlice[0] != &originalValue[0] {
t.Error("Expected to get back the original slice reference")
}
}

func TestReadOnlyList(t *testing.T) {
// Example of a makeEntrySafeFn that appends "_readonly" to demonstrate transformation
makeEntrySafeFn := func(value interface{}) interface{} {
return value.(string) + "_readonly"
}

l := &list.List{}
l.Append("value1", "value2", "value3")

roList := Wrap(l, makeEntrySafeFn)

tests := []struct {
name string
index int
expected interface{}
}{
{"ExistingIndex0", 0, "value1_readonly"},
{"ExistingIndex1", 1, "value2_readonly"},
{"NonExistingIndex", 3, nil},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value := roList.Get(tt.index)
if value != tt.expected {
t.Errorf("For index %d, expected %v, got %v", tt.index, tt.expected, value)
}
})
}
}
Loading

0 comments on commit 8ee448d

Please sign in to comment.