Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
5 changes: 5 additions & 0 deletions contract/p/gnoswap/uinttree/doc.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Package uinttree provides a shared uint64-keyed AVL wrapper for GnoSwap realms.
//
// Keys are encoded as zero-padded decimal strings so natural AVL ordering matches
// uint64 ordering, which makes the package suitable for timestamp-indexed history.
package uinttree
2 changes: 2 additions & 0 deletions contract/p/gnoswap/uinttree/gnomod.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module = "gno.land/p/gnoswap/uinttree"
gno = "0.9"
118 changes: 118 additions & 0 deletions contract/p/gnoswap/uinttree/tree.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package uinttree

import (
"strconv"
"strings"

avl "gno.land/p/nt/avl/v0"
)

type UintTree struct {
tree *avl.Tree
}

func NewUintTree() *UintTree {
return &UintTree{tree: avl.NewTree()}
}

func (self *UintTree) Get(key uint64) (any, bool) {
v, ok := self.tree.Get(EncodeUint(key))
if !ok {
return nil, false
}
return v, true
}

func (self *UintTree) GetByInt64(key int64) (any, bool) {
return self.Get(SafeConvertInt64ToUint64(key))
}

func (self *UintTree) Set(key uint64, value any) {
self.tree.Set(EncodeUint(key), value)
}

func (self *UintTree) SetByInt64(key int64, value any) {
self.Set(SafeConvertInt64ToUint64(key), value)
}

func (self *UintTree) Has(key uint64) bool {
return self.tree.Has(EncodeUint(key))
}

func (self *UintTree) HasByInt64(key int64) bool {
return self.Has(SafeConvertInt64ToUint64(key))
}

func (self *UintTree) Remove(key uint64) {
self.tree.Remove(EncodeUint(key))
}

func (self *UintTree) RemoveByInt64(key int64) {
self.Remove(SafeConvertInt64ToUint64(key))
}

func (self *UintTree) Iterate(start, end uint64, fn func(key uint64, value any) bool) {
self.tree.Iterate(EncodeUint(start), EncodeUint(end), func(key string, value any) bool {
return fn(DecodeUint(key), value)
})
}

func (self *UintTree) IterateByInt64(start, end int64, fn func(key uint64, value any) bool) {
self.Iterate(SafeConvertInt64ToUint64(start), SafeConvertInt64ToUint64(end), fn)
}

func (self *UintTree) ReverseIterate(start, end uint64, fn func(key uint64, value any) bool) {
self.tree.ReverseIterate(EncodeUint(start), EncodeUint(end), func(key string, value any) bool {
return fn(DecodeUint(key), value)
})
}

func (self *UintTree) ReverseIterateByInt64(start, end int64, fn func(key uint64, value any) bool) {
self.ReverseIterate(SafeConvertInt64ToUint64(start), SafeConvertInt64ToUint64(end), fn)
}

func (self *UintTree) Size() int {
return self.tree.Size()
}

func (self *UintTree) IterateByOffset(offset, count int, fn func(key uint64, value any) bool) {
self.tree.IterateByOffset(offset, count, func(key string, value any) bool {
return fn(DecodeUint(key), value)
})
}

func (self *UintTree) Clone() *UintTree {
if self == nil {
return nil
}

cloned := NewUintTree()
self.tree.Iterate("", "", func(key string, value any) bool {
cloned.tree.Set(key, value)
return false
})

return cloned
}

func SafeConvertInt64ToUint64(value int64) uint64 {
if value < 0 {
panic("value must be non-negative")
}

return uint64(value)
}

func EncodeUint(num uint64) string {
s := strconv.FormatUint(num, 10)
zerosNeeded := 20 - len(s)
return strings.Repeat("0", zerosNeeded) + s
}

func DecodeUint(s string) uint64 {
num, err := strconv.ParseUint(s, 10, 64)
if err != nil {
panic(err)
}
return num
}
50 changes: 50 additions & 0 deletions contract/p/gnoswap/uinttree/tree_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package uinttree

import (
"testing"
)

func TestUintTreeSetAndGetByInt64(t *testing.T) {
tree := NewUintTree()
tree.SetByInt64(7, "value")

value, ok := tree.GetByInt64(7)
if !ok {
t.Fatal("expected key to exist")
}
if value != "value" {
t.Fatalf("expected value, got %v", value)
}
}

