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: implement realm for manual rewards #171

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
14 changes: 11 additions & 3 deletions .github/workflows/deploy-realm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: realm
on:
push:
paths:
- "realm/**"
- "r/**"
branches:
- main

Expand All @@ -25,12 +25,20 @@ jobs:
- run: go mod download -x
- run: |
printf '\n\n%s\n\n' "$MNEMONIC" | $GNOKEY add --recover --insecure-password-stdin test1'
cd realm
echo "" | $GNOKEY maketx addpkg \
--gas-wanted 50000000 \
--gas-fee 1ugnot \
--pkgpath gno.land/r/demo/chess_${GITHUB_SHA} \
--pkgdir . \
--pkgdir r/chess \
--insecure-password-stdin \
--remote \
--broadcast test1'

echo "" | $GNOKEY maketx addpkg \
--gas-wanted 50000000 \
--gas-fee 1ugnot \
--pkgpath gno.land/r/demo/reward_entry_${GITHUB_SHA} \
--pkgdir r/reward_entry \
--insecure-password-stdin \
--remote \
--broadcast test1'
4 changes: 2 additions & 2 deletions .github/workflows/realm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: realm
on:
pull_request:
paths:
- "realm/**"
- "r/**"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please rebase and also fix for .github/workflows/deploy-realm.yml

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done -> Can you please verify the changes? -> 0f83b2d

- "Makefile"
- "go.sum"
push:
Expand All @@ -21,4 +21,4 @@ jobs:
with:
go-version: 'stable'
- run: go mod download -x
- run: go run github.com/gnolang/gno/gnovm/cmd/gno test -verbose -run 'Test([^P]|P[^e])' ./realm
- run: go run github.com/gnolang/gno/gnovm/cmd/gno test -verbose -run 'Test([^P]|P[^e])' ./r
4 changes: 2 additions & 2 deletions .github/workflows/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: rules
on:
pull_request:
paths:
- "realm/rules.gno"
- "r/chess/rules.gno"
push:
branches:
- master
Expand All @@ -19,4 +19,4 @@ jobs:
with:
go-version: 'stable'
- run: go mod download -x
- run: go run github.com/gnolang/gno/gnovm/cmd/gno test -verbose -run 'TestPerft' ./realm
- run: go run github.com/gnolang/gno/gnovm/cmd/gno test -verbose -run 'TestPerft' ./r/chess
21 changes: 15 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,22 @@ help: ## Display this help message.
cd web; npm run build
cd web; npm run dev

4_deploy_realm: ## Deploy GnoChess realm on local node.
4_deploy_realms: ## Deploy realms on local node.
echo | $(GNOKEY) maketx addpkg \
--insecure-password-stdin \
--gas-wanted 20000000 \
--gas-fee 1ugnot \
--pkgpath gno.land/r/demo/chess \
--pkgdir ./realm \
--pkgdir ./r/chess \
--broadcast \
DeployKey

echo | $(GNOKEY) maketx addpkg \
--insecure-password-stdin \
--gas-wanted 2000000 \
--gas-fee 1ugnot \
--pkgpath gno.land/r/demo/reward_entry \
--pkgdir ./r/reward_entry \
--broadcast \
DeployKey

Expand All @@ -96,14 +105,14 @@ z_use_remote_gno: ## Use the remote 'github.com/gnolang/gno' module and remove a
@echo "Switching to remote gno module..."
@go mod edit -dropreplace github.com/gnolang/gno

z_test_realm: ## Test the realm.
go run github.com/gnolang/gno/gnovm/cmd/gno test --verbose ./realm
z_test_realms: ## Test the realms.
go run github.com/gnolang/gno/gnovm/cmd/gno test --verbose ./r

z_test_integration: ## Test the realm.
go test -v -run='TestIntegration/.*' .

