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: initial r/gh realm #1134

Open
wants to merge 5 commits into
base: master
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
64 changes: 64 additions & 0 deletions examples/gno.land/r/gh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# GitHub Realm

**Disclaimer**: This realm is not designed to automatically score or rank pull
requests. Its primary purpose is to provide factual, reliable data from GitHub
to the on-chain environment. Any interpretation or scoring of this data should
be handled by other systems or contracts.

Welcome to the GitHub realm. This suite of contracts is designed to bridge the
gap between GitHub's vast repositories of data and our on-chain environment. The
overarching aim is to translate select GitHub metrics and interactions onto the
blockchain, providing a seamless interface between the two ecosystems.

## Purpose of the Package

The main goals of this package are as follows:

1. **Oracle-Filled Data**: The package will primarily be populated by an oracle
that translates and mirrors select GitHub data onto the chain. This ensures a
reliable and consistent flow of data between GitHub and our on-chain
ecosystem.

2. **User Account Linkage**: Users can establish a connection between their
GitHub accounts and their on-chain identities, strengthening the bond between
off-chain activities and on-chain representations.

3. **Helper Functions for Gno Contract Integration**: A series of helper
functions will be available to convert GitHub objects into Gno objects. This
will enable other contracts to seamlessly utilize GitHub data within their
logic and operations.

## Key Features

### On-Chain Representation of GitHub Metrics

GitHub is a treasure trove of valuable metrics. This package will mirror
essential GitHub data on-chain, like issues, PR statuses, and more. The goal is
to provide an on-chain management system that reflects GitHub's activities,
ensuring the two platforms remain interlinked.

### Bidirectional Data Flow

While our primary focus is to mirror GitHub activities on-chain, the reverse
process is also integral. On-chain activities should be recognizable on GitHub,
allowing for a holistic data flow between the two systems.

### User Account Linkage

To bolster user interaction and maintain a reliable data flow, users can link
their GitHub and on-chain accounts. This bi-directional linkage offers users a
cohesive experience, and broadens our spectrum of interactivity.

### Helper Functions

Developers can tap into a range of helper functions, which can transform GitHub
data into Gno-compatible objects. This aids in integrating GitHub's vast
datasets into other on-chain contracts and logics.

## Conclusion

The GitHub package for Gno is a pioneering step towards integrating off-chain
data sources with on-chain functionalities. As we evolve this package, we remain
committed to maintaining the integrity of data, ensuring fairness, and enhancing
the user experience. Feedback, contributions, and suggestions are always
welcome!
52 changes: 52 additions & 0 deletions examples/gno.land/r/gh/accounts.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package gh

import "errors"

// Account represents a GitHub user account or organization.
type Account struct {
id string
Copy link
Member

Choose a reason for hiding this comment

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

handle?

name string
kind string
}
Comment on lines +5 to +10
Copy link
Member

Choose a reason for hiding this comment

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

Hmm. What about having types User, Organization, and type Handle interface { Name() string; URL() string; assertHandle() }? Or Namespace


func (a Account) ID() string { return a.id }
func (a Account) Name() string { return a.name }
func (a Account) Kind() string { return a.kind }
func (a Account) URL() string { return "https://github.com/" + a.id }
func (a Account) IsUser() bool { return a.kind == UserAccount }
func (a Account) IsOrg() bool { return a.kind == OrgAccount }

// TODO: func (a Account) RepoByID() Repo ...

func (a Account) Validate() error {
if a.id == "" {
return errors.New("empty id")
}
if a.kind == "" {
return errors.New("empty kind")
}
if a.name == "" {
return errors.New("empty name")
}
// TODO: validate
return nil
}

func (a Account) String() string {
// XXX: better idea?
Copy link
Member

Choose a reason for hiding this comment

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

either @handle or markdown representation?

return a.URL()
}

const (
UserAccount string = "user"
OrgAccount string = "org"
)

func AccountByID(id string) *Account {
res, ok := accounts.Get(id)
if !ok {
return nil
}

return res.(*Account)
}
50 changes: 50 additions & 0 deletions examples/gno.land/r/gh/accounts_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package gh

import (
"testing"
)

// Test for the Account struct functions.
func TestAccountFunctions(t *testing.T) {
account := Account{
id: "user123",
name: "John Doe",
kind: "user",
}

t.Run("Test Account ID", func(t *testing.T) {
if account.ID() != "user123" {
t.Fatalf("Expected ID to be user123, got %s", account.ID())
}
})

t.Run("Test Account Name", func(t *testing.T) {
if account.Name() != "John Doe" {
t.Fatalf("Expected Name to be John Doe, got %s", account.Name())
}
})

t.Run("Test Account Kind", func(t *testing.T) {
if account.Kind() != "user" {
t.Fatalf("Expected Kind to be user, got %s", account.Kind())
}
})

t.Run("Test Account URL", func(t *testing.T) {
if account.URL() != "https://github.com/user123" {
t.Fatalf("Expected URL to be https://github.com/user123, got %s", account.URL())
}
})

t.Run("Test Account IsUser", func(t *testing.T) {
if !account.IsUser() {
t.Fatal("Expected IsUser to be true")
}
})

t.Run("Test Account IsOrg", func(t *testing.T) {
if account.IsOrg() {
t.Fatal("Expected IsOrg to be false")
}
})
}
1 change: 1 addition & 0 deletions examples/gno.land/r/gh/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/gh
Copy link
Contributor

Choose a reason for hiding this comment

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

missing require?

Suggested change
module gno.land/r/gh
module gno.land/r/gh
require "gno.land/p/demo/avl" v0.0.0-latest

Copy link
Contributor

Choose a reason for hiding this comment

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

gno lint should complain?

anyways gno mod tidy implementation is complete. CI will start complaining about this soon.

Copy link
Member Author

Choose a reason for hiding this comment

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

@harry-hov: We need a solution to eliminate those comments. The gno mod process should be fully automated, from creation to clarity in CI feedback. Please ensure it's streamlined so you don't have to review this aspect in future PRs. Otherwise, we might just bypass the need for gno.mod and switch to automatic detection.

18 changes: 18 additions & 0 deletions examples/gno.land/r/gh/issues_prs.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gh

/*
// IssueOrPR represents a GitHub issue or pull request
type IssueOrPR struct {
Copy link
Member

Choose a reason for hiding this comment

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

For this, I think we should have type Issue and type PR, which embeds Issue, Issue implements type Referencer interface { Reference() string }, returns "#1134"

(feel free to ignore, I think the comment makes it clear that you want to tackle this in another PR)

ID int
Title string
Body string
Author *Account
Repo *Repo
Type string
}

const (
Issue = "issue"
PR = "pr"
)
*/
102 changes: 102 additions & 0 deletions examples/gno.land/r/gh/oracle.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package gh

