Skip to content

Commit

Permalink
Merge branch 'master' into enhance-grc20factory
Browse files Browse the repository at this point in the history
  • Loading branch information
linhpn99 authored Sep 4, 2024
2 parents 3d879e4 + 65ee7a5 commit 0d27eed
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 11 deletions.
17 changes: 17 additions & 0 deletions docs/concepts/portal-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,20 @@ has some drawbacks:
Gno will fail to be replayed, meaning **data will be lost**.
- Since transactions are archived and replayed during genesis,
block height & timestamp cannot be relied upon.

### Deploying to the Portal Loop

There are two ways to deploy code to the Portal Loop:

1. *automatic* - all packages in found in the `examples/gno.land/{p,r}/` directory in the [Gno monorepo](https://github.com/gnolang/gno) get added to the
new genesis each cycle,
2. *permissionless* - this includes replayed transactions with `addpkg`, and
new transactions you can issue with `gnokey maketx addpkg`.

Since the packages in `examples/gno.land/{p,r}` are deployed first,
permissionless deployments get superseded when packages with identical `pkgpath`
get merged into `examples/`.

The above mechanism is also how the `examples/` on the Portal Loop
get collaboratively iterated upon, which is its main mission.

7 changes: 7 additions & 0 deletions examples/gno.land/r/demo/games/shifumi/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module gno.land/r/demo/games/shifumi

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/seqid v0.0.0-latest
gno.land/r/demo/users v0.0.0-latest
)
120 changes: 120 additions & 0 deletions examples/gno.land/r/demo/games/shifumi/shifumi.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package shifumi

import (
"errors"
"std"
"strconv"

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

"gno.land/r/demo/users"
)

const (
empty = iota
rock
paper
scissors
last
)

type game struct {
player1, player2 std.Address // shifumi is a 2 players game
move1, move2 int // can be empty, rock, paper, or scissors
}

var games avl.Tree
var id seqid.ID

func (g *game) play(player std.Address, move int) error {
if !(move > empty && move < last) {
return errors.New("invalid move")
}
if player != g.player1 && player != g.player2 {
return errors.New("invalid player")
}
if player == g.player1 && g.move1 == empty {
g.move1 = move
return nil
}
if player == g.player2 && g.move2 == empty {
g.move2 = move
return nil
}
return errors.New("already played")
}

func (g *game) winner() int {
if g.move1 == empty || g.move2 == empty {
return -1
}
if g.move1 == g.move2 {
return 0
}
if g.move1 == rock && g.move2 == scissors ||
g.move1 == paper && g.move2 == rock ||
g.move1 == scissors && g.move2 == paper {
return 1
}
return 2
}

// NewGame creates a new game where player1 is the caller and player2 the argument.
// A new game index is returned.
func NewGame(player std.Address) int {
games.Set(id.Next().String(), &game{player1: std.PrevRealm().Addr(), player2: player})
return int(id)
}

// Play executes a move for the game at index idx, where move can be:
// 1 (rock), 2 (paper), 3 (scissors).
func Play(idx, move int) {
v, ok := games.Get(seqid.ID(idx).String())
if !ok {
panic("game not found")
}
if err := v.(*game).play(std.PrevRealm().Addr(), move); err != nil {
panic(err)
}
}

func Render(path string) string {
mov1 := []string{"", " 🤜 ", " 🫱 ", " 👉 "}
mov2 := []string{"", " 🤛 ", " 🫲 ", " 👈 "}
win := []string{"pending", "draw", "player1", "player2"}

output := `# 👊 ✋ ✌️ Shifumi
Actions:
* [NewGame](shifumi?help&__func=NewGame) opponentAddress
* [Play](shifumi?help&__func=Play) gameIndex move (1=rock, 2=paper, 3=scissors)
game | player1 | | player2 | | win
--- | --- | --- | --- | --- | ---
`
// Output the 100 most recent games.
maxGames := 100
for n := int(id); n > 0 && int(id)-n < maxGames; n-- {
v, ok := games.Get(seqid.ID(n).String())
if !ok {
continue
}
g := v.(*game)
output += strconv.Itoa(n) + " | " +
shortName(g.player1) + " | " + mov1[g.move1] + " | " +
shortName(g.player2) + " | " + mov2[g.move2] + " | " +
win[g.winner()+1] + "\n"
}
return output
}

func shortName(addr std.Address) string {
user := users.GetUserByAddress(addr)
if user != nil {
return user.Name
}
if len(addr) < 10 {
return string(addr)
}
return string(addr)[:10] + "..."
}
65 changes: 65 additions & 0 deletions examples/gno.land/r/leon/config/config.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package config

import (
"errors"
"std"
)

var (
main std.Address // leon's main address
backup std.Address // backup address
)

func init() {
main = "g125em6arxsnj49vx35f0n0z34putv5ty3376fg5"
}

func Address() std.Address {
return main
}

func Backup() std.Address {
return backup
}

