Skip to content

Commit

Permalink
chore(gnoclient): Add Send support (#1639)
Browse files Browse the repository at this point in the history
gnoclient already supports MsgCall and MsgRun. This adds `Send` to
support MsgSend.

<details><summary>Contributors' checklist...</summary>

- [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
- [x] 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).
</details>

---------

Signed-off-by: Jeff Thompson <[email protected]>
Co-authored-by: Leon Hudak <[email protected]>
  • Loading branch information
jefft0 and leohhhn authored Feb 9, 2024
1 parent 9734695 commit 2b15731
Show file tree
Hide file tree
Showing 4 changed files with 386 additions and 8 deletions.
190 changes: 190 additions & 0 deletions gno.land/pkg/gnoclient/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,193 @@ func TestClient_Call_Errors(t *testing.T) {
})
}
}

func TestClient_Send_Errors(t *testing.T) {
t.Parallel()

toAddress, _ := crypto.AddressFromBech32("g14a0y9a64dugh3l7hneshdxr4w0rfkkww9ls35p")
testCases := []struct {
name string
client Client
cfg BaseTxCfg
msgs []MsgSend
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: []MsgSend{
{
ToAddress: toAddress,
Send: "1ugnot",
},
},
expectedError: ErrMissingSigner,
},
{
name: "Invalid RPCClient",
client: Client{
&mockSigner{},
nil,
},
cfg: BaseTxCfg{
GasWanted: 100000,
GasFee: "10000ugnot",
AccountNumber: 1,
SequenceNumber: 1,
Memo: "Test memo",
},
msgs: []MsgSend{
{
ToAddress: toAddress,
Send: "1ugnot",
},
},
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: []MsgSend{
{
ToAddress: toAddress,
Send: "1ugnot",
},
},
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: []MsgSend{
{
ToAddress: toAddress,
Send: "1ugnot",
},
},
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: []MsgSend{
{
ToAddress: toAddress,
Send: "1ugnot",
},
},
expectedError: ErrInvalidGasWanted,
},
{
name: "Invalid To Address",
client: Client{
Signer: &mockSigner{
info: func() keys.Info {
return &mockKeysInfo{
getAddress: func() crypto.Address {
adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
return adr
},
}
},
},
RPCClient: &mockRPCClient{},
},
cfg: BaseTxCfg{
GasWanted: 100000,
GasFee: "10000ugnot",
AccountNumber: 1,
SequenceNumber: 1,
Memo: "Test memo",
},
msgs: []MsgSend{
{
ToAddress: crypto.Address{},
Send: "1ugnot",
},
},
expectedError: ErrInvalidToAddress,
},
{
name: "Invalid Send Coins",
client: Client{
Signer: &mockSigner{
info: func() keys.Info {
return &mockKeysInfo{
getAddress: func() crypto.Address {
adr, _ := crypto.AddressFromBech32("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
return adr
},
}
},
},
RPCClient: &mockRPCClient{},
},
cfg: BaseTxCfg{
GasWanted: 100000,
GasFee: "10000ugnot",
AccountNumber: 1,
SequenceNumber: 1,
Memo: "Test memo",
},
msgs: []MsgSend{
{
ToAddress: toAddress,
Send: "-1ugnot",
},
},
expectedError: ErrInvalidSendAmount,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

res, err := tc.client.Send(tc.cfg, tc.msgs...)
assert.Nil(t, res)
assert.ErrorIs(t, err, tc.expectedError)
})
}
}
82 changes: 76 additions & 6 deletions gno.land/pkg/gnoclient/client_txs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ import (
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/tm2/pkg/amino"
ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types"
"github.com/gnolang/gno/tm2/pkg/crypto"
"github.com/gnolang/gno/tm2/pkg/errors"
"github.com/gnolang/gno/tm2/pkg/sdk/bank"
"github.com/gnolang/gno/tm2/pkg/std"
)

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")
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")
ErrInvalidToAddress = errors.New("invalid send to address")
ErrInvalidSendAmount = errors.New("invalid send amount")
)

type BaseTxCfg struct {
Expand All @@ -34,6 +38,12 @@ type MsgCall struct {
Send string // Send amount
}

// MsgSend - syntax sugar for bank.MsgSend minus fields in BaseTxCfg
type MsgSend struct {
ToAddress crypto.Address // Send to address
Send string // Send amount
}

// RunCfg contains configuration options for running a temporary package on the blockchain.
type RunCfg struct {
Package *std.MemPackage
Expand Down Expand Up @@ -106,6 +116,66 @@ func (c *Client) Call(cfg BaseTxCfg, msgs ...MsgCall) (*ctypes.ResultBroadcastTx
return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber)
}

// Send currency to an account on the blockchain.
func (c *Client) Send(cfg BaseTxCfg, msgs ...MsgSend) (*ctypes.ResultBroadcastTxCommit, error) {
// Validate required client fields.
if err := c.validateSigner(); err != nil {
return nil, err
}
if err := c.validateRPCClient(); err != nil {
return nil, err
}

// Validate base transaction config
if err := cfg.validateBaseTxConfig(); err != nil {
return nil, err
}

// Parse MsgSend slice
vmMsgs := make([]bank.MsgSend, 0, len(msgs))
for _, msg := range msgs {
// Validate MsgSend fields
if err := msg.validateMsgSend(); 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.MsgSend slice
vmMsgs = append(vmMsgs, bank.MsgSend{
FromAddress: c.Signer.Info().GetAddress(),
ToAddress: msg.ToAddress,
Amount: send,
})
}

// Cast vm.MsgSend back into std.Msg
stdMsgs := make([]std.Msg, len(vmMsgs))
for i, msg := range vmMsgs {
stdMsgs[i] = msg
}

// Parse gas fee
gasFeeCoins, err := std.ParseCoin(cfg.GasFee)
if err != nil {
return nil, err
}

// Pack transaction
tx := std.Tx{
Msgs: stdMsgs,
Fee: std.NewFee(cfg.GasWanted, gasFeeCoins),
Signatures: nil,
Memo: cfg.Memo,
}

return c.signAndBroadcastTxCommit(tx, cfg.AccountNumber, cfg.SequenceNumber)
}

// Temporarily load cfg.Package on the blockchain and run main() which can
// call realm functions and use println() to output to the "console".
// This returns bres where string(bres.DeliverTx.Data) is the "console" output.
Expand Down
Loading

0 comments on commit 2b15731

Please sign in to comment.