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 15 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 @@ -14,6 +14,9 @@ build.gnofaucet:; go build -o build/gnofaucet ./cmd/gnofaucet
build.gnokey:; go build -o build/gnokey ./cmd/gnokey
build.gnotxsync:; go build -o build/gnotxsync ./cmd/gnotxsync

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

Expand Down
11 changes: 4 additions & 7 deletions gno.land/cmd/gnoland/testdata/addpkg.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,14 @@ gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 10000
gnokey maketx call -pkgpath gno.land/r/foobar/bar -func Render -gas-fee 1000000ugnot -gas-wanted 2000000 -args '' -broadcast -chainid=tendermint_test test1

## compare render
cmp stdout stdout.golden
stdout '("hello from foo" string)'
stdout 'OK!'
stdout 'GAS WANTED: 2000000'
stdout 'GAS USED: '

-- bar.gno --
package bar

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

-- stdout.golden --
("hello from foo" string)
OK!
GAS WANTED: 2000000
GAS USED: 69163
28 changes: 28 additions & 0 deletions gno.land/cmd/gnoland/testdata/exec.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 '--- 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("---", 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 @@ func (vh vmHandler) Process(ctx sdk.Context, msg std.Msg) sdk.Result {
return vh.handleMsgAddPackage(ctx, msg)
case MsgCall:
return vh.handleMsgCall(ctx, msg)
case MsgRun:
return vh.handleMsgRun(ctx, msg)
default:
errMsg := fmt.Sprintf("unrecognized vm message type: %T", msg)
return abciResult(std.ErrUnknownRequest(errMsg))
Expand Down Expand Up @@ -77,6 +79,25 @@ func (vh vmHandler) handleMsgCall(ctx sdk.Context, msg MsgCall) (res sdk.Result)
*/
}

// 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
}

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

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

import (
"bytes"
"fmt"
"os"
"strings"
Expand All @@ -27,6 +28,7 @@ const (
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 @@ -282,6 +284,89 @@ func (vm *VMKeeper) Call(ctx sdk.Context, msg MsgCall) (res string, err error) {
// 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))
}
if err := msg.Package.Validate(); err != nil {
return "", ErrInvalidPkgPath(err.Error())
}

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

// 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()
// memPkg.Path = "gno.land/r/main"
_, pv := m.RunMemPackage(memPkg, true) // XXX: just save the state, not the contract

ctx.Logger().Info("CPUCYCLES", "addpkg", m.Cycles)

expr := fmt.Sprintf(`pkg.Main()`)
xn := gno.MustParseExpr(expr)
mpn := gno.NewPackageNode("main", "main", nil)
mpn.Define("pkg", gno.TypedValue{T: &gno.PackageType{}, V: pv})
mpv := mpn.NewPackage()

m2 := gno.NewMachineWithOptions(
gno.MachineOptions{
PkgPath: "",
Output: buf,
Store: store,
Alloc: store.GetAllocator(),
Context: msgCtx,
MaxCycles: vm.maxCycles,
})

m2.SetActivePackage(mpv)
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
}
m2.Release()
}()
m2.Eval(xn)
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) GetSigners() []crypto.Address {
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'")
}
}
}
return MsgRun{
Caller: caller,
Send: send,
Package: &std.MemPackage{
Name: "main",
Path: "gno.land/r/main", // XXX
Files: files,
},
}
}

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

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

// 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
}

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

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

// Implements ReceiveMsg.
func (msg MsgRun) GetReceived() std.Coins {
return msg.Send
}
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 @@ func newMakeTxCmd(rootCfg *baseCfg, io *commands.IO) *commands.Command {
newAddPkgCmd(cfg, io),
newSendCmd(cfg, io),
newCallCmd(cfg, io),
newRunCmd(cfg, io),
)

return cmd
Expand Down
Loading