diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index 86af2858275..d93c0adb950 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -8,6 +8,7 @@ import ( rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/gnolang/gno/tm2/pkg/crypto/keys" "github.com/gnolang/gno/tm2/pkg/log" + "github.com/gnolang/gno/tm2/pkg/std" "github.com/jaekwon/testify/require" ) @@ -50,3 +51,50 @@ func TestClient_Request(t *testing.T) { // XXX: need more test } + +func TestClient_Run(t *testing.T) { + config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir()) + node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNopLogger(), config) + defer node.Stop() + + signer := newInMemorySigner(t, config.TMConfig.ChainID()) + + client := Client{ + Signer: signer, + RPCClient: rpcclient.NewHTTP(remoteAddr, "/websocket"), + } + + code := `package main + +import ( + "std" + + "gno.land/p/demo/ufmt" + "gno.land/r/demo/tests" +) + +func main() { + println(ufmt.Sprintf("- before: %d", tests.Counter())) + for i := 0; i < 10; i++ { + tests.IncCounter() + } + println(ufmt.Sprintf("- after: %d", tests.Counter())) +}` + memPkg := &std.MemPackage{ + Files: []*std.MemFile{ + { + Name: "main.gno", + Body: code, + }, + }, + } + res, err := client.Run(RunCfg{ + Package: memPkg, + GasFee: "1ugnot", + GasWanted: 100000000, + }) + require.NoError(t, err) + require.NotNil(t, res) + require.NotEmpty(t, res.DeliverTx.Data) + require.Equal(t, string(res.DeliverTx.Data), "- before: 0\n- after: 10\n") +} diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index ab475154fad..db22fba93ad 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -2,6 +2,7 @@ package gnoclient import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/errors" @@ -21,6 +22,16 @@ type CallCfg struct { Memo string // Memo } +// RunCfg contains configuration options for running a temporary package on the blockchain. +type RunCfg struct { + Package *std.MemPackage + GasFee string // Gas fee + GasWanted int64 // Gas wanted + AccountNumber uint64 // Account number + SequenceNumber uint64 // Sequence number + Memo string // Memo +} + // Call executes a contract call on the blockchain. func (c *Client) Call(cfg CallCfg) (*ctypes.ResultBroadcastTxCommit, error) { // Validate required client fields. @@ -81,6 +92,61 @@ func (c *Client) Call(cfg CallCfg) (*ctypes.ResultBroadcastTxCommit, error) { return c.signAndBroadcastTxCommit(tx, accountNumber, sequenceNumber) } +// Temporarily load cfg.Package on the blockchain and run main() which can +// call realm functions and use println() to output to the "console". +// This returns bres where string(bres.DeliverTx.Data) is the "console" output. +func (c *Client) Run(cfg RunCfg) (*ctypes.ResultBroadcastTxCommit, error) { + // Validate required client fields. + if err := c.validateSigner(); err != nil { + return nil, errors.Wrap(err, "validate signer") + } + if err := c.validateRPCClient(); err != nil { + return nil, errors.Wrap(err, "validate RPC client") + } + + memPkg := cfg.Package + gasWanted := cfg.GasWanted + gasFee := cfg.GasFee + sequenceNumber := cfg.SequenceNumber + accountNumber := cfg.AccountNumber + memo := cfg.Memo + + // Validate config. + if memPkg.IsEmpty() { + return nil, errors.New("found an empty package " + memPkg.Path) + } + + // Parse gas wanted & fee. + gasFeeCoins, err := std.ParseCoin(gasFee) + if err != nil { + return nil, errors.Wrap(err, "parsing gas fee coin") + } + + caller := c.Signer.Info().GetAddress() + + // precompile and validate syntax + err = gno.PrecompileAndCheckMempkg(memPkg) + if err != nil { + return nil, errors.Wrap(err, "precompile and check") + } + memPkg.Name = "main" + memPkg.Path = "gno.land/r/" + caller.String() + "/run" + + // Construct message & transaction and marshal. + msg := vm.MsgRun{ + Caller: caller, + Package: memPkg, + } + tx := std.Tx{ + Msgs: []std.Msg{msg}, + Fee: std.NewFee(gasWanted, gasFeeCoins), + Signatures: nil, + Memo: memo, + } + + return c.signAndBroadcastTxCommit(tx, accountNumber, sequenceNumber) +} + // signAndBroadcastTxCommit signs a transaction and broadcasts it, returning the result. func (c Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) { caller := c.Signer.Info().GetAddress()