Skip to content

Commit

Permalink
Merge branch 'master' into fix/std.Address-validation
Browse files Browse the repository at this point in the history
  • Loading branch information
r3v4s authored Mar 22, 2024
2 parents 5e8e935 + 728f3f2 commit 9fa5ff9
Show file tree
Hide file tree
Showing 24 changed files with 784 additions and 39 deletions.
4 changes: 2 additions & 2 deletions docs/concepts/standard-library/coin.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ Coins in this set are then available for access by specific types of Bankers,
which can manipulate them depending on access rights.

[//]: # (TODO ADD LINK TO Effective GNO)
Read more about coins in the [Effective Gno] section.
Read more about coins in the [Effective Gno](https://docs.gno.land/concepts/effective-gno/#native-tokens) section.

The Coin(s) API can be found in under the `std` package [reference](../../reference/standard-library/std/coin.md).
The Coin(s) API can be found in under the `std` package [reference](../../reference/standard-library/std/coin.md).
13 changes: 10 additions & 3 deletions docs/reference/go-gno-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ id: go-gno-compatibility

# Go - Gno compatibility

## Native keywords
## Reserved keywords

| keyword | support |
|-------------|------------------------|
Expand Down Expand Up @@ -34,7 +34,14 @@ id: go-gno-compatibility

Generics are currently not implemented.

## Native types
Note that Gno does not support shadowing of built-in types.
While the following built-in typecasting assignment would work in Go, this is not supported in Gno.

```go
rune := rune('a')
```

## Builtin types

| type | usage | persistency |
|-----------------------------------------------|------------------------|------------------------------------------------------------|
Expand All @@ -57,7 +64,7 @@ Generics are currently not implemented.

**\*:** depends on `T`/`T1`/`T2`

Additional native types:
Additional builtin types:

| type | comment |
|----------|--------------------------------------------------------------------------------------------|
Expand Down
9 changes: 9 additions & 0 deletions examples/gno.land/p/demo/memeland/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module gno.land/p/demo/memeland

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/ownable v0.0.0-latest
gno.land/p/demo/seqid v0.0.0-latest
gno.land/p/demo/testutils v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
)
228 changes: 228 additions & 0 deletions examples/gno.land/p/demo/memeland/memeland.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package memeland

import (
"sort"
"std"
"strconv"
"strings"
"time"

"gno.land/p/demo/avl"
"gno.land/p/demo/ownable"
"gno.land/p/demo/seqid"
)

type Post struct {
ID string
Data string
Author std.Address
Timestamp time.Time
UpvoteTracker *avl.Tree // address > struct{}{}
}

type Memeland struct {
*ownable.Ownable
Posts []*Post
MemeCounter seqid.ID
}

func NewMemeland() *Memeland {
return &Memeland{
Ownable: ownable.New(),
Posts: make([]*Post, 0),
}
}

// PostMeme - Adds a new post
func (m *Memeland) PostMeme(data string, timestamp int64) string {
if data == "" || timestamp <= 0 {
panic("timestamp or data cannot be empty")
}

// Generate ID
id := m.MemeCounter.Next().String()

newPost := &Post{
ID: id,
Data: data,
Author: std.PrevRealm().Addr(),
Timestamp: time.Unix(timestamp, 0),
UpvoteTracker: avl.NewTree(),
}

m.Posts = append(m.Posts, newPost)
return id
}

func (m *Memeland) Upvote(id string) string {
post := m.getPost(id)
if post == nil {
panic("post with specified ID does not exist")
}

caller := std.PrevRealm().Addr().String()

if _, exists := post.UpvoteTracker.Get(caller); exists {
panic("user has already upvoted this post")
}

post.UpvoteTracker.Set(caller, struct{}{})

return "upvote successful"
}

// GetPostsInRange returns a JSON string of posts within the given timestamp range, supporting pagination
func (m *Memeland) GetPostsInRange(startTimestamp, endTimestamp int64, page, pageSize int, sortBy string) string {
if len(m.Posts) == 0 {
return "[]"
}

if page < 1 {
panic("page count cannot be less than 1")
}

// No empty pages
if pageSize < 1 {
panic("page size cannot be less than 1")
}

// No pages larger than 10
if pageSize > 10 {
panic("page size cannot be larger than 10")
}

var filteredPosts []*Post

start := time.Unix(startTimestamp, 0)
end := time.Unix(endTimestamp, 0)

// Filtering posts
for _, p := range m.Posts {
if !p.Timestamp.Before(start) && !p.Timestamp.After(end) {
filteredPosts = append(filteredPosts, p)
}
}

switch sortBy {
// Sort by upvote descending
case "UPVOTES":
dateSorter := PostSorter{
Posts: filteredPosts,
LessF: func(i, j int) bool {
return filteredPosts[i].UpvoteTracker.Size() > filteredPosts[j].UpvoteTracker.Size()
},
}
sort.Sort(dateSorter)
default:
// Sort by timestamp, beginning with newest
dateSorter := PostSorter{
Posts: filteredPosts,
LessF: func(i, j int) bool {
return filteredPosts[i].Timestamp.After(filteredPosts[j].Timestamp)
},
}
sort.Sort(dateSorter)
}

// Pagination
startIndex := (page - 1) * pageSize
endIndex := startIndex + pageSize

// If page does not contain any posts
if startIndex >= len(filteredPosts) {
return "[]"
}

// If page contains fewer posts than the page size
if endIndex > len(filteredPosts) {
endIndex = len(filteredPosts)
}

// Return JSON representation of paginated and sorted posts
return PostsToJSONString(filteredPosts[startIndex:endIndex])
}

// RemovePost allows the owner to remove a post with a specific ID
func (m *Memeland) RemovePost(id string) string {
if id == "" {
panic("id cannot be empty")
}

if err := m.CallerIsOwner(); err != nil {
panic(err)
}

for i, post := range m.Posts {
if post.ID == id {
m.Posts = append(m.Posts[:i], m.Posts[i+1:]...)
return id
}
}

panic("post with specified id does not exist")
}

// PostsToJSONString converts a slice of Post structs into a JSON string
func PostsToJSONString(posts []*Post) string {
var sb strings.Builder
sb.WriteString("[")

for i, post := range posts {
if i > 0 {
sb.WriteString(",")
}

sb.WriteString(PostToJSONString(post))
}
sb.WriteString("]")

return sb.String()
}

// PostToJSONString returns a Post formatted as a JSON string
func PostToJSONString(post *Post) string {
var sb strings.Builder

sb.WriteString("{")
sb.WriteString(`"id":"` + post.ID + `",`)
sb.WriteString(`"data":"` + escapeString(post.Data) + `",`)
sb.WriteString(`"author":"` + escapeString(post.Author.String()) + `",`)
sb.WriteString(`"timestamp":"` + strconv.Itoa(int(post.Timestamp.Unix())) + `",`)
sb.WriteString(`"upvotes":` + strconv.Itoa(post.UpvoteTracker.Size()))
sb.WriteString("}")

return sb.String()
}

// escapeString escapes quotes in a string for JSON compatibility.
func escapeString(s string) string {
return strings.ReplaceAll(s, `"`, `\"`)
}

func (m *Memeland) getPost(id string) *Post {
for _, p := range m.Posts {
if p.ID == id {
return p
}
}

return nil
}

// PostSorter is a flexible sorter for the *Post slice
type PostSorter struct {
Posts []*Post
LessF func(i, j int) bool
}

func (p PostSorter) Len() int {
return len(p.Posts)
}

func (p PostSorter) Swap(i, j int) {
p.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]
}

func (p PostSorter) Less(i, j int) bool {
return p.LessF(i, j)
}
Loading

0 comments on commit 9fa5ff9

Please sign in to comment.