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