Skip to content

Commit 17a2b45

Browse files
committed
feat: github oracle
1 parent 4470e0e commit 17a2b45

File tree

10 files changed

+666
-0
lines changed

10 files changed

+666
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
gnokey maketx addpkg \
2+
-deposit="1ugnot" \
3+
-gas-fee="1ugnot" \
4+
-gas-wanted="5000000" \
5+
-broadcast="true" \
6+
-remote="https://rpc.gno.land:443" \
7+
-chainid="portal-loop" \
8+
-pkgdir="." \
9+
-pkgpath="gno.land/r/mikecito/gh_test_4" \
10+
mykey2
11+
12+
gnokey maketx call \
13+
-gas-fee="1ugnot" \
14+
-gas-wanted="5000000" \
15+
-broadcast="true" \
16+
-remote="https://rpc.gno.land:443" \
17+
-chainid="portal-loop" \
18+
-pkgpath="gno.land/r/mikecito/gh_test_4" \
19+
-func="AdminSetOracleAddr" \
20+
-args="g1d46t4el0dduffs5j56t2razaeyvnmkxlduduuw" \
21+
mykey2
22+
23+
gnokey maketx call \
24+
-gas-fee="1ugnot" \
25+
-gas-wanted="5000000" \
26+
-broadcast="true" \
27+
-remote="https://rpc.gno.land:443" \
28+
-chainid="portal-loop" \
29+
-pkgpath="gno.land/r/mikecito/gh_test_4" \
30+
-func="OracleUpsertAccount" \
31+
-args="15034695" \
32+
-args="omarsy" \
33+
-args="6h057" \
34+
-args="user" \
35+
mykey2
36+
37+
gnokey query "vm/qeval" -data='gno.land/r/mikecito/gh_test_4
38+
AccountByID("15034695")' -remote="https://rpc.gno.land:443"
39+
40+
gnokey query "vm/qeval" -data='gno.land/r/mikecito/gh_test_4
41+
RenderAccount("g14mfv59k38r8k5vkevpu0lpqlqra0e9trwp3d32")' -remote="https://rpc.gno.land:443"
42+
43+
gnokey query "vm/qeval" -data='gno.land/r/mikecito/gh_test_4
44+
Render()' -remote="https://rpc.gno.land:443"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package gh
2+
3+
import (
4+
"errors"
5+
"std"
6+
7+
"gno.land/p/demo/avl"
8+
)
9+
10+
// Account represents a GitHub user account or organization.
11+
type Account struct {
12+
id string
13+
login string
14+
name string
15+
kind string
16+
}
17+
18+
func (a Account) ID() string { return a.id }
19+
func (a Account) Name() string { return a.name }
20+
func (a Account) Kind() string { return a.kind }
21+
func (a Account) URL() string { return "https://github.com/" + a.login }
22+
func (a Account) IsUser() bool { return a.kind == UserAccount }
23+
func (a Account) IsOrg() bool { return a.kind == OrgAccount }
24+
25+
// TODO: func (a Account) RepoByID() Repo ...
26+
27+
func (a Account) Validate() error {
28+
if a.id == "" {
29+
return errors.New("empty id")
30+
}
31+
32+
if a.login == "" {
33+
return errors.New("empty login")
34+
}
35+
if a.kind == "" || (a.kind != UserAccount && a.kind != OrgAccount) {
36+
return errors.New("empty kind")
37+
}
38+
if a.name == "" {
39+
return errors.New("empty name")
40+
}
41+
// TODO: validate
42+
return nil
43+
}
44+
45+
func (a Account) Render() string {
46+
return `{ "id": "` + a.id + `", "login": "` + a.login + `", "name": "` + a.name + `", "kind": "` + a.kind + `" }`
47+
}
48+
49+
func (a Account) String() string {
50+
// XXX: better idea?
51+
return a.URL()
52+
}
53+
54+
const (
55+
UserAccount string = "user"
56+
OrgAccount string = "org"
57+
)
58+
59+
func AccountByID(id string) *Account {
60+
res, ok := accounts.Get(id)
61+
if !ok {
62+
return nil
63+
}
64+
65+
return res.(*Account)
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package gh
2+
3+
import (
4+
"std"
5+
)
6+
7+
var(
8+
adminAddr std.Address = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq"
9+
)
10+
11+
12+
func assertIsAdmin() {
13+
if std.GetOrigCaller() != adminAddr {
14+
panic("restricted area")
15+
}
16+
}
17+
18+
func setAdminAddress(address std.Address) {
19+
adminAddr = address
20+
}
21+
22+
func SetAdminAddress(address std.Address) {
23+
assertIsAdmin()
24+
setAdminAddress(address)
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package gh
2+
3+
import (
4+
"std"
5+
"testing"
6+
7+
"gno.land/p/demo/avl"
8+
"gno.land/p/demo/testutils"
9+
)
10+
11+
func TestAssertIsAdmin(t *testing.T) {
12+
adminAddr = "g1test1234"
13+
randomuser := testutils.TestAddress("g1unknown_player")
14+
defer func() {
15+
if r := recover(); r != nil {
16+
}
17+
}()
18+
std.TestSetOrigCaller(randomuser)
19+
assertIsAdmin()
20+
t.Fatalf("should fail because not admin")
21+
}
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module gno.land/r/demo/teritori/gh
2+
3+
require (
4+
gno.land/p/demo/avl v0.0.0-latest
5+
)
6+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package gh
2+
3+
import (
4+
"std"
5+
"strings"
6+
"time"
7+
8+
"gno.land/p/demo/avl"
9+
)
10+
11+
var (
12+
accounts avl.Tree // id -> Account
13+
lastUpdateTime time.Time // used by the bot to only upload the diff
14+
oracleAddr std.Address
15+
)
16+
17+
func OracleLastUpdated() time.Time { return lastUpdateTime }
18+
19+
func OracleUpsertAccount(id, login, name, kind string) {
20+
assertIsOracle()
21+
lastUpdateTime = time.Now()
22+
23+
// get or create
24+
account := &Account{}
25+
res, ok := accounts.Get(id)
26+
if ok {
27+
account = res.(*Account)
28+
} else {
29+
account.id = id
30+
}
31+
32+
// update fields
33+
account.name = name
34+
account.kind = kind
35+
account.login = login
36+
37+
if err := account.Validate(); err != nil {
38+
panic(err)
39+
}
40+
41+
// save
42+
accounts.Set(id, account)
43+
}
44+
45+
func AdminSetOracleAddr(new std.Address) {
46+
assertIsAdmin()
47+
oracleAddr = new
48+
}
49+
50+
func assertIsOracle() {
51+
if std.GetOrigCaller() != oracleAddr {
52+
panic("restricted area")
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package gh
2+
3+
import (
4+
"std"
5+
"testing"
6+
"time"
7+
8+
"gno.land/p/demo/avl"
9+
"gno.land/p/demo/testutils"
10+
)
11+
12+
func TestOracleUpsertUserIsNotOracle(t *testing.T) {
13+
oracleAddr = "g1test1234"
14+
var randomuser = "g1unknown_player"
15+
user := testutils.TestAddress("user")
16+
defer func() {
17+
if r := recover(); r != nil {
18+
}
19+
}()
20+
OracleUpsertAccount("acountID", "villaquiranm","john doe","user")
21+
t.Fatalf("should fail because not admin")
22+
}
23+
24+
func TestOracleUpsertUserOk(t *testing.T) {
25+
oracleAddr = "g1random"
26+
if accounts.Size() != 0 {
27+
t.Fatalf("Accounts is not empty")
28+
}
29+
now := time.Now()
30+
31+
std.TestSetOrigCaller(oracleAddr)
32+
33+
var randomuser = "g1unknown_player"
34+
OracleUpsertAccount("acountID", "villaquiranm","john doe","user")
35+
36+
if accounts.Size() != 1 {
37+
t.Fatalf("User was not created")
38+
}
39+
40+
OracleUpsertAccount("acountID", "villaquiranm","john doe","user")
41+
42+
if accounts.Size() != 1 {
43+
t.Fatalf("User was created more than once")
44+
}
45+
46+
if OracleLastUpdated().Unix() < now.Unix() {
47+
t.Fatalf("OracleLastUpdated was not changed")
48+
}
49+
}
50+
51+
func TestAssertIsOracle(t *testing.T) {
52+
std.TestSetOrigCaller(adminAddr)
53+
AdminSetOracleAddr("g1random123")
54+
defer func() {
55+
if r := recover(); r != nil {
56+
}
57+
}()
58+
assertIsOracle()
59+
t.Fatalf("should fail because user is not oracle")
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package gh
2+
3+
import (
4+
"crypto/ed25519"
5+
"encoding/hex"
6+
"std"
7+
8+
"gno.land/p/demo/avl"
9+
)
10+
11+
var (
12+
addressToAccount avl.Tree // address -> Account
13+
guardianPublicKey string // guardian public (maybe should an array)
14+
)
15+
16+
func init() {
17+
addressToAccount = avl.Tree{}
18+
setAdminAddress(std.GetOrigCaller())
19+
}
20+
21+
// Todo maybe we should gave multi guardian
22+
func SetGuardianPublicKey(publicKey string) {
23+
assertIsAdmin()
24+
guardianPublicKey = publicKey
25+
}
26+
27+
func LinkAccount(accountID string, address std.Address, signature string) {
28+
if !verifySignature(accountID, address, signature) {
29+
panic("signature verification failed")
30+
}
31+
32+
account := AccountByID(accountID)
33+
if account == nil {
34+
panic("account not found")
35+
}
36+
37+
addressToAccount.Set(address.String(), account)
38+
}
39+
40+
func RenderAccount(address std.Address) string {
41+
account, ok := addressToAccount.Get(address.String())
42+
if !ok {
43+
panic("account not found")
44+
}
45+
46+
return account.(*Account).Render()
47+
}
48+
49+
func Render(address string) string {
50+
if address != "" {
51+
return RenderAccount(std.Address(address))
52+
}
53+
str := "["
54+
addressToAccount.Iterate("", "", func(key string, value interface{}) bool {
55+
account := value.(*Account)
56+
str += account.Render()
57+
str += ","
58+
return false
59+
})
60+
str += "]"
61+
62+
return str
63+
}
64+
65+
func verifySignature(accountID string, address std.Address, signature string) bool {
66+
publicKey, err := hex.DecodeString(guardianPublicKey)
67+
if err != nil {
68+
panic("invalid guardian public key")
69+
}
70+
71+
sign, err := hex.DecodeString(signature)
72+
if err != nil {
73+
panic("invalid signature")
74+
}
75+
return ed25519.Verify(publicKey, []byte(accountID+" "+address.String()), sign)
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package gh
2+
3+
import (
4+
"std"
5+
"testing"
6+
7+
"gno.land/p/demo/avl"
8+
"gno.land/p/demo/testutils"
9+
)
10+
11+
// private key 023742D9CFA2824BCA670868E11B00125625F9AF735CA42D9305ADD030F84515
12+
func TestLinkAccount(t *testing.T) {
13+
adminAddr = "g1test1234"
14+
15+
user := testutils.TestAddress("user")
16+
admin := testutils.TestAddress("admin")
17+
18+
std.TestSetOrigCaller(admin)
19+
setAdminAddress(admin)
20+
AdminSetOracleAddr(admin)
21+
SetGuardianPublicKey("E921E29BC2F358F60B5F4C31999355309C6CCEB38204D10117557A39B5F59762")
22+
23+
OracleUpsertAccount("123", "user", "user", UserAccount)
24+
LinkAccount("123", user, "C78A534203008326ACA3C9D3D4338F81B9547002789B79CCAC3514784B780B078126B7AB705954C5CB36A5B171B97B0501EDE74BFDACE3D3096AA0D65B816007")
25+
accountInPublic, ok := addressToAccount.Get(user.String())
26+
if !ok {
27+
t.Fatalf("account not found")
28+
}
29+
account := AccountByID("123")
30+
if accountInPublic != account {
31+
t.Fatalf("account is not same")
32+
}
33+
}

0 commit comments

Comments
 (0)