Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(gnoclient): Add Send support #1639

Merged
merged 4 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 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,192 @@ 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 {
t.Run(tc.name, func(t *testing.T) {
jefft0 marked this conversation as resolved.
Show resolved Hide resolved
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 @@
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 @@
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 @@
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
}

Check warning on line 124 in gno.land/pkg/gnoclient/client_txs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoclient/client_txs.go#L123-L124

Added lines #L123 - L124 were not covered by tests
if err := c.validateRPCClient(); err != nil {
return nil, err
}

Check warning on line 127 in gno.land/pkg/gnoclient/client_txs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoclient/client_txs.go#L126-L127

Added lines #L126 - L127 were not covered by tests

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

Check warning on line 132 in gno.land/pkg/gnoclient/client_txs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoclient/client_txs.go#L131-L132

Added lines #L131 - L132 were not covered by tests

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

Check warning on line 146 in gno.land/pkg/gnoclient/client_txs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoclient/client_txs.go#L145-L146

Added lines #L145 - L146 were not covered by tests

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

Check warning on line 166 in gno.land/pkg/gnoclient/client_txs.go

View check run for this annotation

Codecov / codecov/patch

gno.land/pkg/gnoclient/client_txs.go#L165-L166

Added lines #L165 - L166 were not covered by tests

// 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
Loading