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: add std.CreatePackage #715

Closed
wants to merge 3 commits into from
Closed
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
8 changes: 8 additions & 0 deletions gnovm/pkg/gnolang/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"regexp"
"strconv"
"strings"
"unicode"

"github.com/gnolang/gno/tm2/pkg/errors"
"github.com/gnolang/gno/tm2/pkg/std"
Expand Down Expand Up @@ -109,6 +110,13 @@ const (

type Name string

// Returns if the name is an exported identifier,
// according to the golang's canonical exported name rule,
// namely, starting with a capitalized alphabet.
func (name Name) IsExportedName() bool {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
func (name Name) IsExportedName() bool {
func (name Name) IsExported() bool {

return unicode.IsUpper([]rune(name)[0])
}

// ----------------------------------------
// Location
// Acts as an identifier for nodes.
Expand Down
46 changes: 46 additions & 0 deletions gnovm/pkg/gnolang/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,21 @@ func (pv *PackageValue) GetPkgAddr() crypto.Address {
return DerivePkgAddr(pv.PkgPath)
}

func (pv *PackageValue) GetExportedFunctions(store Store) []*FuncDecl {
res := []*FuncDecl{}
for _, file := range pv.GetPackageNode(store).FileSet.Files {
for _, decl := range file.Decls {
if fdecl, ok := decl.(*FuncDecl); ok {
if !fdecl.NameExpr.Name.IsExportedName() {
continue
}
res = append(res, fdecl)
}
}
}
return res
}

// ----------------------------------------
// NativeValue

Expand Down Expand Up @@ -1521,6 +1536,37 @@ func (tv *TypedValue) ComputeMapKey(store Store, omitType bool) MapKey {
return MapKey(bz)
}

// - PrimitiveValue is moveable across realm boundary.
// - Array and Struct are moveable across realm only if they are composed of moveable types(recursively applied). The value will be copied to the callee realm.
// - Slice are moveable across realm only if they are referring to moveable types(recursively applied)(not implemented yet).
// - Any other cases require some complex inter-realm object management scheme, low priority.
func (tv *TypedValue) IsMoveableAcrossRealm() bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a misnomer because there is already a notion of what is passable between realms as already implemented... that is, one can already call a realm function from another realm, passing in objects, as long as objects aren't sharing persistence. What is implemented here conflicts with that.

I think what you maybe want is IsMoveableAcrossChains or IsIBCArgument, and for that for now I think it should be limited to a list of stringified primitives only, no structs;

and then we need to have a whole series of conversations to evolve that into something that either supports the EVM ABI, or some other interop standard.

switch tv.T.Kind() {
case
BoolKind, StringKind,
IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind,
UintKind, Uint8Kind, Uint16Kind, Uint32Kind, Uint64Kind,
BigintKind, BigdecKind:
return true
case StructKind:
for _, field := range tv.V.(*StructValue).Fields {
if !field.IsMoveableAcrossRealm() {
return false
}
}
return true
case ArrayKind:
for _, field := range tv.V.(*ArrayValue).List {
if !field.IsMoveableAcrossRealm() {
return false
}
}
return true
default:
return false
}
}

// ----------------------------------------
// Value utility/manipulation functions.

Expand Down
165 changes: 165 additions & 0 deletions gnovm/stdlibs/stdlibs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package stdlibs

import (
"crypto/sha256"
"fmt"
"math"
"os"
"reflect"
"strconv"
"strings"
"time"
"unicode"

gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/tm2/pkg/bech32"
Expand Down Expand Up @@ -482,6 +486,167 @@ func InjectPackage(store gno.Store, pn *gno.PackageNode) {
res0.T = addrT
m.PushValue(res0)
},
) /*
pn.DefineNative("LoadPackage",
gno.Flds(
"pkgPath", "string",
),
gno.Flds(
"pkg", gno.AnyT(),
),
func (m *gno.Machine) {
arg0 := m.LastBlock().GetParam1()
pkgPath := arg0.TV.GetString()
fdecls := m.Store.GetPackage(pkgPath).GetExportedFunctions(m.Store)

fields := []*gno.FieldType{}

for _, fdecl := range fdecls {
field := gno.FieldType{
Name: fdecl.Name,

}
}

res0 := gno.Go2GnoValue(
m.Alloc,
m.Store,
reflect.ValueOf(pkg),
)
}
)*/
pn.DefineNative("CallPackage1",
gno.Flds(
"pkgPath", "string",
"functionName", "string",
"args", &gno.SliceTypeExpr{Elt: gno.AnyT(), Vrd: true},
),
gno.Flds(
"returnValue1", gno.AnyT(),
),
func(m *gno.Machine) {
arg0, arg1, arg2 := m.LastBlock().GetParams3()
pkgPath := arg0.TV.GetString()
functionName := arg1.TV.GetString()
if !unicode.IsUpper([]rune(functionName)[0]) {
panic("functionName is not exported")
}
args := arg2.TV.V.(*gno.SliceValue).GetBase(m.Store).List

pv := m.Store.GetPackage(pkgPath, false)
pl := gno.PackageNodeLocation(pkgPath)
pn := store.GetBlockNode(pl).(*gno.PackageNode)
ft := pn.GetStaticTypeOf(store, gno.Name(functionName)).(*gno.FuncType)

mpn := gno.NewPackageNode("main", "main", nil)
mpn.Define("pkg", gno.TypedValue{T: &gno.PackageType{}, V: pv})
mpv := mpn.NewPackage()

// The new environment should be able to access the provided arguments, but not any other caller realm components(variables etc.).
// We achieve this by:
// - Use a fresh gno.Machine for executing a function from dynamically imported package
// - Limiting the set of moveable arguments by checking arg.IsMoveableAcrossRealm(), making sure there is no inter-realm reference.
// - Eval() the CallExpr using the arguments above.

if len(ft.Params) != len(args) {
panic("Argument length does not match")
}

argexprs := make([]interface{}, len(args))
for i, arg := range args {
if !arg.IsMoveableAcrossRealm() {
panic("Argument cannot pass realm boundary")
}
argexprs[i] = &gno.ConstExpr{
TypedValue: arg,
}
}

mm := gno.NewMachineWithOptions(
gno.MachineOptions{
PkgPath: pkgPath,
Output: os.Stdout,
Store: m.Store,
Context: m.Context,
Alloc: m.Alloc,
MaxCycles: m.MaxCycles - m.Cycles,
},
)
mm.SetActivePackage(mpv)

// Call the function.
call := gno.Call(fmt.Sprintf("pkg.%s", functionName), argexprs...)
tvs := mm.Eval(call)
if len(tvs) != 1 {
panic("Return argument is not length 1")
}

m.Cycles -= mm.Cycles

mm.Release()

m.PushValue(tvs[0])
},
)
// CreatePackage creates a new package from an existing path.
// Given a package path and the new path name, with optional trailing arguments for init,
// it creates a new package under the prefix of the current package.
//
// Example: a "template" GRC20 package is created under the std package("std/grc20").
// The init() of grc20 templates takes two arguments: the name of the token and the initial supply.
// To create a new GRC20 package from package "tokenfactory", we can use the following code:
// ```gno
// mytokenPackage := std.CreatePackage("std/grc20", "tokens/mytoken", 10000000000000).(IGRC20);
// ```
// This will create a new package under the current package, with the path "gno.land/r/tokenfactory/tokens/mytoken/grc20".
// The result is an interface{}, exposing all public functions of the package.
// Internally, the interface maps function names to internal native call, `CallPackage`.
pn.DefineNative("CreatePackage",
gno.Flds(
"pkgTemplatePath", "string",
"pkgPath", "string",
"arguments", "...interface{}",
),
gno.Flds(
"pkg", "string",
),
func(m *gno.Machine) {
arg0, arg1, arg2 := m.LastBlock().GetParams3()
pkgTemplatePath := arg0.TV.GetString()
pkgPath := strings.Join([]string{m.Package.PkgPath, arg1.TV.GetString()}, "/")
initArgs := arg2.TV.V.(*gno.SliceValue).GetBase(m.Store).List
if len(initArgs) != 0 {
panic("not implemented: initArgs")
}
if pv := m.Store.GetPackage(pkgPath, false); pv != nil {
panic("package already exists: " + pkgPath)
}

// TODO: validate pkgPath
tpkg := m.Store.GetMemPackage(pkgTemplatePath)
tpkg.RenamePath(pkgPath)

mm := gno.NewMachineWithOptions(
gno.MachineOptions{
PkgPath: "",
Output: os.Stdout,
Store: m.Store,
Alloc: m.Alloc,
Context: m.Context,
MaxCycles: m.MaxCycles - m.Cycles,
},
)

mm.RunMemPackage(tpkg, true)

m.Cycles -= mm.Cycles

mm.Release()

var ret gno.TypedValue
ret.SetString(gno.StringValue(pkgPath))
m.PushValue(ret)
},
)
}
}
Expand Down
15 changes: 15 additions & 0 deletions gnovm/tests/call_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package tests

import (
"testing"
"bytes"

gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
)

func TestCreatePackage(t *testing.T) {
stdin := new(bytes.Buffer)
stdout := os.Stdout
stderr := new(bytes.Buffer)
store := TestStore("..", "", stdin)
}
4 changes: 4 additions & 0 deletions tm2/pkg/std/memfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ type MemPackage struct {
Files []*MemFile
}

func (mempkg *MemPackage) RenamePath(newPath string) {
mempkg.Path = newPath
}

func (mempkg *MemPackage) GetFile(name string) *MemFile {
for _, memFile := range mempkg.Files {
if memFile.Name == name {
Expand Down