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 'gnokey maketx run' #1001

Merged
merged 25 commits into from
Nov 23, 2023
Merged
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
3 changes: 3 additions & 0 deletions gno.land/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ build.gnokey:; go build -o build/gnokey ./cmd/gnokey
build.gnotxsync:; go build -o build/gnotxsync ./cmd/gnotxsync
build.genesis:; go build -o build/genesis ./cmd/genesis

run.gnoland:; go run ./cmd/gnoland start
run.gnoweb:; go run ./cmd/gnoweb

.PHONY: install
install: install.gnoland install.gnoweb install.gnofaucet install.gnokey install.gnotxsync install.genesis

Expand Down
4 changes: 1 addition & 3 deletions gno.land/cmd/gnoland/testdata/addpkg.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ stdout 'OK!'
stdout 'GAS WANTED: 2000000'
stdout 'GAS USED: [0-9]+'


-- bar.gno --
package bar

func Render(path string) string {
return "hello from foo"
}

}
28 changes: 28 additions & 0 deletions gno.land/cmd/gnoland/testdata/run.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## start a new node
gnoland start

## add bar.gno package located in $WORK directory as gno.land/r/foobar/bar
gnokey maketx addpkg -pkgdir $WORK/bar -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1

## execute Render
gnokey maketx run -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 $WORK/script/script.gno

## compare render
stdout 'main: --- hello from foo ---'
stdout 'OK!'
stdout 'GAS WANTED: 200000'
stdout 'GAS USED: '

-- bar/bar.gno --
package bar

func Render(path string) string {
return "hello from foo"
}

-- script/script.gno --
package main
import "gno.land/r/foobar/bar"
func main() {
println("main: ---", bar.Render(""), "---")
}
21 changes: 21 additions & 0 deletions gno.land/pkg/sdk/vm/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
return vh.handleMsgAddPackage(ctx, msg)
case MsgCall:
return vh.handleMsgCall(ctx, msg)
case MsgRun:
return vh.handleMsgRun(ctx, msg)

Check warning on line 31 in gno.land/pkg/sdk/vm/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/handler.go#L30-L31

Added lines #L30 - L31 were not covered by tests
default:
errMsg := fmt.Sprintf("unrecognized vm message type: %T", msg)
return abciResult(std.ErrUnknownRequest(errMsg))
Expand Down Expand Up @@ -77,6 +79,25 @@
*/
}

// Handle MsgRun.
func (vh vmHandler) handleMsgRun(ctx sdk.Context, msg MsgRun) (res sdk.Result) {
amount, err := std.ParseCoins("1000000ugnot") // XXX calculate
if err != nil {
return abciResult(err)
}
err = vh.vm.bank.SendCoins(ctx, msg.Caller, auth.FeeCollectorAddress(), amount)
if err != nil {
return abciResult(err)
}
resstr := ""
resstr, err = vh.vm.Run(ctx, msg)
if err != nil {
return abciResult(err)
}
res.Data = []byte(resstr)
return

Check warning on line 98 in gno.land/pkg/sdk/vm/handler.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/handler.go#L83-L98

Added lines #L83 - L98 were not covered by tests
}

//----------------------------------------
// Query

Expand Down
86 changes: 86 additions & 0 deletions gno.land/pkg/sdk/vm/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
// TODO: move most of the logic in ROOT/gno.land/...

import (
"bytes"
"fmt"
"os"
"regexp"
"strings"

gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
Expand All @@ -27,6 +29,7 @@
type VMKeeperI interface {
AddPackage(ctx sdk.Context, msg MsgAddPackage) error
Call(ctx sdk.Context, msg MsgCall) (res string, err error)
Run(ctx sdk.Context, msg MsgRun) (res string, err error)
}

var _ VMKeeperI = &VMKeeper{}
Expand Down Expand Up @@ -128,6 +131,10 @@
}
}

const (
reReservedPath = `gno\.land/r/g[a-z0-9]+/run`
)

// AddPackage adds a package with given fileset.
func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error {
creator := msg.Creator
Expand All @@ -150,6 +157,11 @@
if pv := store.GetPackage(pkgPath, false); pv != nil {
return ErrInvalidPkgPath("package already exists: " + pkgPath)
}

if ok, _ := regexp.MatchString(reReservedPath, pkgPath); ok {
return ErrInvalidPkgPath("reserved package name: " + pkgPath)
}

Check warning on line 163 in gno.land/pkg/sdk/vm/keeper.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/keeper.go#L162-L163

Added lines #L162 - L163 were not covered by tests

// Pay deposit from creator.
pkgAddr := gno.DerivePkgAddr(pkgPath)

Expand Down Expand Up @@ -282,6 +294,80 @@
// TODO pay for gas? TODO see context?
}

