Skip to content

Commit

Permalink
Add CalculateGas integration-tests & improve deterministicgas docs (#706
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ysv authored Nov 13, 2023
1 parent 0746778 commit 032dd96
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 101 deletions.
44 changes: 22 additions & 22 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ require (
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.16.0
github.com/stretchr/testify v1.8.4
google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a
google.golang.org/grpc v1.59.0
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e
google.golang.org/grpc v1.57.0
)

require (
Expand All @@ -51,10 +51,10 @@ require (
)

require (
cloud.google.com/go v0.110.8 // indirect
cloud.google.com/go/compute v1.23.1 // indirect
cloud.google.com/go v0.110.4 // indirect
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.3 // indirect
cloud.google.com/go/iam v1.1.0 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
cosmossdk.io/core v0.6.1 // indirect
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
Expand Down Expand Up @@ -83,7 +83,7 @@ require (
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v0.20.1 // indirect
github.com/cosmos/ics23/go v0.10.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.12.4 // indirect
github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect
github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
github.com/creachadair/taskgroup v0.4.2 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
Expand All @@ -104,7 +104,7 @@ require (
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.2 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
Expand All @@ -113,9 +113,9 @@ require (
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.3.1
github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/google/uuid v1.3.0
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
Expand Down Expand Up @@ -173,24 +173,24 @@ require (
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.6.0 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
github.com/zondax/hid v0.9.1 // indirect
github.com/zondax/ledger-go v0.14.1 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.128.0 // indirect
google.golang.org/api v0.126.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e // indirect
google.golang.org/protobuf v1.31.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
Expand Down
87 changes: 43 additions & 44 deletions go.sum

Large diffs are not rendered by default.

229 changes: 216 additions & 13 deletions integration-tests/modules/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@
package modules

import (
"encoding/json"
"testing"

sdkmath "cosmossdk.io/math"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
cosmoserrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
authztypes "github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/samber/lo"
"github.com/stretchr/testify/require"

integrationtests "github.com/CoreumFoundation/coreum/v3/integration-tests"
moduleswasm "github.com/CoreumFoundation/coreum/v3/integration-tests/contracts/modules"
"github.com/CoreumFoundation/coreum/v3/pkg/client"
"github.com/CoreumFoundation/coreum/v3/testutil/integration"
assetfttypes "github.com/CoreumFoundation/coreum/v3/x/asset/ft/types"
"github.com/CoreumFoundation/coreum/v3/x/deterministicgas"
)

// TestAuthFeeLimits verifies that invalid message gas won't be accepted.
Expand Down Expand Up @@ -126,22 +133,20 @@ func TestAuthMultisig(t *testing.T) {
ctx, chain := integrationtests.NewCoreumTestingContext(t)
requireT := require.New(t)
recipient := chain.GenAccount()
amountToSendFromMultisigAccount := int64(1000)
amountToSendFromMultisigAccount := int64(1_000_000)

multisigPublicKey, keyNamesSet, err := chain.GenMultisigAccount(3, 2)
signersCount := 7
multisigTreshold := 6
multisigPublicKey, keyNamesSet, err := chain.GenMultisigAccount(signersCount, multisigTreshold)
requireT.NoError(err)
multisigAddress := sdk.AccAddress(multisigPublicKey.Address())
signer1KeyName := keyNamesSet[0]
signer2KeyName := keyNamesSet[1]

bankClient := banktypes.NewQueryClient(chain.ClientContext)

// fund the multisig account
chain.FundAccountWithOptions(ctx, t, multisigAddress, integration.BalancesOptions{
Messages: []sdk.Msg{&banktypes.MsgSend{}},
Amount: sdkmath.NewInt(amountToSendFromMultisigAccount),
Amount: sdkmath.NewInt(amountToSendFromMultisigAccount), // for gas estimation to work wee need account to exist on chain so we fund it with to be sent amount.
})

bankClient := banktypes.NewQueryClient(chain.ClientContext)

// prepare account to be funded from the multisig
recipientAddr := recipient.String()
coinsToSendToRecipient := sdk.NewCoins(chain.NewCoin(sdkmath.NewInt(amountToSendFromMultisigAccount)))
Expand All @@ -151,26 +156,45 @@ func TestAuthMultisig(t *testing.T) {
ToAddress: recipientAddr,
Amount: coinsToSendToRecipient,
}

_, gasEstimation, err := client.CalculateGas(
ctx,
chain.ClientContext.WithFromAddress(multisigAddress),
chain.TxFactory(),
bankSendMsg,
)
requireT.NoError(err)

// fund the multisig account
chain.FundAccountWithOptions(ctx, t, multisigAddress, integration.BalancesOptions{
Amount: sdkmath.NewInt(int64(gasEstimation)), // because of 6/7 multisig gas exceeds FixedGas, and we need to fund it to pay fees.
})

_, err = chain.SignAndBroadcastMultisigTx(
ctx,
chain.ClientContext.WithFromAddress(multisigAddress),
// We intentionally use simulation instead of using `WithGas(chain.GasLimitByMsgs(bankSendMsg))`.
// We do it to test simulation for multisig account.
chain.TxFactory().WithSimulateAndExecute(true),
bankSendMsg,
signer1KeyName)
keyNamesSet[0])
requireT.ErrorIs(err, cosmoserrors.ErrUnauthorized)
t.Log("Partially signed tx executed with expected error")

// sign and submit with the min threshold
txRes, err := chain.SignAndBroadcastMultisigTx(
ctx,
chain.ClientContext.WithFromAddress(multisigAddress),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(bankSendMsg)),
chain.TxFactory().WithSimulateAndExecute(true),
bankSendMsg,
signer1KeyName, signer2KeyName)
keyNamesSet[:multisigTreshold]...)
requireT.NoError(err)
t.Logf("Fully signed tx executed, txHash:%s", txRes.TxHash)
t.Logf("Fully signed tx executed, txHash:%s, gasUsed:%d, gasWanted:%d", txRes.TxHash, txRes.GasUsed, txRes.GasWanted)

// Real gas used might be less that estimation for multisig account (especially when there are many signers)
// because in ConsumeTxSizeGasDecorator ([email protected]/x/auth/ante/basic.go:99) amount of bytes is estimated
// for the worst case.
requireT.LessOrEqual(txRes.GasUsed, int64(gasEstimation))

recipientBalances, err := bankClient.AllBalances(ctx, &banktypes.QueryAllBalancesRequest{
Address: recipientAddr,
Expand Down Expand Up @@ -212,3 +236,182 @@ func TestAuthUnexpectedSequenceNumber(t *testing.T) {
msg)
require.True(t, cosmoserrors.ErrWrongSequence.Is(err))
}

func TestGasEstimation(t *testing.T) {
t.Parallel()
ctx, chain := integrationtests.NewCoreumTestingContext(t)

singlesigAddress := chain.GenAccount()

multisigPublicKey1, _, err := chain.GenMultisigAccount(3, 2)
require.NoError(t, err)
multisigAddress1 := sdk.AccAddress(multisigPublicKey1.Address())

multisigPublicKey2, _, err := chain.GenMultisigAccount(7, 6)
require.NoError(t, err)
multisigAddress2 := sdk.AccAddress(multisigPublicKey2.Address())

dgc := deterministicgas.DefaultConfig()
authParams, err := authtypes.NewQueryClient(chain.ClientContext).Params(ctx, &authtypes.QueryParamsRequest{})
require.NoError(t, err)

// For accounts to exist on chain we need to fund them at least with min amount (1ucore).
chain.FundAccountWithOptions(ctx, t, singlesigAddress, integration.BalancesOptions{Amount: sdkmath.NewInt(1)})
chain.FundAccountWithOptions(ctx, t, multisigAddress1, integration.BalancesOptions{Amount: sdkmath.NewInt(1)})
chain.FundAccountWithOptions(ctx, t, multisigAddress2, integration.BalancesOptions{Amount: sdkmath.NewInt(1)})

// For deterministic messages we are able to assert that gas estimation is equal to exact number.
testsDeterm := []struct {
name string
fromAddress sdk.AccAddress
msgs []sdk.Msg
expectedGas uint64
}{
{
name: "singlesig_bank_send",
fromAddress: singlesigAddress,
msgs: []sdk.Msg{
&banktypes.MsgSend{
FromAddress: singlesigAddress.String(),
ToAddress: singlesigAddress.String(),
Amount: sdk.NewCoins(chain.NewCoin(sdkmath.NewInt(1))),
},
},
// single signature no extra bytes.
expectedGas: dgc.FixedGas + 1*deterministicgas.BankSendPerCoinGas,
},
{
name: "multisig_2_3_bank_send",
fromAddress: multisigAddress1,
msgs: []sdk.Msg{
&banktypes.MsgSend{
FromAddress: multisigAddress1.String(),
ToAddress: multisigAddress1.String(),
Amount: sdk.NewCoins(chain.NewCoin(sdkmath.NewInt(1))),
},
},
// single signature no extra bytes.
expectedGas: dgc.FixedGas + 1*deterministicgas.BankSendPerCoinGas,
},
{
name: "multisig_6_7_bank_send",
fromAddress: multisigAddress2,
msgs: []sdk.Msg{
&banktypes.MsgSend{
FromAddress: multisigAddress2.String(),
ToAddress: multisigAddress2.String(),
Amount: sdk.NewCoins(chain.NewCoin(sdkmath.NewInt(1))),
},
},
// estimation uses worst case to estimate number of bytes in tx which causes possible overflow of free bytes.
// 10 is price for each extra byte over FreeBytes.
expectedGas: dgc.FixedGas + 1*deterministicgas.BankSendPerCoinGas + 1133*authParams.Params.TxSizeCostPerByte,
},
{
name: "singlesig_auth_exec_and_bank_send",
fromAddress: singlesigAddress,
msgs: []sdk.Msg{
lo.ToPtr(
authztypes.NewMsgExec(singlesigAddress, []sdk.Msg{
&banktypes.MsgSend{
FromAddress: singlesigAddress.String(),
ToAddress: singlesigAddress.String(),
Amount: sdk.NewCoins(chain.NewCoin(sdkmath.NewInt(1))),
},
})),
&banktypes.MsgSend{
FromAddress: singlesigAddress.String(),
ToAddress: singlesigAddress.String(),
Amount: sdk.NewCoins(chain.NewCoin(sdkmath.NewInt(1))),
},
},
// single signature no extra bytes.
expectedGas: dgc.FixedGas + 1*deterministicgas.BankSendPerCoinGas + (1*deterministicgas.AuthzExecOverhead + 1*deterministicgas.BankSendPerCoinGas),
},
}
for _, tt := range testsDeterm {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, estimatedGas, err := client.CalculateGas(
ctx,
chain.ClientContext.WithFromAddress(tt.fromAddress),
chain.TxFactory(),
tt.msgs...,
)
require.NoError(t, err)
require.Equal(t, int(tt.expectedGas), int(estimatedGas))
})
}

// For non-deterministic messages we need to deploy a contract.
// Any address could be admin since we are not going to execute it but just estimate.
admin := chain.GenAccount()
chain.FundAccountWithOptions(ctx, t, admin, integration.BalancesOptions{Amount: sdkmath.NewInt(1_000_000)})

initialPayload, err := json.Marshal(moduleswasm.SimpleState{
Count: 1337,
})
require.NoError(t, err)
contractAddr, _, err := chain.Wasm.DeployAndInstantiateWASMContract(
ctx,
chain.TxFactory().WithSimulateAndExecute(true),
admin,
moduleswasm.SimpleStateWASM,
integration.InstantiateConfig{
AccessType: wasmtypes.AccessTypeUnspecified,
Payload: initialPayload,
Label: "simple_state",
},
)
require.NoError(t, err)

wasmPayload, err := moduleswasm.MethodToEmptyBodyPayload(moduleswasm.SimpleIncrement)
require.NoError(t, err)

// For non-deterministic messages we are unable to know exact number, so we do just basic assertion.
testsNonDeterm := []struct {
name string
fromAddress sdk.AccAddress
msgs []sdk.Msg
}{
{
name: "singlesig_wasm_execute_contract",
fromAddress: singlesigAddress,
msgs: []sdk.Msg{
&wasmtypes.MsgExecuteContract{
Sender: singlesigAddress.String(),
Contract: contractAddr,
Msg: wasmtypes.RawContractMessage(wasmPayload),
Funds: sdk.Coins{},
},
},
},
{
name: "multisig_2_3_wasm_execute_contract",
fromAddress: multisigAddress1,
msgs: []sdk.Msg{
&wasmtypes.MsgExecuteContract{
Sender: multisigAddress1.String(),
Contract: contractAddr,
Msg: wasmtypes.RawContractMessage(wasmPayload),
Funds: sdk.Coins{},
},
},
},
}
for _, tt := range testsNonDeterm {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, estimatedGas, err := client.CalculateGas(
ctx,
chain.ClientContext.WithFromAddress(tt.fromAddress),
chain.TxFactory(),
tt.msgs...,
)
require.NoError(t, err)
require.Greater(t, int(estimatedGas), 0)
})
}
}
3 changes: 1 addition & 2 deletions integration-tests/modules/bank_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,10 @@ func TestBankSendGasEstimation(t *testing.T) {
Amount: sdk.NewCoins(chain.NewCoin(amountToSend)),
}

clientCtx := chain.ClientContext.WithFromAddress(sender)
bankSendGas := chain.GasLimitByMsgs(&banktypes.MsgSend{})
_, estimatedGas, err := client.CalculateGas(
ctx,
clientCtx,
chain.ClientContext.WithFromAddress(sender),
chain.TxFactory().
WithGas(bankSendGas),
msg)
Expand Down
Loading

0 comments on commit 032dd96

Please sign in to comment.