-
Notifications
You must be signed in to change notification settings - Fork 10
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
base: main
Are you sure you want to change the base?
Changes from all commits
078cd71
e3bd3d4
df7cb71
2a393ea
926b00c
0f83b2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
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]) | ||
} | ||
} | ||
} |
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 | ||
) |
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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} | ||
|
||
func assertIsWhiteListed(address std.Address) { | ||
for _, e := range whitelist { | ||
if e == address.String() { | ||
return | ||
} | ||
} | ||
panic("restricted access") | ||
} |
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) | ||
}() | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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