From 50d426a6fc793d61cc5e948e2191d3ee855141c3 Mon Sep 17 00:00:00 2001
From: Guilhem Fanton <8671905+gfanton@users.noreply.github.com>
Date: Thu, 1 Feb 2024 17:28:07 +0100
Subject: [PATCH 01/26] feat: add `Ready` method to Node (#1216)
---
gno.land/pkg/integration/testing_node.go | 4 +--
tm2/pkg/bft/node/node.go | 22 ++++++++++++++++
tm2/pkg/bft/node/node_test.go | 33 ++++++++++++++++++++++++
3 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/gno.land/pkg/integration/testing_node.go b/gno.land/pkg/integration/testing_node.go
index b0aa9f7c2af..cc0262b8105 100644
--- a/gno.land/pkg/integration/testing_node.go
+++ b/gno.land/pkg/integration/testing_node.go
@@ -32,8 +32,8 @@ func TestingInMemoryNode(t TestingTS, logger *slog.Logger, config *gnoland.InMem
require.NoError(t, err)
select {
- case <-gnoland.GetNodeReadiness(node):
- case <-time.After(time.Second * 6):
+ case <-node.Ready():
+ case <-time.After(time.Second * 10):
require.FailNow(t, "timeout while waiting for the node to start")
}
diff --git a/tm2/pkg/bft/node/node.go b/tm2/pkg/bft/node/node.go
index a6c67fa2485..57cc18b8ad7 100644
--- a/tm2/pkg/bft/node/node.go
+++ b/tm2/pkg/bft/node/node.go
@@ -9,6 +9,7 @@ import (
"net/http"
_ "net/http/pprof" //nolint:gosec
"strings"
+ "sync"
"time"
"golang.org/x/exp/slog"
@@ -168,6 +169,7 @@ type Node struct {
rpcListeners []net.Listener // rpc servers
txEventStore eventstore.TxEventStore
eventStoreService *eventstore.Service
+ firstBlockSignal <-chan struct{}
}
func initDBs(config *cfg.Config, dbProvider DBProvider) (blockStore *store.BlockStore, stateDB dbm.DB, err error) {
@@ -439,6 +441,20 @@ func NewNode(config *cfg.Config,
// but before it indexed the txs, or, endblocker panicked)
evsw := events.NewEventSwitch()
+ // Signal readiness when receiving the first block.
+ const readinessListenerID = "first_block_listener"
+
+ cFirstBlock := make(chan struct{})
+ var once sync.Once
+ evsw.AddListener(readinessListenerID, func(ev events.Event) {
+ if _, ok := ev.(types.EventNewBlock); ok {
+ once.Do(func() {
+ close(cFirstBlock)
+ evsw.RemoveListener(readinessListenerID)
+ })
+ }
+ })
+
// Transaction event storing
eventStoreService, txEventStore, err := createAndStartEventStoreService(config, evsw, logger)
if err != nil {
@@ -554,6 +570,7 @@ func NewNode(config *cfg.Config,
proxyApp: proxyApp,
txEventStore: txEventStore,
eventStoreService: eventStoreService,
+ firstBlockSignal: cFirstBlock,
}
node.BaseService = *service.NewBaseService(logger, "Node", node)
@@ -653,6 +670,11 @@ func (n *Node) OnStop() {
}
}
+// Ready signals that the node is ready by returning a blocking channel. This channel is closed when the node receives its first block.
+func (n *Node) Ready() <-chan struct{} {
+ return n.firstBlockSignal
+}
+
// ConfigureRPC sets all variables in rpccore so they will serve
// rpc calls from this node
func (n *Node) ConfigureRPC() {
diff --git a/tm2/pkg/bft/node/node_test.go b/tm2/pkg/bft/node/node_test.go
index d182b7fb0d5..3057a41b5f3 100644
--- a/tm2/pkg/bft/node/node_test.go
+++ b/tm2/pkg/bft/node/node_test.go
@@ -108,6 +108,39 @@ func TestNodeDelayedStart(t *testing.T) {
assert.Equal(t, true, startTime.After(n.GenesisDoc().GenesisTime))
}
+func TestNodeReady(t *testing.T) {
+ config := cfg.ResetTestRoot("node_node_test")
+ defer os.RemoveAll(config.RootDir)
+
+ // Create & start node
+ n, err := DefaultNewNode(config, log.NewTestingLogger(t))
+ require.NoError(t, err)
+
+ // Assert that blockstore has zero block before waiting for the first block
+ require.Equal(t, int64(0), n.BlockStore().Height())
+
+ // Assert that first block signal is not alreay received by calling Ready
+ select {
+ case <-n.Ready():
+ require.FailNow(t, "first block signal should not be close before starting the node")
+ default: // ok
+ }
+
+ err = n.Start()
+ require.NoError(t, err)
+ defer n.Stop()
+
+ // Wait until the node is ready or timeout
+ select {
+ case <-time.After(time.Second):
+ require.FailNow(t, "timeout while waiting for first block signal")
+ case <-n.Ready(): // ready
+ }
+
+ // Check that blockstore have at last one block
+ require.GreaterOrEqual(t, n.BlockStore().Height(), int64(1))
+}
+
func TestNodeSetAppVersion(t *testing.T) {
config := cfg.ResetTestRoot("node_app_version_test")
defer os.RemoveAll(config.RootDir)
From d8a6dec882556ed8017bccf6374d8a04ae19fb55 Mon Sep 17 00:00:00 2001
From: Leon Hudak <33522493+leohhhn@users.noreply.github.com>
Date: Thu, 1 Feb 2024 18:43:36 +0100
Subject: [PATCH 02/26] feat(gnoclient): add MultiCall (#1565)
## Description
This PR introduces a the MultiCall feature to the Gnoclient. It allows
for packing multiple vm.MsgCall messages into a single transaction, then
signing & publishing it.
The PR utilizes the same API, just allows for multiple MsgCall messages
in the CallCfg struct, and parses accordingly.
cc @zivkovicmilos @moul @jefft0
Contributors' checklist...
- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
gno.land/pkg/gnoclient/client.go | 5 +-
gno.land/pkg/gnoclient/client_test.go | 397 +++++++++++++++++++++-----
gno.land/pkg/gnoclient/client_txs.go | 113 ++++----
gno.land/pkg/gnoclient/mock_test.go | 284 ++++++++++++++++++
gno.land/pkg/gnoclient/util.go | 22 ++
5 files changed, 700 insertions(+), 121 deletions(-)
create mode 100644 gno.land/pkg/gnoclient/mock_test.go
create mode 100644 gno.land/pkg/gnoclient/util.go
diff --git a/gno.land/pkg/gnoclient/client.go b/gno.land/pkg/gnoclient/client.go
index 2c43a5fa01d..0a6918999a6 100644
--- a/gno.land/pkg/gnoclient/client.go
+++ b/gno.land/pkg/gnoclient/client.go
@@ -2,7 +2,6 @@ package gnoclient
import (
rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
- "github.com/gnolang/gno/tm2/pkg/errors"
)
// Client provides an interface for interacting with the blockchain.
@@ -14,7 +13,7 @@ type Client struct {
// validateSigner checks that the signer is correctly configured.
func (c Client) validateSigner() error {
if c.Signer == nil {
- return errors.New("missing Signer")
+ return ErrMissingSigner
}
return nil
}
@@ -22,7 +21,7 @@ func (c Client) validateSigner() error {
// validateRPCClient checks that the RPCClient is correctly configured.
func (c Client) validateRPCClient() error {
if c.RPCClient == nil {
- return errors.New("missing RPCClient")
+ return ErrMissingRPCClient
}
return nil
}
diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go
index 0fe5f4eefcc..c7e6b3c6c5b 100644
--- a/gno.land/pkg/gnoclient/client_test.go
+++ b/gno.land/pkg/gnoclient/client_test.go
@@ -3,98 +3,359 @@ package gnoclient
import (
"testing"
- "github.com/gnolang/gno/gno.land/pkg/integration"
- "github.com/gnolang/gno/gnovm/pkg/gnoenv"
- rpcclient "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
+ ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types"
+ "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
"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"
)
-func newInMemorySigner(t *testing.T, chainid string) *SignerFromKeybase {
- t.Helper()
-
- mmeonic := integration.DefaultAccount_Seed
- name := integration.DefaultAccount_Name
-
- kb := keys.NewInMemory()
- _, err := kb.CreateAccount(name, mmeonic, "", "", uint32(0), uint32(0))
- require.NoError(t, err)
+func TestClient_Render(t *testing.T) {
+ t.Parallel()
+ testRealmPath := "gno.land/r/demo/deep/very/deep"
+ expectedRender := []byte("it works!")
- return &SignerFromKeybase{
- Keybase: kb, // Stores keys in memory or on disk
- Account: name, // Account name or bech32 format
- Password: "", // Password for encryption
- ChainID: chainid, // Chain ID for transaction signing
+ client := Client{
+ Signer: &mockSigner{
+ sign: func(cfg SignCfg) (*std.Tx, error) {
+ return &std.Tx{}, nil
+ },
+ info: func() keys.Info {
+ return &mockKeysInfo{
+ getAddress: func() crypto.Address {
+ adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
+ return adr
+ },
+ }
+ },
+ },
+ RPCClient: &mockRPCClient{
+ abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) {
+ res := &ctypes.ResultABCIQuery{
+ Response: abci.ResponseQuery{
+ ResponseBase: abci.ResponseBase{
+ Data: expectedRender,
+ },
+ },
+ }
+ return res, nil
+ },
+ },
}
-}
-func TestClient_Request(t *testing.T) {
- config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir())
- node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config)
- defer node.Stop()
+ res, data, err := client.Render(testRealmPath, "")
+ assert.NoError(t, err)
+ assert.NotEmpty(t, data.Response.Data)
+ assert.NotEmpty(t, res)
+ assert.Equal(t, data.Response.Data, expectedRender)
+}
- signer := newInMemorySigner(t, config.TMConfig.ChainID())
+func TestClient_CallSingle(t *testing.T) {
+ t.Parallel()
client := Client{
- Signer: signer,
- RPCClient: rpcclient.NewHTTP(remoteAddr, "/websocket"),
+ Signer: &mockSigner{
+ sign: func(cfg SignCfg) (*std.Tx, error) {
+ return &std.Tx{}, nil
+ },
+ info: func() keys.Info {
+ return &mockKeysInfo{
+ getAddress: func() crypto.Address {
+ adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
+ return adr
+ },
+ }
+ },
+ },
+ RPCClient: &mockRPCClient{
+ broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
+ res := &ctypes.ResultBroadcastTxCommit{
+ DeliverTx: abci.ResponseDeliverTx{
+ ResponseBase: abci.ResponseBase{
+ Data: []byte("it works!"),
+ },
+ },
+ }
+ return res, nil
+ },
+ },
+ }
+
+ cfg := BaseTxCfg{
+ GasWanted: 100000,
+ GasFee: "10000ugnot",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
}
- data, res, err := client.Render("gno.land/r/demo/boards", "")
- require.NoError(t, err)
- require.NotEmpty(t, data)
+ msg := []MsgCall{
+ {
+ PkgPath: "gno.land/r/demo/deep/very/deep",
+ FuncName: "Render",
+ Args: []string{""},
+ Send: "100ugnot",
+ },
+ }
+ res, err := client.Call(cfg, msg...)
+ assert.NoError(t, err)
require.NotNil(t, res)
- require.NotEmpty(t, res.Response.Data)
-
- // XXX: need more test
+ assert.Equal(t, string(res.DeliverTx.Data), "it works!")
}
-func TestClient_Run(t *testing.T) {
- config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir())
- node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config)
- defer node.Stop()
-
- signer := newInMemorySigner(t, config.TMConfig.ChainID())
+func TestClient_CallMultiple(t *testing.T) {
+ t.Parallel()
client := Client{
- Signer: signer,
- RPCClient: rpcclient.NewHTTP(remoteAddr, "/websocket"),
+ Signer: &mockSigner{
+ sign: func(cfg SignCfg) (*std.Tx, error) {
+ return &std.Tx{}, nil
+ },
+ info: func() keys.Info {
+ return &mockKeysInfo{
+ getAddress: func() crypto.Address {
+ adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
+ return adr
+ },
+ }
+ },
+ },
+ RPCClient: &mockRPCClient{
+ broadcastTxCommit: func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
+ res := &ctypes.ResultBroadcastTxCommit{
+ CheckTx: abci.ResponseCheckTx{
+ ResponseBase: abci.ResponseBase{
+ Error: nil,
+ Data: nil,
+ Events: nil,
+ Log: "",
+ Info: "",
+ },
+ },
+ }
+
+ return res, nil
+ },
+ },
}
- code := `package main
+ cfg := BaseTxCfg{
+ GasWanted: 100000,
+ GasFee: "10000ugnot",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
+ }
-import (
- "std"
+ msg := []MsgCall{
+ {
+ PkgPath: "gno.land/r/demo/deep/very/deep",
+ FuncName: "Render",
+ Args: []string{""},
+ Send: "100ugnot",
+ },
+ {
+ PkgPath: "gno.land/r/demo/wugnot",
+ FuncName: "Deposit",
+ Args: []string{""},
+ Send: "1000ugnot",
+ },
+ {
+ PkgPath: "gno.land/r/demo/tamagotchi",
+ FuncName: "Feed",
+ Args: []string{},
+ Send: "",
+ },
+ }
- "gno.land/p/demo/ufmt"
- "gno.land/r/demo/tests"
-)
+ res, err := client.Call(cfg, msg...)
+ assert.NoError(t, err)
+ assert.NotNil(t, res)
+}
-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,
+func TestClient_Call_Errors(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ client Client
+ cfg BaseTxCfg
+ msgs []MsgCall
+ expectedError error
+ }{
+ {
+ name: "Invalid Signer",
+ client: Client{
+ Signer: nil,
+ RPCClient: &mockRPCClient{},
+ },
+ cfg: BaseTxCfg{
+ GasWanted: 100000,
+ GasFee: "10000ugnot",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
+ },
+ msgs: []MsgCall{
+ {
+ PkgPath: "random/path",
+ FuncName: "RandomName",
+ Send: "",
+ Args: []string{},
+ },
+ },
+ expectedError: ErrMissingSigner,
+ },
+ {
+ name: "Invalid RPCClient",
+ client: Client{
+ &mockSigner{},
+ nil,
+ },
+ cfg: BaseTxCfg{
+ GasWanted: 100000,
+ GasFee: "10000ugnot",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
+ },
+ msgs: []MsgCall{
+ {
+ PkgPath: "random/path",
+ FuncName: "RandomName",
+ Send: "",
+ Args: []string{},
+ },
+ },
+ expectedError: ErrMissingRPCClient,
+ },
+ {
+ name: "Invalid Gas Fee",
+ client: Client{
+ Signer: &mockSigner{},
+ RPCClient: &mockRPCClient{},
+ },
+ cfg: BaseTxCfg{
+ GasWanted: 100000,
+ GasFee: "",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
+ },
+ msgs: []MsgCall{
+ {
+ PkgPath: "random/path",
+ FuncName: "RandomName",
+ },
+ },
+ expectedError: ErrInvalidGasFee,
+ },
+ {
+ name: "Negative Gas Wanted",
+ client: Client{
+ Signer: &mockSigner{},
+ RPCClient: &mockRPCClient{},
+ },
+ cfg: BaseTxCfg{
+ GasWanted: -1,
+ GasFee: "10000ugnot",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
},
+ msgs: []MsgCall{
+ {
+ PkgPath: "random/path",
+ FuncName: "RandomName",
+ Send: "",
+ Args: []string{},
+ },
+ },
+ expectedError: ErrInvalidGasWanted,
+ },
+ {
+ name: "0 Gas Wanted",
+ client: Client{
+ Signer: &mockSigner{},
+ RPCClient: &mockRPCClient{},
+ },
+ cfg: BaseTxCfg{
+ GasWanted: 0,
+ GasFee: "10000ugnot",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
+ },
+ msgs: []MsgCall{
+ {
+ PkgPath: "random/path",
+ FuncName: "RandomName",
+ Send: "",
+ Args: []string{},
+ },
+ },
+ expectedError: ErrInvalidGasWanted,
+ },
+ {
+ name: "Invalid PkgPath",
+ client: Client{
+ Signer: &mockSigner{},
+ RPCClient: &mockRPCClient{},
+ },
+ cfg: BaseTxCfg{
+ GasWanted: 100000,
+ GasFee: "10000ugnot",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
+ },
+ msgs: []MsgCall{
+ {
+ PkgPath: "",
+ FuncName: "RandomName",
+ Send: "",
+ Args: []string{},
+ },
+ },
+ expectedError: ErrEmptyPkgPath,
+ },
+ {
+ name: "Invalid FuncName",
+ client: Client{
+ Signer: &mockSigner{},
+ RPCClient: &mockRPCClient{},
+ },
+ cfg: BaseTxCfg{
+ GasWanted: 100000,
+ GasFee: "10000ugnot",
+ AccountNumber: 1,
+ SequenceNumber: 1,
+ Memo: "Test memo",
+ },
+ msgs: []MsgCall{
+ {
+ PkgPath: "random/path",
+ FuncName: "",
+ Send: "",
+ Args: []string{},
+ },
+ },
+ expectedError: ErrEmptyFuncName,
},
}
- 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")
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ res, err := tc.client.Call(tc.cfg, tc.msgs...)
+ assert.Nil(t, res)
+ assert.ErrorIs(t, err, tc.expectedError)
+ })
+ }
}
diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go
index db22fba93ad..9f06217599f 100644
--- a/gno.land/pkg/gnoclient/client_txs.go
+++ b/gno.land/pkg/gnoclient/client_txs.go
@@ -9,17 +9,29 @@ import (
"github.com/gnolang/gno/tm2/pkg/std"
)
-// CallCfg contains configuration options for executing a contract call.
-type CallCfg struct {
- PkgPath string // Package path
- FuncName string // Function name
- Args []string // Function arguments
- GasFee string // Gas fee
- GasWanted int64 // Gas wanted
- Send string // Send amount
- AccountNumber uint64 // Account number
- SequenceNumber uint64 // Sequence number
- Memo string // Memo
+var (
+ ErrEmptyPkgPath = errors.New("empty pkg path")
+ ErrEmptyFuncName = errors.New("empty function name")
+ ErrInvalidGasWanted = errors.New("invalid gas wanted")
+ ErrInvalidGasFee = errors.New("invalid gas fee")
+ ErrMissingSigner = errors.New("missing Signer")
+ ErrMissingRPCClient = errors.New("missing RPCClient")
+)
+
+type BaseTxCfg struct {
+ GasFee string // Gas fee
+ GasWanted int64 // Gas wanted
+ AccountNumber uint64 // Account number
+ SequenceNumber uint64 // Sequence number
+ Memo string // Memo
+}
+
+// MsgCall - syntax sugar for vm.MsgCall
+type MsgCall struct {
+ PkgPath string // Package path
+ FuncName string // Function name
+ Args []string // Function arguments
+ Send string // Send amount
}
// RunCfg contains configuration options for running a temporary package on the blockchain.
@@ -33,63 +45,64 @@ type RunCfg struct {
}
// Call executes a contract call on the blockchain.
-func (c *Client) Call(cfg CallCfg) (*ctypes.ResultBroadcastTxCommit, error) {
+func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTxCommit, error) {
// Validate required client fields.
if err := c.validateSigner(); err != nil {
- return nil, errors.Wrap(err, "validate signer")
+ return nil, err
}
if err := c.validateRPCClient(); err != nil {
- return nil, errors.Wrap(err, "validate RPC client")
+ return nil, err
}
- pkgPath := cfg.PkgPath
- funcName := cfg.FuncName
- args := cfg.Args
- gasWanted := cfg.GasWanted
- gasFee := cfg.GasFee
- send := cfg.Send
- sequenceNumber := cfg.SequenceNumber
- accountNumber := cfg.AccountNumber
- memo := cfg.Memo
-
- // Validate config.
- if pkgPath == "" {
- return nil, errors.New("missing PkgPath")
+ // Validate base transaction config
+ if err := cfg.validateBaseTxConfig(); err != nil {
+ return nil, err
}
- if funcName == "" {
- return nil, errors.New("missing FuncName")
+
+ // Parse MsgCall slice
+ vmMsgs := make([]vm.MsgCall, 0, len(msgs))
+ for _, msg := range msgs {
+ // Validate MsgCall fields
+ if err := msg.validateMsgCall(); err != nil {
+ return nil, err
+ }
+
+ // Parse send coins
+ send, err := std.ParseCoins(msg.Send)
+ if err != nil {
+ return nil, err
+ }
+
+ // Unwrap syntax sugar to vm.MsgCall slice
+ vmMsgs = append(vmMsgs, vm.MsgCall{
+ Caller: c.Signer.Info().GetAddress(),
+ PkgPath: msg.PkgPath,
+ Func: msg.FuncName,
+ Send: send,
+ })
}
- // Parse send amount.
- sendCoins, err := std.ParseCoins(send)
- if err != nil {
- return nil, errors.Wrap(err, "parsing send coins")
+ // Cast vm.MsgCall back into std.Msg
+ stdMsgs := make([]std.Msg, len(vmMsgs))
+ for i, msg := range vmMsgs {
+ stdMsgs[i] = msg
}
- // Parse gas wanted & fee.
- gasFeeCoins, err := std.ParseCoin(gasFee)
+ // Parse gas fee
+ gasFeeCoins, err := std.ParseCoin(cfg.GasFee)
if err != nil {
- return nil, errors.Wrap(err, "parsing gas fee coin")
+ return nil, err
}
- caller := c.Signer.Info().GetAddress()
-
- // Construct message & transaction and marshal.
- msg := vm.MsgCall{
- Caller: caller,
- Send: sendCoins,
- PkgPath: pkgPath,
- Func: funcName,
- Args: args,
- }
+ // Pack transaction
tx := std.Tx{
- Msgs: []std.Msg{msg},
- Fee: std.NewFee(gasWanted, gasFeeCoins),
+ Msgs: stdMsgs,
+ Fee: std.NewFee(cfg.GasWanted, gasFeeCoins),
Signatures: nil,
- Memo: memo,
+ Memo: cfg.Memo,
}
- return c.signAndBroadcastTxCommit(tx, accountNumber, sequenceNumber)
+ return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber)
}
// Temporarily load cfg.Package on the blockchain and run main() which can
diff --git a/gno.land/pkg/gnoclient/mock_test.go b/gno.land/pkg/gnoclient/mock_test.go
new file mode 100644
index 00000000000..4a12dfd2d88
--- /dev/null
+++ b/gno.land/pkg/gnoclient/mock_test.go
@@ -0,0 +1,284 @@
+package gnoclient
+
+import (
+ "github.com/gnolang/gno/tm2/pkg/bft/rpc/client"
+ ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types"
+ "github.com/gnolang/gno/tm2/pkg/bft/types"
+ "github.com/gnolang/gno/tm2/pkg/crypto"
+ "github.com/gnolang/gno/tm2/pkg/crypto/hd"
+ "github.com/gnolang/gno/tm2/pkg/crypto/keys"
+ "github.com/gnolang/gno/tm2/pkg/std"
+)
+
+// Signer mock
+type (
+ mockSign func(cfg SignCfg) (*std.Tx, error)
+ mockInfo func() keys.Info
+ mockValidate func() error
+)
+
+type mockSigner struct {
+ sign mockSign
+ info mockInfo
+ validate mockValidate
+}
+
+func (m *mockSigner) Sign(cfg SignCfg) (*std.Tx, error) {
+ if m.sign != nil {
+ return m.sign(cfg)
+ }
+ return nil, nil
+}
+
+func (m *mockSigner) Info() keys.Info {
+ if m.info != nil {
+ return m.info()
+ }
+ return nil
+}
+
+func (m *mockSigner) Validate() error {
+ if m.validate != nil {
+ return m.validate()
+ }
+ return nil
+}
+
+// Keys Info mock
+type (
+ mockGetAddress func() crypto.Address
+ mockGetType func() keys.KeyType
+ mockGetName func() string
+ mockGetPubKey func() crypto.PubKey
+ mockGetPath func() (*hd.BIP44Params, error)
+)
+
+type mockKeysInfo struct {
+ getAddress mockGetAddress
+ getType mockGetType
+ getName mockGetName
+ getPubKey mockGetPubKey
+ getPath mockGetPath
+}
+
+func (m *mockKeysInfo) GetAddress() crypto.Address {
+ if m.getAddress != nil {
+ return m.getAddress()
+ }
+ return crypto.Address{}
+}
+
+func (m *mockKeysInfo) GetType() keys.KeyType {
+ if m.getType != nil {
+ return m.getType()
+ }
+ return 0
+}
+
+func (m *mockKeysInfo) GetName() string {
+ if m.getName != nil {
+ return m.getName()
+ }
+ return ""
+}
+
+func (m *mockKeysInfo) GetPubKey() crypto.PubKey {
+ if m.getPubKey != nil {
+ return m.getPubKey()
+ }
+ return nil
+}
+
+func (m *mockKeysInfo) GetPath() (*hd.BIP44Params, error) {
+ if m.getPath != nil {
+ return m.getPath()
+ }
+ return nil, nil
+}
+
+// RPC Client mock
+type (
+ mockBroadcastTxCommit func(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error)
+ mockABCIQuery func(path string, data []byte) (*ctypes.ResultABCIQuery, error)
+ mockABCIInfo func() (*ctypes.ResultABCIInfo, error)
+ mockABCIQueryWithOptions func(path string, data []byte, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error)
+ mockBroadcastTxAsync func(tx types.Tx) (*ctypes.ResultBroadcastTx, error)
+ mockBroadcastTxSync func(tx types.Tx) (*ctypes.ResultBroadcastTx, error)
+ mockGenesis func() (*ctypes.ResultGenesis, error)
+ mockBlockchainInfo func(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error)
+ mockNetInfo func() (*ctypes.ResultNetInfo, error)
+ mockDumpConsensusState func() (*ctypes.ResultDumpConsensusState, error)
+ mockConsensusState func() (*ctypes.ResultConsensusState, error)
+ mockConsensusParams func(height *int64) (*ctypes.ResultConsensusParams, error)
+ mockHealth func() (*ctypes.ResultHealth, error)
+ mockBlock func(height *int64) (*ctypes.ResultBlock, error)
+ mockBlockResults func(height *int64) (*ctypes.ResultBlockResults, error)
+ mockCommit func(height *int64) (*ctypes.ResultCommit, error)
+ mockValidators func(height *int64) (*ctypes.ResultValidators, error)
+ mockStatus func() (*ctypes.ResultStatus, error)
+ mockUnconfirmedTxs func(limit int) (*ctypes.ResultUnconfirmedTxs, error)
+ mockNumUnconfirmedTxs func() (*ctypes.ResultUnconfirmedTxs, error)
+)
+
+type mockRPCClient struct {
+ broadcastTxCommit mockBroadcastTxCommit
+ abciQuery mockABCIQuery
+ abciInfo mockABCIInfo
+ abciQueryWithOptions mockABCIQueryWithOptions
+ broadcastTxAsync mockBroadcastTxAsync
+ broadcastTxSync mockBroadcastTxSync
+ genesis mockGenesis
+ blockchainInfo mockBlockchainInfo
+ netInfo mockNetInfo
+ dumpConsensusState mockDumpConsensusState
+ consensusState mockConsensusState
+ consensusParams mockConsensusParams
+ health mockHealth
+ block mockBlock
+ blockResults mockBlockResults
+ commit mockCommit
+ validators mockValidators
+ status mockStatus
+ unconfirmedTxs mockUnconfirmedTxs
+ numUnconfirmedTxs mockNumUnconfirmedTxs
+}
+
+func (m *mockRPCClient) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
+ if m.broadcastTxCommit != nil {
+ return m.broadcastTxCommit(tx)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) ABCIQuery(path string, data []byte) (*ctypes.ResultABCIQuery, error) {
+ if m.abciQuery != nil {
+ return m.abciQuery(path, data)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) ABCIInfo() (*ctypes.ResultABCIInfo, error) {
+ if m.abciInfo != nil {
+ return m.ABCIInfo()
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) ABCIQueryWithOptions(path string, data []byte, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) {
+ if m.abciQueryWithOptions != nil {
+ return m.abciQueryWithOptions(path, data, opts)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
+ if m.broadcastTxAsync != nil {
+ return m.broadcastTxAsync(tx)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
+ if m.broadcastTxSync != nil {
+ return m.broadcastTxSync(tx)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) Genesis() (*ctypes.ResultGenesis, error) {
+ if m.genesis != nil {
+ return m.genesis()
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) {
+ if m.blockchainInfo != nil {
+ return m.blockchainInfo(minHeight, maxHeight)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) NetInfo() (*ctypes.ResultNetInfo, error) {
+ if m.netInfo != nil {
+ return m.netInfo()
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) {
+ if m.dumpConsensusState != nil {
+ return m.dumpConsensusState()
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) ConsensusState() (*ctypes.ResultConsensusState, error) {
+ if m.consensusState != nil {
+ return m.consensusState()
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) ConsensusParams(height *int64) (*ctypes.ResultConsensusParams, error) {
+ if m.consensusParams != nil {
+ return m.consensusParams(height)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) Health() (*ctypes.ResultHealth, error) {
+ if m.health != nil {
+ return m.health()
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) Block(height *int64) (*ctypes.ResultBlock, error) {
+ if m.block != nil {
+ return m.block(height)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) {
+ if m.blockResults != nil {
+ return m.blockResults(height)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) Commit(height *int64) (*ctypes.ResultCommit, error) {
+ if m.commit != nil {
+ return m.commit(height)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) Validators(height *int64) (*ctypes.ResultValidators, error) {
+ if m.validators != nil {
+ return m.validators(height)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) Status() (*ctypes.ResultStatus, error) {
+ if m.status != nil {
+ return m.status()
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) {
+ if m.unconfirmedTxs != nil {
+ return m.unconfirmedTxs(limit)
+ }
+ return nil, nil
+}
+
+func (m *mockRPCClient) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) {
+ if m.numUnconfirmedTxs != nil {
+ return m.numUnconfirmedTxs()
+ }
+ return nil, nil
+}
diff --git a/gno.land/pkg/gnoclient/util.go b/gno.land/pkg/gnoclient/util.go
new file mode 100644
index 00000000000..d9836fe04bb
--- /dev/null
+++ b/gno.land/pkg/gnoclient/util.go
@@ -0,0 +1,22 @@
+package gnoclient
+
+func (cfg BaseTxCfg) validateBaseTxConfig() error {
+ if cfg.GasWanted < 0 {
+ return ErrInvalidGasWanted
+ }
+ if cfg.GasFee < "" {
+ return ErrInvalidGasFee
+ }
+
+ return nil
+}
+
+func (msg MsgCall) validateMsgCall() error {
+ if msg.PkgPath == "" {
+ return ErrEmptyPkgPath
+ }
+ if msg.FuncName == "" {
+ return ErrEmptyFuncName
+ }
+ return nil
+}
From d6f0cde0627fdcd499c9ca2150d0ae61f0ac66df Mon Sep 17 00:00:00 2001
From: Morgan
Date: Thu, 1 Feb 2024 18:53:17 +0100
Subject: [PATCH 03/26] test: add regression integ test for gnolang/gnochess#97
(#1608)
From #1172, pushed to current codebase.
Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>
---
.../gnoland/testdata/issue-gnochess-97.txtar | 41 +++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 gno.land/cmd/gnoland/testdata/issue-gnochess-97.txtar
diff --git a/gno.land/cmd/gnoland/testdata/issue-gnochess-97.txtar b/gno.land/cmd/gnoland/testdata/issue-gnochess-97.txtar
new file mode 100644
index 00000000000..89406d328d4
--- /dev/null
+++ b/gno.land/cmd/gnoland/testdata/issue-gnochess-97.txtar
@@ -0,0 +1,41 @@
+# test for https://github.com/gnolang/gnochess/issues/97
+
+gnoland start
+
+gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/bug97 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1
+
+gnokey maketx call -pkgpath 'gno.land/r/demo/bug97' -func 'RealmCall1' -gas-fee 1000000ugnot -gas-wanted 2000000 -send '' -broadcast -chainid='tendermint_test' test1
+stdout 'OK!'
+
+gnokey maketx call -pkgpath 'gno.land/r/demo/bug97' -func 'RealmCall2' -gas-fee 1000000ugnot -gas-wanted 2000000 -send '' -broadcast -chainid='tendermint_test' test1
+stdout 'OK!'
+
+gnokey maketx call -pkgpath 'gno.land/r/demo/bug97' -func 'RealmCall1' -gas-fee 1000000ugnot -gas-wanted 2000000 -send '' -broadcast -chainid='tendermint_test' test1
+stdout 'OK!'
+
+-- bug97.gno --
+package bug97
+
+var x = [3]int{1, 2, 3}
+
+func newX() [3]int { return x}
+
+type S struct {
+ Arr [3]int
+}
+
+func NewS() S {
+ return S{Arr: x}
+}
+
+var s S
+
+func RealmCall1() {
+ s = NewS()
+}
+
+func RealmCall2() {
+ arr2 := s.Arr
+ arr2[0] = 8
+ s = S{Arr: arr2}
+}
From 988ca914182476a7c196d425b580f9e7647ecdcd Mon Sep 17 00:00:00 2001
From: deelawn
Date: Thu, 1 Feb 2024 22:43:37 +0000
Subject: [PATCH 04/26] feat(sdk/vm): support float as arguments to `maketx
call` (#1434)
Addresses #1427
This allows passing in float types to contract functions
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---------
Co-authored-by: Hariom Verma
---
gno.land/cmd/gnoland/testdata/float-arg.txtar | 24 +++++++
gno.land/pkg/sdk/vm/convert.go | 70 +++++++++++--------
2 files changed, 64 insertions(+), 30 deletions(-)
create mode 100644 gno.land/cmd/gnoland/testdata/float-arg.txtar
diff --git a/gno.land/cmd/gnoland/testdata/float-arg.txtar b/gno.land/cmd/gnoland/testdata/float-arg.txtar
new file mode 100644
index 00000000000..ac684fc8fab
--- /dev/null
+++ b/gno.land/cmd/gnoland/testdata/float-arg.txtar
@@ -0,0 +1,24 @@
+# test for float args
+
+## start a new node
+gnoland start
+
+gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/float_realm -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1
+
+gnokey maketx call -pkgpath gno.land/r/demo/float_realm --func AddF32 -args 10.5 --args 20 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1
+stdout '(30.5 float32)'
+
+gnokey maketx call -pkgpath gno.land/r/demo/float_realm --func AddF64 -args 3.1 --args 2.2 --gas-fee 1000000ugnot --gas-wanted 2000000 --broadcast -chainid=tendermint_test test1
+stdout '(5.3[0-9]* float64)'
+
+-- float_realm.gno --
+package float_realm
+
+func AddF32(x, y float32) float32 {
+ return x + y
+}
+
+func AddF64(x, y float64) float64 {
+ return x + y
+}
+
diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go
index de4db67fb04..f70f99403a8 100644
--- a/gno.land/pkg/sdk/vm/convert.go
+++ b/gno.land/pkg/sdk/vm/convert.go
@@ -5,9 +5,16 @@ import (
"fmt"
"strconv"
+ "github.com/cockroachdb/apd/v3"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
)
+func assertCharNotPlus(b byte) {
+ if b == '+' {
+ panic("numbers cannot start with +")
+ }
+}
+
// These convert string representations of public-facing arguments to GNO types.
// The limited set of input types available should map 1:1 to types supported
// in FunctionSignature{}.
@@ -34,9 +41,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetString(gno.StringValue(arg))
return
case gno.IntType:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
i64, err := strconv.ParseInt(arg, 10, 64)
if err != nil {
panic(fmt.Sprintf(
@@ -46,9 +51,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetInt(int(i64))
return
case gno.Int8Type:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
i8, err := strconv.ParseInt(arg, 10, 8)
if err != nil {
panic(fmt.Sprintf(
@@ -58,9 +61,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetInt8(int8(i8))
return
case gno.Int16Type:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
i16, err := strconv.ParseInt(arg, 10, 16)
if err != nil {
panic(fmt.Sprintf(
@@ -70,9 +71,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetInt16(int16(i16))
return
case gno.Int32Type:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
i32, err := strconv.ParseInt(arg, 10, 32)
if err != nil {
panic(fmt.Sprintf(
@@ -82,9 +81,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetInt32(int32(i32))
return
case gno.Int64Type:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
i64, err := strconv.ParseInt(arg, 10, 64)
if err != nil {
panic(fmt.Sprintf(
@@ -94,9 +91,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetInt64(i64)
return
case gno.UintType:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
u64, err := strconv.ParseUint(arg, 10, 64)
if err != nil {
panic(fmt.Sprintf(
@@ -106,9 +101,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetUint(uint(u64))
return
case gno.Uint8Type:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
u8, err := strconv.ParseUint(arg, 10, 8)
if err != nil {
panic(fmt.Sprintf(
@@ -118,9 +111,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetUint8(uint8(u8))
return
case gno.Uint16Type:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
u16, err := strconv.ParseUint(arg, 10, 16)
if err != nil {
panic(fmt.Sprintf(
@@ -130,9 +121,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetUint16(uint16(u16))
return
case gno.Uint32Type:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
u32, err := strconv.ParseUint(arg, 10, 32)
if err != nil {
panic(fmt.Sprintf(
@@ -142,9 +131,7 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
tv.SetUint32(uint32(u32))
return
case gno.Uint64Type:
- if arg[0] == '+' {
- panic("numbers cannot start with +")
- }
+ assertCharNotPlus(arg[0])
u64, err := strconv.ParseUint(arg, 10, 64)
if err != nil {
panic(fmt.Sprintf(
@@ -153,6 +140,14 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
}
tv.SetUint64(u64)
return
+ case gno.Float32Type:
+ value := convertFloat(arg, 32)
+ tv.SetFloat32(float32(value))
+ return
+ case gno.Float64Type:
+ value := convertFloat(arg, 64)
+ tv.SetFloat64(value)
+ return
default:
panic(fmt.Sprintf("unexpected primitive type %s", bt.String()))
}
@@ -195,3 +190,18 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
panic(fmt.Sprintf("unexpected type in contract arg: %v", argT))
}
}
+
+func convertFloat(value string, precision int) float64 {
+ assertCharNotPlus(value[0])
+ dec, _, err := apd.NewFromString(value)
+ if err != nil {
+ panic(fmt.Sprintf("error parsing float%d %s: %v", precision, value, err))
+ }
+
+ f64, err := strconv.ParseFloat(dec.String(), precision)
+ if err != nil {
+ panic(fmt.Sprintf("error value exceeds float%d precision %s: %v", precision, value, err))
+ }
+
+ return f64
+}
From 37c1c31b0531f4b3f5b87a7c2547dbe7ede8beba Mon Sep 17 00:00:00 2001
From: Leon Hudak <33522493+leohhhn@users.noreply.github.com>
Date: Fri, 2 Feb 2024 12:14:40 +0100
Subject: [PATCH 05/26] fix: add missing args field to `gnoclient` Call (#1616)
## Description
This PR fixes the Gnoclient Call function that left out the MsgCall
arguments when parsing, and introduces integration tests to test this.
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [x] Added references to related issues and PRs
- [x] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---
gno.land/pkg/gnoclient/client_test.go | 2 +-
gno.land/pkg/gnoclient/client_txs.go | 1 +
gno.land/pkg/gnoclient/integration_test.go | 127 +++++++++++++++++++++
3 files changed, 129 insertions(+), 1 deletion(-)
create mode 100644 gno.land/pkg/gnoclient/integration_test.go
diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go
index c7e6b3c6c5b..f5ff9ee3e1d 100644
--- a/gno.land/pkg/gnoclient/client_test.go
+++ b/gno.land/pkg/gnoclient/client_test.go
@@ -168,7 +168,7 @@ func TestClient_CallMultiple(t *testing.T) {
{
PkgPath: "gno.land/r/demo/tamagotchi",
FuncName: "Feed",
- Args: []string{},
+ Args: []string{""},
Send: "",
},
}
diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go
index 9f06217599f..1b791094b86 100644
--- a/gno.land/pkg/gnoclient/client_txs.go
+++ b/gno.land/pkg/gnoclient/client_txs.go
@@ -78,6 +78,7 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx
Caller: c.Signer.Info().GetAddress(),
PkgPath: msg.PkgPath,
Func: msg.FuncName,
+ Args: msg.Args,
Send: send,
})
}
diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go
new file mode 100644
index 00000000000..4d06aaaf81f
--- /dev/null
+++ b/gno.land/pkg/gnoclient/integration_test.go
@@ -0,0 +1,127 @@
+package gnoclient
+
+import (
+ "testing"
+
+ "github.com/gnolang/gno/gno.land/pkg/integration"
+ "github.com/gnolang/gno/gnovm/pkg/gnoenv"
+ 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/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestClient_Call_Single_Integration(t *testing.T) {
+ // Set up in-memory node
+ config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir())
+ node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config)
+ defer node.Stop()
+
+ // Init Signer & RPCClient
+ signer := newInMemorySigner(t, "tendermint_test")
+ rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")
+
+ // Setup Client
+ client := Client{
+ Signer: signer,
+ RPCClient: rpcClient,
+ }
+
+ // Make Tx config
+ baseCfg := BaseTxCfg{
+ GasFee: "10000ugnot",
+ GasWanted: 8000000,
+ AccountNumber: 0,
+ SequenceNumber: 0,
+ Memo: "",
+ }
+
+ // Make Msg config
+ msg := MsgCall{
+ PkgPath: "gno.land/r/demo/deep/very/deep",
+ FuncName: "Render",
+ Args: []string{"test argument"},
+ Send: "",
+ }
+
+ // Execute call
+ res, err := client.Call(baseCfg, msg)
+
+ expected := "(\"hi test argument\" string)"
+ got := string(res.DeliverTx.Data)
+
+ assert.Nil(t, err)
+ assert.Equal(t, expected, got)
+}
+
+func TestClient_Call_Multiple_Integration(t *testing.T) {
+ // Set up in-memory node
+ config, _ := integration.TestingNodeConfig(t, gnoenv.RootDir())
+ node, remoteAddr := integration.TestingInMemoryNode(t, log.NewNoopLogger(), config)
+ defer node.Stop()
+
+ // Init Signer & RPCClient
+ signer := newInMemorySigner(t, "tendermint_test")
+ rpcClient := rpcclient.NewHTTP(remoteAddr, "/websocket")
+
+ // Setup Client
+ client := Client{
+ Signer: signer,
+ RPCClient: rpcClient,
+ }
+
+ // Make Tx config
+ baseCfg := BaseTxCfg{
+ GasFee: "10000ugnot",
+ GasWanted: 8000000,
+ AccountNumber: 0,
+ SequenceNumber: 0,
+ Memo: "",
+ }
+
+ // Make Msg configs
+ msg1 := MsgCall{
+ PkgPath: "gno.land/r/demo/deep/very/deep",
+ FuncName: "Render",
+ Args: []string{""},
+ Send: "",
+ }
+
+ // Same call, different argument
+ msg2 := MsgCall{
+ PkgPath: "gno.land/r/demo/deep/very/deep",
+ FuncName: "Render",
+ Args: []string{"test argument"},
+ Send: "",
+ }
+
+ expected := "(\"it works!\" string)(\"hi test argument\" string)"
+
+ // Execute call
+ res, err := client.Call(baseCfg, msg1, msg2)
+
+ got := string(res.DeliverTx.Data)
+ assert.Nil(t, err)
+ assert.Equal(t, expected, got)
+}
+
+// todo add more integration tests.
+
+func newInMemorySigner(t *testing.T, chainid string) *SignerFromKeybase {
+ t.Helper()
+
+ mnemonic := integration.DefaultAccount_Seed
+ name := integration.DefaultAccount_Name
+
+ kb := keys.NewInMemory()
+ _, err := kb.CreateAccount(name, mnemonic, "", "", uint32(0), uint32(0))
+ require.NoError(t, err)
+
+ return &SignerFromKeybase{
+ Keybase: kb, // Stores keys in memory or on disk
+ Account: name, // Account name or bech32 format
+ Password: "", // Password for encryption
+ ChainID: chainid, // Chain ID for transaction signing
+ }
+}
From 05dcccee4a3e5fcec1903020c637eaf2a37d527e Mon Sep 17 00:00:00 2001
From: Alexis Colin
Date: Mon, 5 Feb 2024 12:57:33 +0100
Subject: [PATCH 06/26] docs: add edit page link and info (#1629)
Related to #1505,This PR aims to add the edit page option in order to
link the source `md` file to docs page. So readers will be invited to
edit the page to improve it. It also add the last update date, so
readers can know if the page is up to date.
---
misc/docusaurus/docusaurus.config.js | 4 +++-
misc/docusaurus/src/css/custom.css | 3 ++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/misc/docusaurus/docusaurus.config.js b/misc/docusaurus/docusaurus.config.js
index 5fdbbacf98c..8a29ef52006 100644
--- a/misc/docusaurus/docusaurus.config.js
+++ b/misc/docusaurus/docusaurus.config.js
@@ -28,7 +28,7 @@ const config = {
src: "https://sa.gno.services/latest.js",
async: true,
defer: true,
- }
+ },
],
presets: [
@@ -40,6 +40,8 @@ const config = {
path: "../../docs",
routeBasePath: "/",
sidebarPath: require.resolve("./sidebars.js"),
+ showLastUpdateTime: true,
+ editUrl: ({ docPath }) => `https://github.com/gnolang/gno/edit/master/docs/${docPath}`,
},
blog: false,
theme: {
diff --git a/misc/docusaurus/src/css/custom.css b/misc/docusaurus/src/css/custom.css
index 9955c1cae78..5f644ce3fa0 100644
--- a/misc/docusaurus/src/css/custom.css
+++ b/misc/docusaurus/src/css/custom.css
@@ -118,7 +118,8 @@ body nav[class*="navbarHidden"] {
}
}
-.theme-doc-markdown {
+.theme-doc-markdown,
+.theme-doc-footer {
max-width: var(--content-max-w, 700px);
}
From 44ee7711e374b9645f69375318556712fbc23c19 Mon Sep 17 00:00:00 2001
From: Morgan
Date: Mon, 5 Feb 2024 19:01:26 +0100
Subject: [PATCH 07/26] chore(examples): prefer Go and Gno for respective
language names (#1607)
More on my crusade for right terminology and avoid dooming us to have
"gnolang" as the official language name.
See-also #1548
---
CONTRIBUTING.md | 10 +++++-----
examples/gno.land/r/gnoland/home/home.gno | 6 +++---
examples/gno.land/r/gnoland/home/home_filetest.gno | 6 +++---
examples/gno.land/r/gnoland/pages/page_about.gno | 10 +++++-----
examples/gno.land/r/gnoland/pages/page_gnolang.gno | 6 +++---
examples/gno.land/r/gnoland/pages/pages_test.gno | 2 +-
6 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a3f34b8a85e..4a2e8485d06 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -52,7 +52,7 @@ Likewise, if you have an idea on how to improve this guide, go for it as well.
### Environment
-The gno repository is primarily based on Golang (Go) and Gnolang (Gno).
+The gno repository is primarily based on Go (Golang) and Gno.
The primary tech stack for working on the repository:
@@ -63,7 +63,7 @@ It is recommended to work on a Unix environment, as most of the tooling is built
for Windows / Linux / macOS).
For Gno, there is no specific tooling that needs to be installed, that’s not already provided with the repo itself.
-You can utilize the `gno` command to facilitate Gnolang support when writing Smart Contracts in Gno, by installing it
+You can utilize the `gno` command to facilitate Gno support when writing Smart Contracts in Gno, by installing it
with `make install_gno`.
If you are working on Go source code on this repository, `pkg.go.dev` will not
@@ -149,16 +149,16 @@ if (executable('gnols'))
else
echomsg 'gnols binary not found: LSP disabled for Gno files'
endif
-
+
function! s:on_lsp_buffer_enabled() abort
" Autocompletion
setlocal omnifunc=lsp#complete
" Format on save
autocmd BufWritePre LspDocumentFormatSync
" Some optional mappings
- nmap i (lsp-hover)
+ nmap i (lsp-hover)
" Following mappings are not supported yet by gnols
- " nmap gd (lsp-definition)
+ " nmap gd (lsp-definition)
" nmap rr (lsp-rename)
endfunction
augroup lsp_install
diff --git a/examples/gno.land/r/gnoland/home/home.gno b/examples/gno.land/r/gnoland/home/home.gno
index 14da10dd63a..a7796dc3f46 100644
--- a/examples/gno.land/r/gnoland/home/home.gno
+++ b/examples/gno.land/r/gnoland/home/home.gno
@@ -73,7 +73,7 @@ func upcomingEvents(limit int) ui.Element {
func introSection() ui.Element {
return ui.Element{
- ui.H3("An interpretation of the Golang (Go) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination."),
+ ui.H3("An interpretation of the Go (Golang) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination."),
ui.Paragraph("If you’re concerned about information censorship and want to contribute to the #GnoWorldOrder, follow our socials to find out how."),
ui.Paragraph("Gno.land is in building mode. If you want to help lay the foundations of a fairer and freer world through innovative ideas and exceptional code, join us today."),
}
@@ -220,12 +220,12 @@ func discoverLinks() ui.Element {
-### Build with Gnolang
+### Build with Gno
- [Gno dev with CLI (soon)](#)
- [Explore the Universe](/ecosystem)
- [Test in the browser (soon)](#)
-- [About the Gnolang Language](/gnolang)
+- [About the Gno Language](/gnolang)
- [Docs/ Tutorials](https://github.com/gnolang)
- [Gno by example](https://gno-by-example.com/)
- [Getting started video (soon)](#)
diff --git a/examples/gno.land/r/gnoland/home/home_filetest.gno b/examples/gno.land/r/gnoland/home/home_filetest.gno
index e87e5917676..014bdf96136 100644
--- a/examples/gno.land/r/gnoland/home/home_filetest.gno
+++ b/examples/gno.land/r/gnoland/home/home_filetest.gno
@@ -9,7 +9,7 @@ func main() {
// Output:
// # Welcome to Gno.land
//
-// ### An interpretation of the Golang (Go) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination.
+// ### An interpretation of the Go (Golang) programming language for advanced developers and intrepid pioneers to build succinct, composable smart contracts for social coordination.
//
//
// If you’re concerned about information censorship and want to contribute to the #GnoWorldOrder, follow our socials to find out how.
@@ -124,12 +124,12 @@ func main() {
//
//
//
-// ### Build with Gnolang
+// ### Build with Gno
//
// - [Gno dev with CLI (soon)](#)
// - [Explore the Universe](/ecosystem)
// - [Test in the browser (soon)](#)
-// - [About the Gnolang Language](/gnolang)
+// - [About the Gno Language](/gnolang)
// - [Docs/ Tutorials](https://github.com/gnolang)
// - [Gno by example](https://gno-by-example.com/)
// - [Getting started video (soon)](#)
diff --git a/examples/gno.land/r/gnoland/pages/page_about.gno b/examples/gno.land/r/gnoland/pages/page_about.gno
index 9aba4e39f76..80c43c1741d 100644
--- a/examples/gno.land/r/gnoland/pages/page_about.gno
+++ b/examples/gno.land/r/gnoland/pages/page_about.gno
@@ -2,19 +2,19 @@ package gnopages
func init() {
path := "about"
- title := "Gno.land Is A Platform To Write Smart Contracts In Gnolang (Gno)"
- // XXX: description := "On Gno.land, developers write smart contracts and other blockchain apps using Gnolang (Gno) without learning a language that’s exclusive to a single ecosystem."
+ title := "Gno.land Is A Platform To Write Smart Contracts In Gno"
+ // XXX: description := "On Gno.land, developers write smart contracts and other blockchain apps using Gno without learning a language that’s exclusive to a single ecosystem."
body := `# About Gno.land
-Gno.land is a platform to write smart contracts in Gnolang (Gno).
-Using an interpreted version of the general-purpose programming language Golang (Go), developers can write smart contracts and other blockchain apps without having to learn a language that’s exclusive to a single ecosystem.
+Gno.land is a platform to write smart contracts in Gno.
+Using an interpreted version of the general-purpose programming language Go (Golang), developers can write smart contracts and other blockchain apps without having to learn a language that’s exclusive to a single ecosystem.
Web2 developers can easily contribute to web3 and start building a more transparent, accountable world.
The Gno transaction token, GNOT, and the contributor memberships power the platform, which runs on a variation of Proof of Stake.
Proof of Contribution rewards contributors from technical and non-technical backgrounds, fairly and for life with GNOT.
This consensus mechanism also achieves higher security with fewer validators, optimizing resources for a greener, more sustainable, and enduring blockchain ecosystem.
-Any blockchain using Gnolang achieves succinctness, composability, expressivity, and completeness not found in any other smart contract platform.
+Any blockchain using Gno achieves succinctness, composability, expressivity, and completeness not found in any other smart contract platform.
By observing a minimal structure, the design can endure over time and challenge the regime of information censorship we’re living in today.`
_ = b.NewPost("", path, title, body, nil)
}
diff --git a/examples/gno.land/r/gnoland/pages/page_gnolang.gno b/examples/gno.land/r/gnoland/pages/page_gnolang.gno
index f0c2bfe276d..ecbadab9f01 100644
--- a/examples/gno.land/r/gnoland/pages/page_gnolang.gno
+++ b/examples/gno.land/r/gnoland/pages/page_gnolang.gno
@@ -3,11 +3,11 @@ package gnopages
func init() {
var (
path = "gnolang"
- title = "Gnolang (Gno) Is a Complete Language for Blockchain"
+ title = "Gno Is a Complete Language for Blockchain"
// XXX: description = "Gnolang (Gno) is an interpretation of the popular Golang (Go) language for blockchain created by Tendermint and Cosmos founder Jae Kwon."
- body = `# About the Gnolang, the Gno Language
+ body = `# About the Gno, the Language for Gno.land
-[Gnolang](https://github.com/gnolang/gno/blob/master/LICENSE.md) (Gno) is an interpretation of the widely-used Golang (Go) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same.
+[Gno](https://github.com/gnolang/gno/blob/master/LICENSE.md) is an interpretation of the widely-used Go (Golang) programming language for blockchain created by Cosmos co-founder Jae Kwon in 2022 to mark a new era in smart contracting. Gno is ~99% identical to Go, so Go programmers can start coding in Gno right away, with a minimal learning curve. For example, Gno comes with blockchain-specific standard libraries, but any code that doesn’t use blockchain-specific logic can run in Go with minimal processing. Libraries that don’t make sense in the blockchain context, such as network or operating-system access, are not available in Gno. Otherwise, Gno loads and uses many standard libraries that power Go, so most of the parsing of the source code is the same.
Under the hood, the Gno code is parsed into an abstract syntax tree (AST) and the AST itself is used in the interpreter, rather than bytecode as in many virtual machines such as Java, Python, or Wasm. This makes even the GnoVM accessible to any Go programmer. The novel design of the intuitive GnoVM interpreter allows Gno to freeze and resume the program by persisting and loading the entire memory state. Gno is deterministic, auto-persisted, and auto-Merkle-ized, allowing (smart contract) programs to be succinct, as the programmer doesn’t have to serialize and deserialize objects to persist them into a database (unlike programming applications with the Cosmos SDK).
diff --git a/examples/gno.land/r/gnoland/pages/pages_test.gno b/examples/gno.land/r/gnoland/pages/pages_test.gno
index 5a6fe84ad38..0119ac78985 100644
--- a/examples/gno.land/r/gnoland/pages/pages_test.gno
+++ b/examples/gno.land/r/gnoland/pages/pages_test.gno
@@ -33,7 +33,7 @@ func TestAbout(t *testing.T) {
got := Render("p/about")
expectedSubtrings := []string{
"# About Gno.land",
- "Gno.land is a platform to write smart contracts in Gnolang (Gno).",
+ "Gno.land is a platform to write smart contracts in Gno.",
}
for _, substring := range expectedSubtrings {
if !strings.Contains(got, substring) {
From 846956f5a12846dead5b88f0332b13f654749a8d Mon Sep 17 00:00:00 2001
From: Alexis Colin
Date: Tue, 6 Feb 2024 14:04:45 +0100
Subject: [PATCH 08/26] docs: add links into navbar (#1627)
This PR aims to add following links into the docs website navbar:
`Playground`, `blog` and `Gno.Land homepage`.
I chose to avoid setting GnoLand label as `home` in order to avoid a
misunderstanding between `docs home` and `Gno.Land home`. But we can
discuss more about the label and its position here.
Close: #1527
---
misc/docusaurus/docusaurus.config.js | 16 ++++++++++++++++
misc/docusaurus/src/css/custom.css | 9 +++++++++
2 files changed, 25 insertions(+)
diff --git a/misc/docusaurus/docusaurus.config.js b/misc/docusaurus/docusaurus.config.js
index 8a29ef52006..6daec1be821 100644
--- a/misc/docusaurus/docusaurus.config.js
+++ b/misc/docusaurus/docusaurus.config.js
@@ -63,12 +63,28 @@ const config = {
srcDark: "img/gnoland_light.svg",
},
items: [
+ {
+ position: "right",
+ label: "Back to Gno.Land",
+ to: "https://gno.land",
+ className: "gno-header__copy",
+ },
{
type: "docSidebar",
sidebarId: "tutorialSidebar",
position: "left",
label: "Docs",
},
+ {
+ position: "left",
+ label: "Playground",
+ to: "https://play.gno.land",
+ },
+ {
+ position: "left",
+ label: "Blog",
+ to: "https://test3.gno.land/r/gnoland/blog",
+ },
{
href: "https://github.com/gnolang/gno",
html: `