// Run executes arbitrary Gno code in the context of the caller's realm.
func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) {
caller := msg.Caller
pkgAddr := caller
store := vm.getGnoStore(ctx)
send := msg.Send
memPkg := msg.Package

// Validate arguments.
callerAcc := vm.acck.GetAccount(ctx, caller)
if callerAcc == nil {
return "", std.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", caller))
}

Check warning on line 309 in gno.land/pkg/sdk/vm/keeper.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/keeper.go#L308-L309

Added lines #L308 - L309 were not covered by tests
if err := msg.Package.Validate(); err != nil {
return "", ErrInvalidPkgPath(err.Error())
}

Check warning on line 312 in gno.land/pkg/sdk/vm/keeper.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/keeper.go#L311-L312

Added lines #L311 - L312 were not covered by tests

// Send send-coins to pkg from caller.
err = vm.bank.SendCoins(ctx, caller, pkgAddr, send)
if err != nil {
return "", err
}

Check warning on line 318 in gno.land/pkg/sdk/vm/keeper.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/keeper.go#L317-L318

Added lines #L317 - L318 were not covered by tests

// Parse and run the files, construct *PV.
msgCtx := stdlibs.ExecContext{
ChainID: ctx.ChainID(),
Height: ctx.BlockHeight(),
Timestamp: ctx.BlockTime().Unix(),
Msg: msg,
OrigCaller: caller.Bech32(),
OrigSend: send,
OrigSendSpent: new(std.Coins),
OrigPkgAddr: pkgAddr.Bech32(),
Banker: NewSDKBanker(vm, ctx),
}
// Parse and run the files, construct *PV.
buf := new(bytes.Buffer)
m := gno.NewMachineWithOptions(
gno.MachineOptions{
PkgPath: "",
moul marked this conversation as resolved.
Show resolved Hide resolved
Output: buf,
Store: store,
Alloc: store.GetAllocator(),
Context: msgCtx,
MaxCycles: vm.maxCycles,
})
defer m.Release()
_, pv := m.RunMemPackage(memPkg, false)
ctx.Logger().Info("CPUCYCLES", "addpkg", m.Cycles)

m2 := gno.NewMachineWithOptions(
gno.MachineOptions{
PkgPath: "",
Output: buf,
Store: store,
Alloc: store.GetAllocator(),
Context: msgCtx,
MaxCycles: vm.maxCycles,
})
m2.SetActivePackage(pv)
defer func() {
if r := recover(); r != nil {
err = errors.Wrap(fmt.Errorf("%v", r), "VM call panic: %v\n%s\n",
r, m2.String())
return
}

Check warning on line 362 in gno.land/pkg/sdk/vm/keeper.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/keeper.go#L359-L362

Added lines #L359 - L362 were not covered by tests
m2.Release()
}()
m2.RunMain()
ctx.Logger().Info("CPUCYCLES call: ", m2.Cycles)
res = buf.String()
return res, nil
}

// QueryFuncs returns public facing function signatures.
func (vm *VMKeeper) QueryFuncs(ctx sdk.Context, pkgPath string) (fsigs FunctionSignatures, err error) {
store := vm.getGnoStore(ctx)
Expand Down
58 changes: 58 additions & 0 deletions gno.land/pkg/sdk/vm/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,61 @@ func GetAdmin() string {
assert.NoError(t, err)
assert.Equal(t, res, addrString)
}

// Call Run without imports, without variables.
func TestVMKeeperRunSimple(t *testing.T) {
env := setupTestEnv()
ctx := env.ctx

// Give "addr1" some gnots.
addr := crypto.AddressFromPreimage([]byte("addr1"))
acc := env.acck.NewAccountWithAddress(ctx, addr)
env.acck.SetAccount(ctx, acc)

files := []*std.MemFile{
{"script.gno", `
package main

func main() {
println("hello world!")
}
`},
}

coins := std.MustParseCoins("")
msg2 := NewMsgRun(addr, coins, files)
res, err := env.vmk.Run(ctx, msg2)
assert.NoError(t, err)
assert.Equal(t, res, "hello world!\n")
}

// Call Run with stdlibs.
func TestVMKeeperRunImportStdlibs(t *testing.T) {
env := setupTestEnv()
ctx := env.ctx

// Give "addr1" some gnots.
addr := crypto.AddressFromPreimage([]byte("addr1"))
acc := env.acck.NewAccountWithAddress(ctx, addr)
env.acck.SetAccount(ctx, acc)

files := []*std.MemFile{
{"script.gno", `
package main

import "std"

func main() {
addr := std.GetOrigCaller()
println("hello world!", addr)
}
`},
}

coins := std.MustParseCoins("")
msg2 := NewMsgRun(addr, coins, files)
res, err := env.vmk.Run(ctx, msg2)
assert.NoError(t, err)
expectedString := fmt.Sprintf("hello world! %s\n", addr.String())
assert.Equal(t, res, expectedString)
}
64 changes: 64 additions & 0 deletions gno.land/pkg/sdk/vm/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,67 @@
func (msg MsgCall) GetReceived() std.Coins {
return msg.Send
}

//----------------------------------------
// MsgRun

// MsgRun - executes arbitrary Gno code.
type MsgRun struct {
Caller crypto.Address `json:"caller" yaml:"caller"`
Send std.Coins `json:"send" yaml:"send"`
Package *std.MemPackage `json:"package" yaml:"package"`
}

var _ std.Msg = MsgRun{}

func NewMsgRun(caller crypto.Address, send std.Coins, files []*std.MemFile) MsgRun {
for _, file := range files {
if strings.HasSuffix(file.Name, ".gno") {
pkgName := string(gno.PackageNameFromFileBody(file.Name, file.Body))
if pkgName != "main" {
panic("package name should be 'main'")

Check warning on line 156 in gno.land/pkg/sdk/vm/msgs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/msgs.go#L156

Added line #L156 was not covered by tests
}
}
}
return MsgRun{
Caller: caller,
Send: send,
Package: &std.MemPackage{
Name: "main",
Path: "gno.land/r/" + caller.String() + "/run",
Files: files,
},
}
}

// Implements Msg.
func (msg MsgRun) Route() string { return RouterKey }

Check warning on line 172 in gno.land/pkg/sdk/vm/msgs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/msgs.go#L172

Added line #L172 was not covered by tests

// Implements Msg.
func (msg MsgRun) Type() string { return "run" }

Check warning on line 175 in gno.land/pkg/sdk/vm/msgs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/msgs.go#L175

Added line #L175 was not covered by tests

// Implements Msg.
func (msg MsgRun) ValidateBasic() error {
if msg.Caller.IsZero() {
return std.ErrInvalidAddress("missing caller address")
}
if msg.Package.Path == "" { // XXX
return ErrInvalidPkgPath("missing package path")
}
return nil

Check warning on line 185 in gno.land/pkg/sdk/vm/msgs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/msgs.go#L178-L185

Added lines #L178 - L185 were not covered by tests
}

// Implements Msg.
func (msg MsgRun) GetSignBytes() []byte {
return std.MustSortJSON(amino.MustMarshalJSON(msg))

Check warning on line 190 in gno.land/pkg/sdk/vm/msgs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/msgs.go#L189-L190

Added lines #L189 - L190 were not covered by tests
}

// Implements Msg.
func (msg MsgRun) GetSigners() []crypto.Address {
return []crypto.Address{msg.Caller}

Check warning on line 195 in gno.land/pkg/sdk/vm/msgs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/msgs.go#L194-L195

Added lines #L194 - L195 were not covered by tests
}

// Implements ReceiveMsg.
func (msg MsgRun) GetReceived() std.Coins {
return msg.Send

Check warning on line 200 in gno.land/pkg/sdk/vm/msgs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/sdk/vm/msgs.go#L199-L200

Added lines #L199 - L200 were not covered by tests
}
1 change: 1 addition & 0 deletions gno.land/pkg/sdk/vm/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var Package = amino.RegisterPackage(amino.NewPackage(
std.Package,
).WithTypes(
MsgCall{}, "m_call",
MsgRun{}, "m_run",
MsgAddPackage{}, "m_addpkg", // TODO rename both to MsgAddPkg?

// errors
Expand Down
1 change: 1 addition & 0 deletions tm2/pkg/crypto/keys/client/maketx.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
newAddPkgCmd(cfg, io),
newSendCmd(cfg, io),
newCallCmd(cfg, io),
newRunCmd(cfg, io),

Check warning on line 39 in tm2/pkg/crypto/keys/client/maketx.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/crypto/keys/client/maketx.go#L39

Added line #L39 was not covered by tests
)

return cmd
Expand Down
Loading
Loading