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

Add CalculateGas integration-tests & improve deterministicgas docs #706

Merged
merged 17 commits into from
Nov 13, 2023
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.0 // 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.

227 changes: 214 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,180 @@ 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 _, test := range testsDeterm {
t.Run(test.name, func(t *testing.T) {
_, estimatedGas, err := client.CalculateGas(
ctx,
chain.ClientContext.WithFromAddress(test.fromAddress),
chain.TxFactory(),
test.msgs...,
)
require.NoError(t, err)
require.Equal(t, int(test.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 _, test := range testsNonDeterm {
t.Run(test.name, func(t *testing.T) {
_, estimatedGas, err := client.CalculateGas(
ctx,
chain.ClientContext.WithFromAddress(test.fromAddress),
chain.TxFactory(),
test.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
Loading