-
Notifications
You must be signed in to change notification settings - Fork 375
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): add two new upgrade examples + refactor existing ones (…
…#2334) Depends on #2342 cc @jeronimoalbi --------- Signed-off-by: moul <94029+moul@users.noreply.github.com> Co-authored-by: Jerónimo Albi <jeronimo.albi@gmail.com> Co-authored-by: Morgan <morgan@morganbaz.com>
- Loading branch information
1 parent
3eaf449
commit 8865638
Showing
20 changed files
with
477 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module gno.land/p/demo/nestedpkg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Package nestedpkg provides helpers for package-path based access control. | ||
// It is useful for upgrade patterns relying on namespaces. | ||
package nestedpkg | ||
|
||
// To test this from a realm and have std.CurrentRealm/PrevRealm work correctly, | ||
// this file is tested from gno.land/r/demo/tests/nestedpkg_test.gno | ||
// XXX: move test to ths directory once we support testing a package and | ||
// specifying values for both PrevRealm and CurrentRealm. | ||
|
||
import ( | ||
"std" | ||
"strings" | ||
) | ||
|
||
// IsCallerSubPath checks if the caller realm is located in a subfolder of the current realm. | ||
func IsCallerSubPath() bool { | ||
var ( | ||
cur = std.CurrentRealm().PkgPath() + "/" | ||
prev = std.PrevRealm().PkgPath() + "/" | ||
) | ||
return strings.HasPrefix(prev, cur) | ||
} | ||
|
||
// AssertCallerIsSubPath panics if IsCallerSubPath returns false. | ||
func AssertCallerIsSubPath() { | ||
var ( | ||
cur = std.CurrentRealm().PkgPath() + "/" | ||
prev = std.PrevRealm().PkgPath() + "/" | ||
) | ||
if !strings.HasPrefix(prev, cur) { | ||
panic("call restricted to nested packages. current realm is " + cur + ", previous realm is " + prev) | ||
} | ||
} | ||
|
||
// IsCallerParentPath checks if the caller realm is located in a parent location of the current realm. | ||
func IsCallerParentPath() bool { | ||
var ( | ||
cur = std.CurrentRealm().PkgPath() + "/" | ||
prev = std.PrevRealm().PkgPath() + "/" | ||
) | ||
return strings.HasPrefix(cur, prev) | ||
} | ||
|
||
// AssertCallerIsParentPath panics if IsCallerParentPath returns false. | ||
func AssertCallerIsParentPath() { | ||
var ( | ||
cur = std.CurrentRealm().PkgPath() + "/" | ||
prev = std.PrevRealm().PkgPath() + "/" | ||
) | ||
if !strings.HasPrefix(cur, prev) { | ||
panic("call restricted to parent packages. current realm is " + cur + ", previous realm is " + prev) | ||
} | ||
} | ||
|
||
// IsSameNamespace checks if the caller realm and the current realm are in the same namespace. | ||
func IsSameNamespace() bool { | ||
var ( | ||
cur = nsFromPath(std.CurrentRealm().PkgPath()) + "/" | ||
prev = nsFromPath(std.PrevRealm().PkgPath()) + "/" | ||
) | ||
return cur == prev | ||
} | ||
|
||
// AssertIsSameNamespace panics if IsSameNamespace returns false. | ||
func AssertIsSameNamespace() { | ||
var ( | ||
cur = nsFromPath(std.CurrentRealm().PkgPath()) + "/" | ||
prev = nsFromPath(std.PrevRealm().PkgPath()) + "/" | ||
) | ||
if cur != prev { | ||
panic("call restricted to packages from the same namespace. current realm is " + cur + ", previous realm is " + prev) | ||
} | ||
} | ||
|
||
// nsFromPath extracts the namespace from a package path. | ||
func nsFromPath(pkgpath string) string { | ||
parts := strings.Split(pkgpath, "/") | ||
|
||
// Specifically for gno.land, potential paths are in the form of DOMAIN/r/NAMESPACE/... | ||
// XXX: Consider extra checks. | ||
// XXX: Support non gno.land domains, where p/ and r/ won't be enforced. | ||
if len(parts) >= 3 { | ||
return parts[2] | ||
} | ||
return "" | ||
} | ||
|
||
// XXX: Consider adding IsCallerDirectlySubPath | ||
// XXX: Consider adding IsCallerDirectlyParentPath |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
module gno.land/r/demo/tests | ||
|
||
require gno.land/r/demo/tests/subtests v0.0.0-latest | ||
require ( | ||
gno.land/p/demo/nestedpkg v0.0.0-latest | ||
gno.land/r/demo/tests/subtests v0.0.0-latest | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package tests | ||
|
||
import ( | ||
"std" | ||
"testing" | ||
) | ||
|
||
func TestNestedPkg(t *testing.T) { | ||
// direct child | ||
cur := "gno.land/r/demo/tests/foo" | ||
std.TestSetRealm(std.NewCodeRealm(cur)) | ||
if !IsCallerSubPath() { | ||
t.Errorf(cur + " should be a sub path") | ||
} | ||
if IsCallerParentPath() { | ||
t.Errorf(cur + " should not be a parent path") | ||
} | ||
if !HasCallerSameNamespace() { | ||
t.Errorf(cur + " should be from the same namespace") | ||
} | ||
|
||
// grand-grand-child | ||
cur = "gno.land/r/demo/tests/foo/bar/baz" | ||
std.TestSetRealm(std.NewCodeRealm(cur)) | ||
if !IsCallerSubPath() { | ||
t.Errorf(cur + " should be a sub path") | ||
} | ||
if IsCallerParentPath() { | ||
t.Errorf(cur + " should not be a parent path") | ||
} | ||
if !HasCallerSameNamespace() { | ||
t.Errorf(cur + " should be from the same namespace") | ||
} | ||
|
||
// direct parent | ||
cur = "gno.land/r/demo" | ||
std.TestSetRealm(std.NewCodeRealm(cur)) | ||
if IsCallerSubPath() { | ||
t.Errorf(cur + " should not be a sub path") | ||
} | ||
if !IsCallerParentPath() { | ||
t.Errorf(cur + " should be a parent path") | ||
} | ||
if !HasCallerSameNamespace() { | ||
t.Errorf(cur + " should be from the same namespace") | ||
} | ||
|
||
// fake parent (prefix) | ||
cur = "gno.land/r/dem" | ||
std.TestSetRealm(std.NewCodeRealm(cur)) | ||
if IsCallerSubPath() { | ||
t.Errorf(cur + " should not be a sub path") | ||
} | ||
if IsCallerParentPath() { | ||
t.Errorf(cur + " should not be a parent path") | ||
} | ||
if HasCallerSameNamespace() { | ||
t.Errorf(cur + " should not be from the same namespace") | ||
} | ||
|
||
// different namespace | ||
cur = "gno.land/r/foo" | ||
std.TestSetRealm(std.NewCodeRealm(cur)) | ||
if IsCallerSubPath() { | ||
t.Errorf(cur + " should not be a sub path") | ||
} | ||
if IsCallerParentPath() { | ||
t.Errorf(cur + " should not be a parent path") | ||
} | ||
if HasCallerSameNamespace() { | ||
t.Errorf(cur + " should not be from the same namespace") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,40 @@ | ||
Various upgrade pattern explorations. | ||
# Various upgrade pattern explorations | ||
|
||
This repository explores different upgrade patterns for Gno smart contracts. | ||
|
||
## `upgrade_a` | ||
|
||
- Versions are independent. | ||
- Versions are not pausable; users can interact with them independently. | ||
- New versions wrap the previous one (can be recursive) to extend the logic and optionally the storage. | ||
- There is no consistency between versions; updating a version will impact the more recent ones but won't affect the older ones. | ||
- Users and contracts interacting with non-latest versions won't have the latest state. | ||
|
||
## `upgrade_b` | ||
|
||
- Versions include a `SetNextVersion` function which pauses the current implementation and invites users interacting with a deprecated version to switch to the most recent one. | ||
- Since only one version can be used at a time, the latest version can safely recycle the previous version's state in read-only mode. | ||
- These logics can be applied recursively. | ||
- Users and contracts interacting with non-latest versions will switch to a more restricted version (read-only). | ||
|
||
## `upgrade_c` | ||
|
||
- `root` is the storage contract with simple logic. | ||
- Versions implement the logic and rely on `root` to manage the state. | ||
- In the current example, only one version can write to `root` (the latest); in practice, it could be possible to support various logics concurrently relying on `root` for storage. | ||
|
||
## `upgrade_d` -- "Lazy Migration" | ||
|
||
- Demonstrates lazy migrations from v1 to v2 of a data structure in Gno. | ||
- Uses AVL trees, but storage can vary since public `Get` functions are used. | ||
- v1 can be made pausable and read-only during migration. | ||
|
||
## `upgrade_e` | ||
|
||
- `home` is the front-facing contract, focusing on exposing a consistent API to users. | ||
- Versions implement an interface that `home` looks for and self-register themselves, which instantly makes `home` use the new logic implementation for ongoing calls. | ||
|
||
## `upgrade_f` | ||
|
||
- Similar to `upgrade_e`. | ||
- Replaces self-registration with manual registration by an admin. |
2 changes: 1 addition & 1 deletion
2
...e_patterns/upgrade_a/integration_test.gno → ...tterns/upgrade_a/integration_filetest.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
package upgradea | ||
package main | ||
|
||
import ( | ||
v1 "gno.land/r/x/manfred_upgrade_patterns/upgrade_a/v1" | ||
|
53 changes: 53 additions & 0 deletions
53
examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/integration_filetest.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package main | ||
|
||
import ( | ||
"gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root" | ||
"gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1" | ||
"gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2" | ||
) | ||
|
||
func main() { | ||
println("# v1 impl") | ||
println("root.Get()", root.Get()) | ||
println("v1.Get()", v1.Get()) | ||
println("v1.Inc()", v1.Inc()) | ||
println("v1.Inc()", v1.Inc()) | ||
println("v1.Inc()", v1.Inc()) | ||
println("v1.Get()", v1.Get()) | ||
println() | ||
|
||
println("# v2 impl") | ||
root.SetCurrentImpl("gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v2") | ||
println("v2.Get()", v2.Get()) | ||
println("v2.Inc()", v2.Inc()) | ||
println("v2.Inc()", v2.Inc()) | ||
println("v2.Inc()", v2.Inc()) | ||
println("v2.Get()", v2.Get()) | ||
println() | ||
|
||
println("# getters") | ||
println("root.Get()", root.Get()) | ||
println("v1.Get()", v1.Get()) | ||
println("v2.Get()", v2.Get()) | ||
} | ||
|
||
// Output: | ||
// # v1 impl | ||
// root.Get() 0 | ||
// v1.Get() 0 | ||
// v1.Inc() 1 | ||
// v1.Inc() 2 | ||
// v1.Inc() 3 | ||
// v1.Get() 3 | ||
// | ||
// # v2 impl | ||
// v2.Get() 6 | ||
// v2.Inc() 1003 | ||
// v2.Inc() 2003 | ||
// v2.Inc() 3003 | ||
// v2.Get() 6006 | ||
// | ||
// # getters | ||
// root.Get() 3003 | ||
// v1.Get() 3003 | ||
// v2.Get() 6006 |
28 changes: 20 additions & 8 deletions
28
examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_c/root/root.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,33 @@ | ||
package root | ||
|
||
import "std" | ||
|
||
var ( | ||
counter int | ||
currentImplementation = "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1" | ||
counter int | ||
currentImpl = "gno.land/r/x/manfred_upgrade_patterns/upgrade_c/v1" | ||
) | ||
|
||
func Inc() int { | ||
// TODO: if caller is currentImplementation | ||
counter++ | ||
func Inc(nb int) int { | ||
assertIsCurrentImpl() | ||
counter += nb | ||
return counter | ||
} | ||
|
||
func Get() int { | ||
return counter | ||
} | ||
|
||
func UpdateCurrentImplementation(address string) { | ||
// TODO: if is admin | ||
currentImplementation = address | ||
func SetCurrentImpl(pkgpath string) { | ||
assertIsAdmin() | ||
currentImpl = pkgpath | ||
} | ||
|
||
func assertIsCurrentImpl() { | ||
if std.PrevRealm().PkgPath() != currentImpl { | ||
panic("unauthorized") | ||
} | ||
} | ||
|
||
func assertIsAdmin() { | ||
// TODO | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 0 additions & 9 deletions
9
examples/gno.land/r/x/manfred_upgrade_patterns/upgrade_d/README.md
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.