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: draft add notable contribution #1303

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
9 changes: 9 additions & 0 deletions gno/r/notablecontribution/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module gno.land/r/teritori/notablecontribution

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/gnorkle/feeds/static v0.0.0-latest
gno.land/p/demo/gnorkle/gnorkle v0.0.0-latest
gno.land/p/demo/gnorkle/message v0.0.0-latest
gno.land/p/demo/testutils v0.0.0-latest
)
186 changes: 186 additions & 0 deletions gno/r/notablecontribution/public.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package notablecontribution

import (
"std"
"errors"
"gno.land/p/demo/avl"
"gno.land/p/demo/gnorkle/gnorkle"
"gno.land/p/demo/gnorkle/message"
"gno.land/p/demo/gnorkle/feeds/static"
)

type UserType string
type NotableContributionType string

type User struct {
ID string
Type UserType
}

func (u *User) JSON() (string) {
return `{"ID":"` + u.ID + `","Type":"` + string(u.Type) + `"}`
}

func (u *User) String() string {
return u.ID + ":" + string(u.Type)
}

type NotableContribution struct {
ID string
Type NotableContributionType
Description string
URL string
Date string
}

func (n *NotableContribution) JSON() (string) {
return `{"ID":"` + string(n.ID) + `", "Type":"` + string(n.Type) + `","Description":"` + n.Description + `","URL":"` + n.URL + `","Date":"` + n.Date + `"}`
}

type NotableContributions []NotableContribution

func (n *NotableContributions) JSON() string {
var result string
for _, nc := range *n {
result += nc.JSON() + ","
}
return "[" + result[:len(result)-1] + "]"
}

const (
// User types
UserTypeUser UserType = "address"
UserGithub UserType = "github"

// Notable contribution types
NotableContributionTypeCommit NotableContributionType = "commit"
NotableContributionTypeIssue NotableContributionType = "issue"
NotableContributionTypePR NotableContributionType = "pr"
NotableContributionTypeReview NotableContributionType = "review"
NotableContributionTypeComment NotableContributionType = "comment"

verifiedResult string = "OK"
)

var (
postHandler postGnorkleMessageHandler
ownerAddress = std.GetOrigCaller()
userContributions *avl.Tree
oracle *gnorkle.Instance
)

func init() {
oracle = gnorkle.NewInstance()
oracle.AddToWhitelist("", []string{string(ownerAddress)})
userContributions = avl.NewTree()
}

type postGnorkleMessageHandler struct{}

// Handle implements the gnorkle.MessageHandler interface.
func (h postGnorkleMessageHandler) Handle(i *gnorkle.Instance, funcType message.FuncType, feed gnorkle.Feed) error {
if funcType != message.FuncTypeIngest {
return nil
}

result, _, consumable := feed.Value()
if !consumable {
return nil
}

defer oracle.RemoveFeed(feed.ID())

// Couldn't verify; nothing to do.
if result.String != verifiedResult {
return nil
}

feedTasks := feed.Tasks()
if len(feedTasks) != 1 {
return errors.New("expected feed to have exactly one task")
}

task, ok := feedTasks[0].(*verificationTask)
if !ok {
return errors.New("expected task to be of type *verificationTask")
}

contributions := NotableContributions{}
res, ok := userContributions.Get(task.user.String())
if ok {
contributions = res.(NotableContributions)
}

contributions = append(contributions, task.notablecontribution)
userContributions.Set(task.user.String(), contributions)

return nil
}

func RequestNotableContribution(userID string, userType UserType, ntID string, ntType NotableContributionType, description, url, date string) {
user := User{ID: userID, Type: userType}
nt := NotableContribution{
ID: ntID,
Type: ntType, Description: description, URL: url, Date: date}

vt := &verificationTask{
user: user,
notablecontribution: nt,
}
if err := oracle.AddFeeds(
static.NewSingleValueFeed(
vt.ID(),
"string",
vt,
),
); err != nil {
panic(err)
}
}

func GnorkleEntrypoint(message string) string {
result, err := oracle.HandleMessage(message, postHandler)
if err != nil {
panic(err)
}

return result
}

