Skip to content

Commit

Permalink
Merge branch 'master' into gno-revert-3250
Browse files Browse the repository at this point in the history
  • Loading branch information
mvertes authored Jan 14, 2025
2 parents ee03341 + d54d004 commit 9007d55
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 1 deletion.
157 changes: 157 additions & 0 deletions gno.land/pkg/gnoclient/client_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package gnoclient

import (
"errors"
"testing"

"github.com/gnolang/gno/tm2/pkg/amino"
abciErrors "github.com/gnolang/gno/tm2/pkg/bft/abci/example/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -1409,3 +1412,157 @@ func addPackageSigningSeparately(t *testing.T, client Client, cfg BaseTxCfg, msg
require.NotNil(t, res)
return res, nil
}

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

t.Run("RPC client not set", func(t *testing.T) {
t.Parallel()

c := &Client{
RPCClient: nil, // not set
}

estimate, err := c.EstimateGas(&std.Tx{})

assert.Zero(t, estimate)
assert.ErrorIs(t, err, ErrMissingRPCClient)
})

t.Run("unsuccessful query, rpc error", func(t *testing.T) {
t.Parallel()

var (
rpcErr = errors.New("rpc error")
mockRPCClient = &mockRPCClient{
abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) {
require.Equal(t, simulatePath, path)

var tx std.Tx

require.NoError(t, amino.Unmarshal(data, &tx))

return nil, rpcErr
},
}
)

c := &Client{
RPCClient: mockRPCClient,
}

estimate, err := c.EstimateGas(&std.Tx{})

assert.Zero(t, estimate)
assert.ErrorIs(t, err, rpcErr)
})

t.Run("unsuccessful query, process error", func(t *testing.T) {
t.Parallel()

var (
response = &ctypes.ResultABCIQuery{
Response: abci.ResponseQuery{
ResponseBase: abci.ResponseBase{
Error: abciErrors.UnknownError{},
},
},
}
mockRPCClient = &mockRPCClient{
abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) {
require.Equal(t, simulatePath, path)

var tx std.Tx

require.NoError(t, amino.Unmarshal(data, &tx))

return response, nil
},
}
)

c := &Client{
RPCClient: mockRPCClient,
}

estimate, err := c.EstimateGas(&std.Tx{})

assert.Zero(t, estimate)
assert.ErrorIs(t, err, abciErrors.UnknownError{})
})

t.Run("invalid response format", func(t *testing.T) {
t.Parallel()

var (
response = &ctypes.ResultABCIQuery{
Response: abci.ResponseQuery{
Value: []byte("totally valid amino"),
},
}
mockRPCClient = &mockRPCClient{
abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) {
require.Equal(t, simulatePath, path)

var tx std.Tx

require.NoError(t, amino.Unmarshal(data, &tx))

return response, nil
},
}
)

c := &Client{
RPCClient: mockRPCClient,
}

estimate, err := c.EstimateGas(&std.Tx{})

assert.Zero(t, estimate)
assert.ErrorContains(t, err, "unable to unmarshal gas estimation response")
})

t.Run("valid gas estimation", func(t *testing.T) {
t.Parallel()

var (
gasUsed = int64(100000)
deliverResp = &abci.ResponseDeliverTx{
GasUsed: gasUsed,
}
)

// Encode the response
encodedResp, err := amino.Marshal(deliverResp)
require.NoError(t, err)

var (
response = &ctypes.ResultABCIQuery{
Response: abci.ResponseQuery{
Value: encodedResp, // valid amino binary
},
}
mockRPCClient = &mockRPCClient{
abciQuery: func(path string, data []byte) (*ctypes.ResultABCIQuery, error) {
require.Equal(t, simulatePath, path)

var tx std.Tx

require.NoError(t, amino.Unmarshal(data, &tx))

return response, nil
},
}
)

c := &Client{
RPCClient: mockRPCClient,
}

estimate, err := c.EstimateGas(&std.Tx{})

require.NoError(t, err)
assert.Equal(t, gasUsed, estimate)
})
}
47 changes: 46 additions & 1 deletion gno.land/pkg/gnoclient/client_txs.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package gnoclient

import (
"fmt"

"github.com/gnolang/gno/gno.land/pkg/sdk/vm"
"github.com/gnolang/gno/tm2/pkg/amino"
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/errors"
"github.com/gnolang/gno/tm2/pkg/sdk/bank"
Expand All @@ -16,6 +19,8 @@ var (
ErrMissingRPCClient = errors.New("missing RPCClient")
)

const simulatePath = ".app/simulate"

// BaseTxCfg defines the base transaction configuration, shared by all message types
type BaseTxCfg struct {
GasFee string // Gas fee
Expand Down Expand Up @@ -292,4 +297,44 @@ func (c *Client) BroadcastTxCommit(signedTx *std.Tx) (*ctypes.ResultBroadcastTxC
return bres, nil
}

// TODO: Add more functionality, examples, and unit tests.
// EstimateGas returns the least amount of gas required
// for the transaction to go through on the chain (minimum gas wanted).
// The estimation process assumes the transaction is properly signed
func (c *Client) EstimateGas(tx *std.Tx) (int64, error) {
// Make sure the RPC client is set
if err := c.validateRPCClient(); err != nil {
return 0, err
}

// Prepare the transaction.
// The transaction needs to be amino-binary encoded
// in order to be estimated
encodedTx, err := amino.Marshal(tx)
if err != nil {
return 0, fmt.Errorf("unable to marshal tx: %w", err)
}

// Perform the simulation query
resp, err := c.RPCClient.ABCIQuery(simulatePath, encodedTx)
if err != nil {
return 0, fmt.Errorf("unable to perform ABCI query: %w", err)
}

// Extract the query response
if err = resp.Response.Error; err != nil {
return 0, fmt.Errorf("error encountered during ABCI query: %w", err)
}

var deliverTx abci.ResponseDeliverTx
if err = amino.Unmarshal(resp.Response.Value, &deliverTx); err != nil {
return 0, fmt.Errorf("unable to unmarshal gas estimation response: %w", err)
}

if err = deliverTx.Error; err != nil {
return 0, fmt.Errorf("error encountered during gas estimation: %w", err)
}

// Return the actual value returned by the node
// for executing the transaction
return deliverTx.GasUsed, nil
}

0 comments on commit 9007d55

Please sign in to comment.