z_build_realm: ## Precompile and build the generated Go files. Assumes a working clone of gno in ../gno.
z_build_chess_realm: ## Precompile and build the generated Go files. Assumes a working clone of gno in ../gno.
mkdir -p ../gno/examples/gno.land/r/gnochess
cp -rf realm/*.gno ../gno/examples/gno.land/r/gnochess
cp -rf r/chess/*.gno ../gno/examples/gno.land/r/gnochess
go run github.com/gnolang/gno/gnovm/cmd/gno precompile --verbose ../gno/examples/gno.land
go run github.com/gnolang/gno/gnovm/cmd/gno build --verbose ../gno/examples/gno.land/r/gnochess
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ services:
depends_on:
- gnoland
volumes:
- "./realm:/realm"
- "./r:/r"
logging:
driver: "json-file"
options:
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
87 changes: 87 additions & 0 deletions r/reward_entry/entry.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package reward_entry

import (
"sort"
"std"
"time"

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

var entries avl.Tree // address -> *RewardEntry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It prevents a single address from having multiple entries.

We can survive with this condition, but I think it makes sense for such manual entry realm to support multiple entries per address.

Copy link
Contributor Author

@harry-hov harry-hov Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a good idea to have multiple entries for single address.

Btw you can always override points (increase/decrease).

For some reason we want entry for every points earner. we can have something like:

// RewardEntry represents a reward entry
type RewardEntry struct {
	address std.Address
        totalPoints uint64
	points  []*Point

	updatedAt time.Time
	updatedBy std.Address
}

type Point struct {
        points  uint64
	reason  string
}

Considering the time constrains, I suggest we shouldn't make things unnecessarily complex, unless it is need of the hour


// RewardEntry represents a reward entry
type RewardEntry struct {
address std.Address
points uint64
reason string

updatedAt time.Time
updatedBy std.Address
}

func SetRewardEntry(address std.Address, points uint64, reason string) {
std.AssertOriginCall()
caller := std.GetOrigCaller()
assertIsWhiteListed(caller)

entry := &RewardEntry{
address: address,
points: points,
reason: reason,

updatedAt: time.Now(),
updatedBy: caller,
}
entries.Set(address.String(), entry)
}

func rewardEntrySorted() []RewardEntry {
sorted := []RewardEntry{}
entries.Iterate("", "", func(key string, value interface{}) bool {
entry := value.(*RewardEntry)
i := sort.Search(len(sorted), func(i int) bool { return sorted[i].points <= entry.points })
if i > len(sorted) && sorted[i].points == entry.points {
i++
}
sorted = append(sorted, RewardEntry{})
copy(sorted[i+1:], sorted[i:])
sorted[i] = *entry
return false
})

return sorted
}

func Render(path string) string {
switch {
case path == "":
entries := rewardEntrySorted()
return markdown(entries)
default:
return "404\n"
}
}

func markdown(in []RewardEntry) string {
res := "# Reward entries:\n\n"

if len(in) == 0 {
res += "*No reward entry found*\n"
return res
}

// Create a table header
res += "| Address | Points | Reason | Updated-by | Updated-at |\n"
res += "| --------------- | --------- | --------------- | ---------- | ---------- |\n"

// Iterate over reward entries and format them as Markdown rows
for _, entry := range in {
updatedAt := entry.updatedAt.Format(time.UnixDate)
row := ufmt.Sprintf("| %s | %dpoints | %s | %s | %s |\n", entry.address.String(), entry.points, entry.reason, entry.updatedBy.String(), updatedAt)
res += row
}

return res
}
56 changes: 56 additions & 0 deletions r/reward_entry/entry_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package reward_entry

import (
"std"
"strings"
"testing"

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

func TestRewardEntry(t *testing.T) {
// Override whitelist for testing
whitelist = []string{std.Address("address1"), std.Address("address2"), std.Address("address3")}

// Add reward entry for `foo`` and `bar``
std.TestSetOrigCaller(std.Address("address1"))
SetRewardEntry("foo", 1000, "oof")
SetRewardEntry("bar", 1500, "rab")

// `address2` modify foo's points
std.TestSetOrigCaller(std.Address("address2"))
SetRewardEntry("foo", 1200, "oof; 200 more for good handwriting")

// `unauthorized` address tries to modify foo's points
std.TestSetOrigCaller(std.Address("unauthorized"))
func() {
defer func() {
if r := recover(); r == nil {
t.Errorf("expected panic for unauthorized address")
}
}()
SetRewardEntry("foo", 1200, "oof")
}()

// Note: Render() prints entries in sorted order
// (sorted by points; high -> low)
expectedRows := []string{
"# Reward entries:",
"",
"| Address | Points | Reason | Updated-by | Updated-at |",
"| --------------- | --------- | --------------- | ---------- |",
"| bar | 1500points | rab | address1 |",
"| foo | 1200points | oof; 200 more for good handwriting | address2 |",
}

out := Render("")
// Split the actual output into rows
actualRows := strings.Split(out, "\n")

// Check each row one by one
for i, expectedRow := range expectedRows {
if !strings.HasPrefix(actualRows[i], expectedRow) {
t.Errorf("Row %d does not match:\nExpected:\n%s\nGot:\n%s", i+1, expectedRow, actualRows[i])
}
}
}
6 changes: 6 additions & 0 deletions r/reward_entry/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module gno.land/r/demo/reward_entry

require (
"gno.land/p/demo/avl" v0.0.0-latest
"gno.land/p/demo/ufmt" v0.0.0-latest
)
17 changes: 17 additions & 0 deletions r/reward_entry/whitelist.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package reward_entry

import "std"

// XXX: Update as required
var whitelist = []string{
"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
Copy link
Contributor

@moul moul Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we already have an admin address shared with the competition operators? if not we should set it now.

cc @thehowl @zivkovicmilos @albttx

}

func assertIsWhiteListed(address std.Address) {
for _, e := range whitelist {
if e == address.String() {
return
}
}
panic("restricted access")
}
37 changes: 37 additions & 0 deletions r/reward_entry/whitelist_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package reward_entry

import (
"std"
"testing"
)

func TestAssertIsWhiteListed(t *testing.T) {
// Override whitelist for testing
whitelist = []string{std.Address("address1"), std.Address("address2"), std.Address("address3")}

// Test with a valid address
validAddress := std.Address("address1")
assertIsWhiteListed(validAddress) // This should not panic

// Test with an invalid address; should cause a panic
invalidAddress := std.Address("invalid_address")
func() {
defer func() {
if r := recover(); r == nil {
t.Errorf("expected panic for invalid address")
}
}()
assertIsWhiteListed(invalidAddress)
}()

// Test with an empty whitelist; should cause a panic
whitelist = []string{}
func() {
defer func() {
if r := recover(); r == nil {
t.Errorf("expected panic for empty whitelist")
}
}()
assertIsWhiteListed(validAddress)
}()
}
2 changes: 1 addition & 1 deletion tutorial/02_move_validator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ the task of doing, we will let you simply fill in the fun parts...

Open up `rules.gno` -- you should see the rough structure of a move generator
that we've implemented for this workshop. In fact, it is a trimmed down version
of the one we're using for [the full GnoChess dApp](../../realm/rules.gno), but
of the one we're using for [the full GnoChess dApp](../../r/chess/rules.gno), but
let's not get ahead of ourselves.

By scrolling down to the `Position.validateMove` method, you should be able to
Expand Down
2 changes: 1 addition & 1 deletion util/devnet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
networks:
- gnonode
volumes:
- "../../realm:/realm"
- "../../r:/r"
restart: on-failure
logging:
driver: "json-file"
Expand Down