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

docs: Effective Gno #1000

Merged
merged 56 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
a9359c0
docs: Effective Gno
moul Jul 29, 2023
4ac286a
chore: fixup
moul Nov 14, 2023
6c9c7cb
chore: fixup
moul Nov 14, 2023
47165d3
Merge branch 'master' into dev/moul/effective-gno
moul Dec 6, 2023
9cdde6a
Update effective-gno.md
moul Dec 15, 2023
f7361de
chore: fixup
moul Dec 15, 2023
13818a6
chore: fixup
moul Dec 15, 2023
450d5df
chore: fixup
moul Dec 15, 2023
9a7e0a8
chore: fixup
moul Dec 18, 2023
e00d8cc
chore: fixup
moul Dec 18, 2023
25e10a2
chore: fixup
moul Dec 18, 2023
2726149
chore: fixup
moul Dec 18, 2023
8d59747
chore: fixup
moul Dec 18, 2023
c43218c
chore: fixup
moul Dec 18, 2023
507fa84
chore: fixup
moul Dec 18, 2023
10673fc
chore: fixup
moul Dec 18, 2023
d67cf4e
chore: fixup
moul Dec 18, 2023
e761ebc
Merge branch 'master' into dev/moul/effective-gno
waymobetta Dec 18, 2023
1ce7d81
chore: fixup
moul Dec 19, 2023
c7e305e
Apply suggestions from code review
moul Dec 19, 2023
0d53b38
Update docs/how-to-guides/effective-gno.md
moul Dec 19, 2023
0768dce
chore: fixup
moul Dec 19, 2023
e5d95a2
chore: fixup
moul Dec 19, 2023
c63a170
chore: fixup
moul Dec 19, 2023
989b299
chore: fixup
moul Dec 19, 2023
e1715e4
chore: fixup
moul Dec 20, 2023
b7d1d5a
chore: fixup
moul Dec 20, 2023
fadd38c
chore: fixup
moul Dec 20, 2023
66bf747
chore: fixup
moul Dec 20, 2023
aef37b5
chore: fixup
moul Dec 20, 2023
b5ff694
chore: fixup
moul Dec 20, 2023
fe0a2df
chore: fixup
moul Dec 20, 2023
92be992
chore: fixup
moul Dec 20, 2023
e871e37
chore: fixup
moul Dec 20, 2023
ea55eec
Update docs/how-to-guides/effective-gno.md
moul Dec 21, 2023
3b7fa93
Update docs/how-to-guides/effective-gno.md
moul Dec 21, 2023
c0323f0
Update docs/how-to-guides/effective-gno.md
moul Dec 21, 2023
5f0bcc3
Update docs/how-to-guides/effective-gno.md
moul Dec 21, 2023
78572df
Update docs/how-to-guides/effective-gno.md
moul Dec 26, 2023
96d9574
Update docs/how-to-guides/effective-gno.md
moul Dec 26, 2023
5ff0eb5
Update docs/how-to-guides/effective-gno.md
moul Dec 26, 2023
bc08da1
Update docs/how-to-guides/effective-gno.md
moul Dec 26, 2023
469ad48
Update docs/how-to-guides/effective-gno.md
moul Dec 26, 2023
b5572ca
Update docs/how-to-guides/effective-gno.md
moul Dec 26, 2023
1004314
Update docs/how-to-guides/effective-gno.md
moul Dec 26, 2023
c1df3f6
Update docs/how-to-guides/effective-gno.md
moul Dec 26, 2023
33d806e
Apply suggestions from code review
moul Dec 26, 2023
71b2e76
Apply suggestions from code review
moul Dec 26, 2023
16984a2
chore: use prevrealm
moul Jan 31, 2024
a1d980f
chore: avl.tree / map
moul Jan 31, 2024
8002296
chore: fixup
moul Jan 31, 2024
58ac245
Merge branch 'master' into dev/moul/effective-gno
moul Feb 1, 2024
d03b0d7
Update docs/how-to-guides/effective-gno.md
moul Feb 1, 2024
718b5fc
Update docs/how-to-guides/effective-gno.md
moul Feb 8, 2024
85e94be
Merge branch 'master' into dev/moul/effective-gno
moul Feb 8, 2024
594dde8
Update docs/how-to-guides/effective-gno.md
moul Feb 8, 2024
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
255 changes: 255 additions & 0 deletions docs/how-to-guides/effective-gno.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
---
id: 'effective-gno'
---

# Effective Gno

Welcome to the guide for writing effective Gno code. This document is designed
to help you understand the nuances of Gno and how to use it effectively.