// SetOwner transfers ownership of the contract to the given address.
func SetOwner(owner std.Address) {
if ownerAddress != std.GetOrigCaller() {
panic("only the owner can set a new owner")
}

ownerAddress = owner

// In the context of this contract, the owner is the only one that can
// add new feeds to the oracle.
oracle.ClearWhitelist("")
oracle.AddToWhitelist("", []string{string(ownerAddress)})
}

func GetUserContributions(userID, userType string) NotableContributions {
contributions, ok := userContributions.Get(userID + ":" + userType)
if !ok {
return NotableContributions{}
}
return contributions.(NotableContributions)
}

func Render(path string) string {
result := ``
userContributions.Iterate("", "", func(user string, ctInf interface{}) bool {
contributions := ctInf.(NotableContributions)
result += `"`+ user + `":` + contributions.JSON() + `,`
return true
})

length := len(result)
if length == 0 {
return `{}`
}

return `{` +result[:len(result)-1] + "}"
}
66 changes: 66 additions & 0 deletions gno/r/notablecontribution/public_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package notablecontribution
import (
"std"
"testing"

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

func TestNotableContributionLifecycle(t *testing.T) {
defaultAddress := std.GetOrigCaller()
user1Address := std.Address(testutils.TestAddress("user 1"))

result := GnorkleEntrypoint("request")
if result != "[]" {
t.Fatalf("expected empty request result, got %s", result)
}

RequestNotableContribution("omarsy", UserGithub, "200032", NotableContributionTypeCommit, "", "https://github.com/gnolang/gno/commit/200032", "13/10/2024 15:51:23")

var errMsg string
func() {
defer func() {
if r := recover(); r != nil {
errMsg = r.(error).Error()
}
}()
RequestNotableContribution("omarsy", UserGithub, "200032", NotableContributionTypeCommit, "", "https://github.com/gnolang/gno/commit/200032", "13/10/2024 15:51:23")
}()
if errMsg != "feed already exists" {
t.Fatalf("expected feed already exists, got %s", errMsg)
}

result = GnorkleEntrypoint("request")
expResult := `[{"id":"omarsy:github:200032:commit","type":"0","value_type":"string","tasks":[{"user":{"ID":"omarsy","Type":"github"},"notablecontribution":{"ID":"200032", "Type":"commit","Description":"","URL":"https://github.com/gnolang/gno/commit/200032","Date":"13/10/2024 15:51:23"}}]}]`
if result != expResult {
t.Fatalf("expected request result %s, got %s", expResult, result)
}

// Try to trigger feed ingestion from the non-authorized user.
std.TestSetOrigCaller(user1Address)
func() {
defer func() {
if r := recover(); r != nil {
errMsg = r.(error).Error()
}
}()
GnorkleEntrypoint("ingest,omarsy:github:200032:commit,OK")
}()
if errMsg != "caller not whitelisted" {
t.Fatalf("expected caller not whitelisted, got %s", errMsg)
}
std.TestSetOrigCaller(defaultAddress)

GnorkleEntrypoint("ingest,omarsy:github:200032:commit,OK")

result = GnorkleEntrypoint("request")
if result != `[]` {
t.Fatalf("expected empty request result, got %s", result)
}

result = Render("")
expectedResult := `{"omarsy:github":[{"ID":"200032", "Type":"commit","Description":"","URL":"https://github.com/gnolang/gno/commit/200032","Date":"13/10/2024 15:51:23"}]}`
if result != expectedResult {
t.Fatalf("expected render result %s, got %s", expectedResult, result)
}
}
28 changes: 28 additions & 0 deletions gno/r/notablecontribution/task.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package notablecontribution

import (
"bufio"
"bytes"
)

type verificationTask struct {
user User
notablecontribution NotableContribution
}

// MarshalJSON marshals the task contents to JSON.
func (t *verificationTask) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
w := bufio.NewWriter(buf)

w.Write(
[]byte(`{"user":` + t.user.JSON() + `,"notablecontribution":` + t.notablecontribution.JSON() + `}`),
)

w.Flush()
return buf.Bytes(), nil
}

func (t *verificationTask) ID() string {
return t.user.ID + ":" + string(t.user.Type) + ":" + t.notablecontribution.ID + ":" + string(t.notablecontribution.Type)
}
Loading