import (
"std"
"strings"
"time"

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

var (
accounts avl.Tree // uri -> Account
repos avl.Tree // uri -> Repo
issueOrPRs avl.Tree // uri -> IssueOrPR
Comment on lines +12 to +14
Copy link
Member

Choose a reason for hiding this comment

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

they're not uris

lastUpdateTime time.Time // used by the bot to only upload the diff
oracleAddr std.Address = "g1eunnckcl6r8ncwj0lrpxu9g5062xcvwxqlrf29"
adminAddr std.Address = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" // @manfred
)

func OracleLastUpdated() time.Time { return lastUpdateTime }

func OracleUpsertAccount(id, name, kind string) {
Copy link
Member

Choose a reason for hiding this comment

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

Upsert feels sql-y, when under the hood we just have a k-v. Can we not do Set?

assertIsOracle()
lastUpdateTime = time.Now()

// get or create
account := &Account{}
res, ok := accounts.Get(id)
if ok {
account = res.(*Account)
} else {
account.id = id
}

// update fields
account.name = name
account.kind = kind

if err := account.Validate(); err != nil {
panic(err)
}

// save
accounts.Set(id, account)
}

func OracleUpsertRepo(id string, isPrivate, isFork bool) {
Copy link
Member

Choose a reason for hiding this comment

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

s/id/path/

assertIsOracle()
lastUpdateTime = time.Now()

// get or create
repo := &Repo{}
res, ok := repos.Get(id)
if ok {
repo = res.(*Repo)
} else {
repo.id = id
}

parts := strings.Split(id, "/")
if len(parts) != 2 {
panic("invalid id")
}
ownerID := parts[0]
name := parts[1]
Comment on lines +60 to +65
Copy link
Member

Choose a reason for hiding this comment

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

add util func, de-duplicate from above?


// update fields
repo.name = name
repo.isPrivate = isPrivate
repo.isFork = isFork
repo.owner = AccountByID(ownerID)

if err := repo.Validate(); err != nil {
panic(err)
}

// save
repos.Set(id, repo)
}

func AdminSetOracleAddr(new std.Address) {
assertIsAdmin()
oracleAddr = new
}

// XXX: remove once it will be easy to query private variables' state.
func AdminGetOracleAddr() std.Address { return oracleAddr }

func assertIsAdmin() {
if std.GetOrigCaller() != adminAddr {
panic("restricted area")
}
}

func assertIsOracle() {
if std.GetOrigCaller() != oracleAddr {
panic("restricted area")
}
}

// TODO: could be a great fit for a vector-based/state machine approach, mostly for optimizations
// func OracleApplyVectors(vectors ...)
8 changes: 8 additions & 0 deletions examples/gno.land/r/gh/public.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gh

func LinkAccount(account string, signature string) {
// TODO: verify signature
// TODO: upsert AccountLink
// TODO: generate challenge to be signed and published on gh
panic("not implemented")
}
5 changes: 5 additions & 0 deletions examples/gno.land/r/gh/render.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gh

func Render(path string) string {
panic("not implemented")
}
46 changes: 46 additions & 0 deletions examples/gno.land/r/gh/repos.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package gh

import "errors"

// Repo represents a GitHub repository.
type Repo struct {
id string
owner *Account
name string
isPrivate bool
Copy link
Member

Choose a reason for hiding this comment

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

if it's private, the oracle probably shouldn't know about it, right?

isFork bool
}

func (r Repo) ID() string { return r.id }
func (r Repo) Name() string { return r.name }
func (r Repo) Owner() *Account { return r.owner }
func (r Repo) IsPrivate() bool { return r.isPrivate }
func (r Repo) IsFork() bool { return r.isFork }
func (r Repo) URL() string { return r.owner.URL() + "/" + r.name }

func (r Repo) String() string {
// XXX: better idea?
return r.URL()
}

func (r Repo) Validate() error {
if r.id == "" {
return errors.New("id is empty")
}
if r.name == "" {
return errors.New("name is empty")
}
if r.owner == nil {
return errors.New("owner is nil")
}
return nil
}

func RepoByID(id string) *Repo {
res, ok := repos.Get(id)
if !ok {
return nil
}

return res.(*Repo)
}
41 changes: 41 additions & 0 deletions examples/gno.land/r/gh/repos_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gh

import "testing"

// Test for the Repo struct functions.
func TestRepoFunctions(t *testing.T) {
account := &Account{
id: "org123",
name: "Sample Org",
kind: "org",
}
repo := Repo{
id: "org123/sample-repo",
owner: account,
name: "sample-repo",
}

t.Run("Test Repo ID", func(t *testing.T) {
if repo.ID() != "org123/sample-repo" {
t.Fatalf("Expected ID to be org123/sample-repo, got %s", repo.ID())
}
})

t.Run("Test Repo Name", func(t *testing.T) {
if repo.Name() != "sample-repo" {
t.Fatalf("Expected Name to be sample-repo, got %s", repo.Name())
}
})

t.Run("Test Repo Owner", func(t *testing.T) {
if repo.Owner().ID() != "org123" {
t.Fatalf("Expected Owner ID to be org123, got %s", repo.Owner().ID())
}
})

t.Run("Test Repo URL", func(t *testing.T) {
if repo.URL() != "https://github.com/org123/sample-repo" {
t.Fatalf("Expected URL to be https://github.com/org123/sample-repo, got %s", repo.URL())
}
})
}
Loading
Loading