Skip to content

Commit

Permalink
feat: draft add notable contribution
Browse files Browse the repository at this point in the history
  • Loading branch information
omarsy committed Oct 13, 2024
1 parent 79c5253 commit c76362d
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 0 deletions.
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)
}

0 comments on commit c76362d

Please sign in to comment.