Before we dive in, it's important to note that Gno shares several similarities
with Go. Therefore, if you haven't already, we highly recommend reading
["Effective Go"](https://go.dev/doc/effective_go) as a primer.

## Counter-Intuitive Good Practices

This section highlights some Gno good practices that might seem
counter-intuitive, especially if you're coming from a Go background.

### Embrace Global Variables in Gno

In Gno, using global variables isn't just acceptable - it's encouraged. This is
waymobetta marked this conversation as resolved.
Show resolved Hide resolved
moul marked this conversation as resolved.
Show resolved Hide resolved
because global variables in Gno provide a way to have persisted states
automatically.

In Go, you would typically write your logic and maintain some state in memory.
However, to persist the state and ensure it survives a restart, you would need
to use a store (like a plain file, custom file structure, a database, a
key-value store, an API, etc.).

In contrast, Gno simplifies this process. When you declare global variables in
Gno, the GnoVM automatically persists and restores them as needed between each
run.

However, be mindful not to export your global variables. Doing so would make
Copy link
Contributor

Choose a reason for hiding this comment

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

However, be mindful not to export your global variables. Doing so would make..

I understand this is a simplified example, but to me this message + the corresponding code block implies that were counter public that one would be able to increment it even without a setter function like IncCounter(). And if there is a public setter function like IncCounter present which, as it is currently implemented, would be accessible for all to read/write provided there is no modifier present.

Am I understanding this incorrectly?

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this still true? I think this code path it ends up following if something like that happens. https://github.com/gnolang/gno/blob/master/gnovm/pkg/gnolang/realm.go#L148

Copy link
Member Author

Choose a reason for hiding this comment

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

You're likely correct.

I believe we should examine this more thoroughly, ensuring we assess not just the variable, but also aspects like whether it's a pointer.

them accessible for everyone to read and write.

Here's an ideal pattern to follow:

```go
var counter int

func GetCounter() int {
return counter
}

func IncCounter() {
counter++
}
```

### Embrace Panic in Gno

In Gno, it's important to know when to return an `error` and when to use `panic()`.
Each does something different to your code and data.

When you return an error in Gno, it's like giving back any other piece of data.
moul marked this conversation as resolved.
Show resolved Hide resolved
It tells you something went wrong, but it doesn't stop your code or undo any
changes you made.

But, when you use panic in Gno, it stops your code right away, says it failed,
moul marked this conversation as resolved.
Show resolved Hide resolved
and doesn't save any changes you made. This is safer when you want to stop
waymobetta marked this conversation as resolved.
Show resolved Hide resolved
everything and not save wrong changes.

In general, it's good to use `panic()` in realms. In reusable packages, you can
use either panic or errors, depending on what you need.
moul marked this conversation as resolved.
Show resolved Hide resolved

```go
import "std"

func Foobar() {
caller := std.GetOrigCaller()
if caller != "g1234567890123456789012345678912345678" {
panic("permission denied")
}
// ...
}
```

- TODO: suggest MustXXX and AssertXXX flows in p/.
Copy link
Contributor

Choose a reason for hiding this comment

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

Leftover?

Copy link
Member Author

Choose a reason for hiding this comment

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

The inline to-dos can be fixed now or later. Although some people may dislike them, I personally find them beneficial when merged in this way.

Regarding write permissions, I believe you have the highest level of permissions, possibly because it's my personal fork. However, you still have the following options:

  1. Open a pull request against my pull request.
  2. Utilize GitHub's "suggestion" feature, which can be committed with a single click. This is my preferred method for receiving suggestions, and it also allows your authorship to be respected.


### Understand the importance of `init()`

In Gno, the `init()` function isn't just a function, it's a cornerstone. It's
automatically triggered when a new realm is added onchain, making it a one-time
moul marked this conversation as resolved.
Show resolved Hide resolved
setup tool for the lifetime of a realm.

Unlike Go, where `init()` is used for tasks like setting up database
connections, configuring logging, or initializing global variables every time
you start a program, in Gno, `init()` is executed once per realm's lifetime.
moul marked this conversation as resolved.
Show resolved Hide resolved

In Gno, `init()` primarily serves two purposes:
1. It registers your new realm on a new realm. This is typically done using the
moul marked this conversation as resolved.
Show resolved Hide resolved
registry pattern. This means you import another realm and call a method.
2. It configures the initial state, i.e., global variables.

```go
import "gno.land/r/some/registry"

func init() {
registry.Register("myID", myCallback)
}

func myCallback(a, b string) { /* ... */ }
```

A common use case could be to set the "admin" as the caller uploading the
package.

```go
import (
"std"
"time"
)

var (
created time.Time
admin std.Address
list []string
)

func init() {
created = time.Now()
admin = std.GetOrigCaller()
list = append(list, "foo", "bar")
}
moul marked this conversation as resolved.
Show resolved Hide resolved
```

In essence, `init()` in Gno is your go-to function for setting up and
registering realms. It's a powerful tool that helps keep your realms organized
and properly configured from the get-go.

## Gno Good Practices

### Design Your Realm as a Public API

In Go, all your packages, including your dependencies, are typically treated as
part of your safe zone, similar to a secure perimeter. The boundary is drawn
between your program and the rest of the world, which means you secure the API
itself, potentially with authentication middlewares.

However, in Gno, your package is the public API. It's exposed to the outside
moul marked this conversation as resolved.
Show resolved Hide resolved
world and can be accessed by other realms. Therefore, it's crucial to design
your realm with the same level of care and security considerations as you would
a public API.

One approach is to simulate a secure perimeter within your realm by having
private functions for the logic and then writing your API layer by adding some
front-facing API with authentication. This way, you can control access to your
realm's functionality and ensure that only authorized callers can execute
certain operations.

```go
import "std"

func PublicMethod(nb int) {
caller := std.GetOrigCaller()
privateMethod(caller, nb)
}

func privateMethod(caller std.Address, nb int) { /* ... */ }
```

In this example, `PublicMethod` is a public function that can be called by other
waymobetta marked this conversation as resolved.
Show resolved Hide resolved
realms. It retrieves the caller's address using `std.GetOrigCaller()`, and then
passes it to `privateMethod`, which is a private function that performs the
actual logic. This way, `privateMethod` can only be called from within the
realm, and it can use the caller's address for authentication or authorization
checks.

### Construct "Safe" Objects

A safe object in Gno is an object that is designed to be tamper-proof and
secure. It's created with the intent of preventing unauthorized access and
modifications. This follows the same principle of making a package an API, but
for a Go object that can be directly referenced by other realms.
moul marked this conversation as resolved.
Show resolved Hide resolved

The goal is to create an object which, once instantiated, can be linked and its
pointer can be "stored" by other realms without issue, because it protects its
usage completely.

```go
type MySafeStruct {
counter nb
admin std.Address
}

func NewSafeStruct() *MySafeStruct {
caller := std.GetOrigCaller()
return &MySafeStruct{
counter: 0,
admin: caller,
}
}

func (s *MySafeStruct) Counter() int { return s.counter }
func (s *MySafeStruct) Inc() {
caller := std.GetOrigCaller()
if caller != s.admin {
moul marked this conversation as resolved.
Show resolved Hide resolved
panic("permission denied")
}
s.counter++
}
```

Then, you can register this object in another or several other realms so other
realms can access the object, but still following your own rules.

```go
import "gno.land/r/otherrealm"

func init() {
mySafeObj := NewSafeStruct()
otherrealm.Register(mySafeObject)
}

// then, otherrealm can call the public functions but won't be the "owner" of
moul marked this conversation as resolved.
Show resolved Hide resolved
// the object.
```

## TODO

- Packages vs realms, subpackages, subrealms, internal
- Elaborate on the benefits of global variables
- Discuss the advantages of NPM-style small and focused libraries
- Describe how versioning is different in Gno
- Explain why exporting a variable is unsafe; instead, suggest creating getters and setters that check for permission to update
- Explain how to export an object securely
- Discuss the future of code generation
- Provide examples of unoptimized / gas inefficient code
- Discuss optimized data structures
- Explain the use of state machines
- Share patterns for setting an initial owner
- Discuss Test-Driven Development (TDD)
- write tests efficiently (mixing unit tests, and other tests)
- Suggest shipping related code to aid review
- Encourage writing documentation for users, not just developers
- Discuss the different reasons for exporting/unexporting things
- Introduce the contract contract pattern
- Discuss the upgrade pattern and its future
- Introduce the DAO pattern
- Discuss the use of gno run for customization instead of contracts everywhere
- Suggest using multiple AVL trees as an alternative to SQL indexes
- Discuss the use of r/NAME/home and p/NAME/home/{foo, bar}[/v0-9]
- Provide guidance on when to launch a local testnet, a full node, gnodev, etc.
- Suggest matching the package name with the folder
- Recommend using the demo/ folder for most things
- Suggest keeping package names short and clear
- Discuss VERSIONING
- Suggest using p/ for interfaces and r/ for implementation
- make your contract mixing onchain, unittest, and run, and eventually client.
- std.GetOrigCaller vs std.PrevRealm().Addr(), etc
- go std vs gno std
- use rand
- use time
- use oracles
1 change: 1 addition & 0 deletions misc/docusaurus/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const sidebars = {
'how-to-guides/creating-grc20',
'how-to-guides/creating-grc721',
'how-to-guides/connect-wallet-dapp',
'how-to-guides/effective-gno',
],
},
{
Expand Down
Loading