func SetAddress(a std.Address) error {
if !a.IsValid() {
return errors.New("config: invalid address")
}

if err := checkAuthorized(); err != nil {
return err
}

main = a
return nil
}

func SetBackup(a std.Address) error {
if !a.IsValid() {
return errors.New("config: invalid address")
}

if err := checkAuthorized(); err != nil {
return err
}

backup = a
return nil
}

func checkAuthorized() error {
caller := std.PrevRealm().Addr()
if caller != main || caller != backup {
return errors.New("config: unauthorized")
}

return nil
}

func AssertAuthorized() {
caller := std.PrevRealm().Addr()
if caller != main || caller != backup {
panic("config: unauthorized")
}
}
1 change: 1 addition & 0 deletions examples/gno.land/r/leon/config/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/r/leon/config
8 changes: 8 additions & 0 deletions examples/gno.land/r/leon/home/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module gno.land/r/leon/home

require (
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/r/demo/art/gnoface v0.0.0-latest
gno.land/r/demo/art/millipede v0.0.0-latest
gno.land/r/leon/config v0.0.0-latest
)
121 changes: 121 additions & 0 deletions examples/gno.land/r/leon/home/home.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package home

import (
"std"
"strconv"

"gno.land/p/demo/ufmt"

"gno.land/r/demo/art/gnoface"
"gno.land/r/demo/art/millipede"
"gno.land/r/leon/config"
)

var (
pfp string // link to profile picture
pfpCaption string // profile picture caption
abtMe [2]string
)

func init() {
pfp = "https://i.imgflip.com/91vskx.jpg"
pfpCaption = "[My favourite painting & pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)"
abtMe = [2]string{
`### About me
Hi, I'm Leon, a DevRel Engineer at gno.land. I am a tech enthusiast,
life-long learner, and sharer of knowledge.`,
`### Contributions
My contributions to gno.land can mainly be found
[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).
TODO import r/gh
`,
}
}

func UpdatePFP(url, caption string) {
config.AssertAuthorized()
pfp = url
pfpCaption = caption
}

func UpdateAboutMe(col1, col2 string) {
config.AssertAuthorized()
abtMe[0] = col1
abtMe[1] = col2
}

func Render(path string) string {
out := "# Leon's Homepage\n\n"

out += renderAboutMe()
out += renderBlogPosts()
out += "\n\n"
out += renderArt()

return out
}

func renderBlogPosts() string {
out := ""
//out += "## Leon's Blog Posts"

// todo fetch blog posts authored by @leohhhn
// and render them
return out
}

func renderAboutMe() string {
out := "<div class='columns-3'>"

out += "<div>\n\n"
out += ufmt.Sprintf("![my profile pic](%s)\n\n%s\n\n", pfp, pfpCaption)
out += "</div>\n\n"

out += "<div>\n\n"
out += abtMe[0] + "\n\n"
out += "</div>\n\n"

out += "<div>\n\n"
out += abtMe[1] + "\n\n"
out += "</div>\n\n"

out += "</div><!-- /columns-3 -->\n\n"

return out
}

func renderArt() string {
out := `<div class="jumbotron">` + "\n\n"
out += "# Gno Art\n\n"

out += "<div class='columns-3'>"

out += renderGnoFace()
out += renderMillipede()
out += "Empty spot :/"

out += "</div><!-- /columns-3 -->\n\n"

out += "This art is dynamic; it will change with every new block.\n\n"
out += `</div><!-- /jumbotron -->` + "\n"

return out
}

func renderGnoFace() string {
out := "<div>\n\n"
out += gnoface.Render(strconv.Itoa(int(std.GetHeight())))
out += "</div>\n\n"

return out
}

func renderMillipede() string {
out := "<div>\n\n"
out += "Millipede\n\n"
out += "```\n" + millipede.Draw(int(std.GetHeight())%10+1) + "```\n"
out += "</div>\n\n"

return out
}
4 changes: 4 additions & 0 deletions gnovm/pkg/gnolang/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ func isReservedName(n Name) bool {

// scans uverse static node for blocknames. (slow)
func isUverseName(n Name) bool {
if n == "panic" {
// panic is not in uverse, as it is parsed as its own statement (PanicStmt)
return true
}
uverseNames := UverseNode().GetBlockNames()
for _, name := range uverseNames {
if name == n {
Expand Down
12 changes: 1 addition & 11 deletions gnovm/pkg/gnolang/uverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -928,17 +928,7 @@ func UverseNode() *PackageNode {
return
},
)
defNative("panic",
Flds( // params
"err", AnyT(), // args[0]
),
nil, // results
func(m *Machine) {
arg0 := m.LastBlock().GetParams1()
xv := arg0.Deref()
panic(xv.Sprint(m))
},
)
// NOTE: panic is its own statement type, and is not defined as a function.
defNative("print",
Flds( // params
"xs", Vrd(AnyT()), // args[0]
Expand Down

0 comments on commit 0d27eed

Please sign in to comment.