func TestUintTreeReverseIterateByInt64(t *testing.T) {
tree := NewUintTree()
tree.SetByInt64(5, "five")
tree.SetByInt64(10, "ten")

var latest string
tree.ReverseIterateByInt64(0, 10, func(_ uint64, value any) bool {
latest = value.(string)
return true
})

if latest != "ten" {
t.Fatalf("expected ten, got %s", latest)
}
}

func TestUintTreeSetByInt64PanicsOnNegativeKey(t *testing.T) {
tree := NewUintTree()

defer func() {
r := recover()
if r == nil {
t.Fatal("expected panic")
}
if r != "value must be non-negative" {
t.Fatalf("unexpected panic: %v", r)
}
}()

tree.SetByInt64(-1, "value")
}
124 changes: 0 additions & 124 deletions contract/r/gnoswap/gov/staker/tree.gno

This file was deleted.

9 changes: 9 additions & 0 deletions contract/r/gnoswap/gov/staker/tree_compat.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package staker

import utree "gno.land/p/gnoswap/uinttree"

type UintTree = utree.UintTree

func NewUintTree() *utree.UintTree {
return utree.NewUintTree()
}
42 changes: 42 additions & 0 deletions contract/r/gnoswap/gov/staker/tree_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package staker

import (
"testing"

uassert "gno.land/p/nt/uassert/v0"
)

func TestUintTreeSetAndGetByInt64(t *testing.T) {
tree := NewUintTree()
tree.SetByInt64(7, "value")

value, ok := tree.GetByInt64(7)
uassert.True(t, ok)
uassert.Equal(t, "value", value)
}

func TestUintTreeReverseIterateByInt64(t *testing.T) {
tree := NewUintTree()
tree.SetByInt64(5, "five")
tree.SetByInt64(10, "ten")

var latest string
tree.ReverseIterateByInt64(0, 10, func(_ uint64, value any) bool {
latest = value.(string)
return true
})

uassert.Equal(t, "ten", latest)
}

func TestUintTreeSetByInt64PanicsOnNegativeKey(t *testing.T) {
tree := NewUintTree()

defer func() {
r := recover()
uassert.NotEqual(t, nil, r)
uassert.Equal(t, "value must be non-negative", r)
}()

tree.SetByInt64(-1, "value")
}
10 changes: 8 additions & 2 deletions contract/r/gnoswap/gov/staker/v1/getter.gno
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ func (gs *govStakerV1) GetTotalDelegationAmountAtSnapshot(snapshotTime int64) (i
return 0, false
}

if snapshotTime < 0 {
panic("snapshotTime must be non-negative")
}
toTimestamp := snapshotTime
if toTimestamp < math.MaxInt64 {
toTimestamp = toTimestamp + 1
Expand All @@ -160,7 +163,7 @@ func (gs *govStakerV1) GetTotalDelegationAmountAtSnapshot(snapshotTime int64) (i
)

// ReverseIterate from 0 to snapshotTime to find the most recent entry at or before snapshotTime
history.ReverseIterate(0, toTimestamp, func(key int64, value any) bool {
history.ReverseIterateByInt64(0, toTimestamp, func(key uint64, value any) bool {
amountInt, ok := value.(int64)
if !ok {
panic(ufmt.Sprintf("invalid amount type: %T", value))
Expand Down Expand Up @@ -200,6 +203,9 @@ func (gs *govStakerV1) GetUserDelegationAmountAtSnapshot(userAddr address, snaps
return 0, false
}

if snapshotTime < 0 {
panic("snapshotTime must be non-negative")
}
toTimestamp := snapshotTime
if toTimestamp < math.MaxInt64 {
toTimestamp = toTimestamp + 1
Expand All @@ -211,7 +217,7 @@ func (gs *govStakerV1) GetUserDelegationAmountAtSnapshot(userAddr address, snaps
)

// ReverseIterate from 0 to snapshotTime to find the most recent entry at or before snapshotTime
userHistory.ReverseIterate(0, toTimestamp, func(key int64, value any) bool {
userHistory.ReverseIterateByInt64(0, toTimestamp, func(key uint64, value any) bool {
amountInt, ok := value.(int64)
if !ok {
panic(ufmt.Sprintf("invalid amount type: %T", value))
Expand Down
Loading
Loading