diff --git a/gno.land/pkg/gnoclient/client_test.go b/gno.land/pkg/gnoclient/client_test.go index d7795f918bf..b7eb21837a7 100644 --- a/gno.land/pkg/gnoclient/client_test.go +++ b/gno.land/pkg/gnoclient/client_test.go @@ -115,7 +115,13 @@ func TestCallSingle(t *testing.T) { res, err := client.Call(cfg, msg...) assert.NoError(t, err) require.NotNil(t, res) - assert.Equal(t, string(res.DeliverTx.Data), "it works!") + expected := "it works!" + assert.Equal(t, string(res.DeliverTx.Data), expected) + + res, err = callSigningSeparately(t, client, cfg, msg...) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), expected) } func TestCallMultiple(t *testing.T) { @@ -192,6 +198,10 @@ func TestCallMultiple(t *testing.T) { res, err := client.Call(cfg, msg...) assert.NoError(t, err) assert.NotNil(t, res) + + res, err = callSigningSeparately(t, client, cfg, msg...) + assert.NoError(t, err) + assert.NotNil(t, res) } func TestCallErrors(t *testing.T) { @@ -656,7 +666,13 @@ func main() { res, err := client.Run(cfg, msg) assert.NoError(t, err) require.NotNil(t, res) - assert.Equal(t, "hi gnoclient!\n", string(res.DeliverTx.Data)) + expected := "hi gnoclient!\n" + assert.Equal(t, expected, string(res.DeliverTx.Data)) + + res, err = runSigningSeparately(t, client, cfg, msg) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, expected, string(res.DeliverTx.Data)) } func TestRunMultiple(t *testing.T) { @@ -740,7 +756,13 @@ func main() { res, err := client.Run(cfg, msg1, msg2) assert.NoError(t, err) require.NotNil(t, res) - assert.Equal(t, "hi gnoclient!\nhi gnoclient!\n", string(res.DeliverTx.Data)) + expected := "hi gnoclient!\nhi gnoclient!\n" + assert.Equal(t, expected, string(res.DeliverTx.Data)) + + res, err = runSigningSeparately(t, client, cfg, msg1, msg2) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, expected, string(res.DeliverTx.Data)) } func TestRunErrors(t *testing.T) { @@ -1326,3 +1348,63 @@ func TestLatestBlockHeightErrors(t *testing.T) { }) } } + +// The same as client.Call, but test signing separately +func callSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msgs ...vm.MsgCall) (*ctypes.ResultBroadcastTxCommit, error) { + t.Helper() + tx, err := NewCallTx(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err := client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + return res, nil +} + +// The same as client.Run, but test signing separately +func runSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msgs ...vm.MsgRun) (*ctypes.ResultBroadcastTxCommit, error) { + t.Helper() + tx, err := NewRunTx(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err := client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + return res, nil +} + +// The same as client.Send, but test signing separately +func sendSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msgs ...bank.MsgSend) (*ctypes.ResultBroadcastTxCommit, error) { + t.Helper() + tx, err := NewSendTx(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err := client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + return res, nil +} + +// The same as client.AddPackage, but test signing separately +func addPackageSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msgs ...vm.MsgAddPackage) (*ctypes.ResultBroadcastTxCommit, error) { + t.Helper() + tx, err := NewAddPackageTx(cfg, msgs...) + assert.NoError(t, err) + require.NotNil(t, tx) + signedTx, err := client.SignTx(*tx, cfg.AccountNumber, cfg.SequenceNumber) + assert.NoError(t, err) + require.NotNil(t, signedTx) + res, err := client.BroadcastTxCommit(signedTx) + assert.NoError(t, err) + require.NotNil(t, res) + return res, nil +} diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index c113ea21944..9d3dbde22ae 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -35,6 +35,16 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...vm.MsgCall) (*ctypes.ResultBroadcas return nil, err } + tx, err := NewCallTx(cfg, msgs...) + if err != nil { + return nil, err + } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// NewCallTx makes an unsigned transaction from one or more MsgCall. +// The Caller field must be set. +func NewCallTx(cfg BaseTxCfg, msgs ...vm.MsgCall) (*std.Tx, error) { // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { return nil, err @@ -57,14 +67,12 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...vm.MsgCall) (*ctypes.ResultBroadcas } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // Run executes one or more MsgRun calls on the blockchain @@ -77,6 +85,16 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...vm.MsgRun) (*ctypes.ResultBroadcastT return nil, err } + tx, err := NewRunTx(cfg, msgs...) + if err != nil { + return nil, err + } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// NewRunTx makes an unsigned transaction from one or more MsgRun. +// The Caller field must be set. +func NewRunTx(cfg BaseTxCfg, msgs ...vm.MsgRun) (*std.Tx, error) { // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { return nil, err @@ -99,14 +117,12 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...vm.MsgRun) (*ctypes.ResultBroadcastT } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // Send executes one or more MsgSend calls on the blockchain @@ -119,6 +135,16 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...bank.MsgSend) (*ctypes.ResultBroadc return nil, err } + tx, err := NewSendTx(cfg, msgs...) + if err != nil { + return nil, err + } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// NewSendTx makes an unsigned transaction from one or more MsgSend. +// The FromAddress field must be set. +func NewSendTx(cfg BaseTxCfg, msgs ...bank.MsgSend) (*std.Tx, error) { // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { return nil, err @@ -141,14 +167,12 @@ func (c *Client) Send(cfg BaseTxCfg, msgs ...bank.MsgSend) (*ctypes.ResultBroadc } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // AddPackage executes one or more AddPackage calls on the blockchain @@ -161,6 +185,16 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...vm.MsgAddPackage) (*ctypes.Re return nil, err } + tx, err := NewAddPackageTx(cfg, msgs...) + if err != nil { + return nil, err + } + return c.signAndBroadcastTxCommit(*tx, cfg.AccountNumber, cfg.SequenceNumber) +} + +// NewAddPackageTx makes an unsigned transaction from one or more MsgAddPackage. +// The Creator field must be set. +func NewAddPackageTx(cfg BaseTxCfg, msgs ...vm.MsgAddPackage) (*std.Tx, error) { // Validate base transaction config if err := cfg.validateBaseTxConfig(); err != nil { return nil, err @@ -183,18 +217,29 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...vm.MsgAddPackage) (*ctypes.Re } // Pack transaction - tx := std.Tx{ + return &std.Tx{ Msgs: vmMsgs, Fee: std.NewFee(cfg.GasWanted, gasFeeCoins), Signatures: nil, Memo: cfg.Memo, - } - - return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber) + }, nil } // signAndBroadcastTxCommit signs a transaction and broadcasts it, returning the result func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumber uint64) (*ctypes.ResultBroadcastTxCommit, error) { + signedTx, err := c.SignTx(tx, accountNumber, sequenceNumber) + if err != nil { + return nil, err + } + return c.BroadcastTxCommit(signedTx) +} + +// SignTx signs a transaction and returns a signed tx ready for broadcasting. +// If accountNumber or sequenceNumber is 0 then query the blockchain for the value. +func (c *Client) SignTx(tx std.Tx, accountNumber, sequenceNumber uint64) (*std.Tx, error) { + if err := c.validateSigner(); err != nil { + return nil, err + } caller, err := c.Signer.Info() if err != nil { return nil, err @@ -218,7 +263,15 @@ func (c *Client) signAndBroadcastTxCommit(tx std.Tx, accountNumber, sequenceNumb if err != nil { return nil, errors.Wrap(err, "sign") } + return signedTx, nil +} +// BroadcastTxCommit marshals and broadcasts the signed transaction, returning the result. +// If the result has a delivery error, then return a wrapped error. +func (c *Client) BroadcastTxCommit(signedTx *std.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + if err := c.validateRPCClient(); err != nil { + return nil, err + } bz, err := amino.Marshal(signedTx) if err != nil { return nil, errors.Wrap(err, "marshaling tx binary bytes") diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go index f2e5026aa9a..ea068e0680b 100644 --- a/gno.land/pkg/gnoclient/integration_test.go +++ b/gno.land/pkg/gnoclient/integration_test.go @@ -66,6 +66,11 @@ func TestCallSingle_Integration(t *testing.T) { got := string(res.DeliverTx.Data) assert.Equal(t, expected, got) + + res, err = callSigningSeparately(t, client, baseCfg, msg) + require.NoError(t, err) + got = string(res.DeliverTx.Data) + assert.Equal(t, expected, got) } func TestCallMultiple_Integration(t *testing.T) { @@ -123,6 +128,11 @@ func TestCallMultiple_Integration(t *testing.T) { got := string(res.DeliverTx.Data) assert.Equal(t, expected, got) + + res, err = callSigningSeparately(t, client, baseCfg, msg1, msg2) + require.NoError(t, err) + got = string(res.DeliverTx.Data) + assert.Equal(t, expected, got) } func TestSendSingle_Integration(t *testing.T) { @@ -176,6 +186,17 @@ func TestSendSingle_Integration(t *testing.T) { got := account.GetCoins() assert.Equal(t, expected, got) + + res, err = sendSigningSeparately(t, client, baseCfg, msg) + require.NoError(t, err) + assert.Equal(t, "", string(res.DeliverTx.Data)) + + // Get the new account balance + account, _, err = client.QueryAccount(toAddress) + require.NoError(t, err) + expected2 := std.Coins{{Denom: ugnot.Denom, Amount: int64(2 * amount)}} + got = account.GetCoins() + assert.Equal(t, expected2, got) } func TestSendMultiple_Integration(t *testing.T) { @@ -237,6 +258,17 @@ func TestSendMultiple_Integration(t *testing.T) { got := account.GetCoins() assert.Equal(t, expected, got) + + res, err = sendSigningSeparately(t, client, baseCfg, msg1, msg2) + require.NoError(t, err) + assert.Equal(t, "", string(res.DeliverTx.Data)) + + // Get the new account balance + account, _, err = client.QueryAccount(toAddress) + require.NoError(t, err) + expected2 := std.Coins{{Denom: ugnot.Denom, Amount: int64(2 * (amount1 + amount2))}} + got = account.GetCoins() + assert.Equal(t, expected2, got) } // Run tests @@ -300,6 +332,11 @@ func main() { assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, string(res.DeliverTx.Data), "- before: 0\n- after: 10\n") + + res, err = runSigningSeparately(t, client, baseCfg, msg) + assert.NoError(t, err) + require.NotNil(t, res) + assert.Equal(t, string(res.DeliverTx.Data), "- before: 10\n- after: 20\n") } // Run tests @@ -387,6 +424,12 @@ func main() { assert.NoError(t, err) require.NotNil(t, res) assert.Equal(t, expected, string(res.DeliverTx.Data)) + + res, err = runSigningSeparately(t, client, baseCfg, msg1, msg2) + require.NoError(t, err) + require.NotNil(t, res) + expected2 := "- before: 10\n- after: 20\nhi gnoclient!\n" + assert.Equal(t, expected2, string(res.DeliverTx.Data)) } func TestAddPackageSingle_Integration(t *testing.T) { @@ -460,6 +503,18 @@ func Echo(str string) string { baseAcc, _, err := client.QueryAccount(gnolang.DerivePkgAddr(deploymentPath)) require.NoError(t, err) assert.Equal(t, baseAcc.GetCoins(), deposit) + + // Test signing separately (using a different deployment path) + deploymentPathB := "gno.land/p/demo/integration/test/echo2" + msg.Package.Path = deploymentPathB + _, err = addPackageSigningSeparately(t, client, baseCfg, msg) + assert.NoError(t, err) + query, err = client.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPathB), + }) + require.NoError(t, err) + assert.Equal(t, string(query.Response.Data), fileName) } func TestAddPackageMultiple_Integration(t *testing.T) { @@ -571,6 +626,27 @@ func Hello(str string) string { baseAcc, _, err = client.QueryAccount(gnolang.DerivePkgAddr(deploymentPath2)) require.NoError(t, err) assert.Equal(t, baseAcc.GetCoins(), deposit) + + // Test signing separately (using a different deployment path) + deploymentPath1B := "gno.land/p/demo/integration/test/echo2" + deploymentPath2B := "gno.land/p/demo/integration/test/hello2" + msg1.Package.Path = deploymentPath1B + msg2.Package.Path = deploymentPath2B + _, err = addPackageSigningSeparately(t, client, baseCfg, msg1, msg2) + assert.NoError(t, err) + query, err = client.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPath1B), + }) + require.NoError(t, err) + assert.Equal(t, string(query.Response.Data), "echo.gno") + query, err = client.Query(QueryCfg{ + Path: "vm/qfile", + Data: []byte(deploymentPath2B), + }) + require.NoError(t, err) + assert.Contains(t, string(query.Response.Data), "hello.gno") + assert.Contains(t, string(query.Response.Data), "gno.mod") } // todo add more integration tests: