wasmd/x/wasm/keeper/keeper_test.go

2988 lines
106 KiB
Go

package keeper
import (
"bytes"
_ "embed"
"encoding/json"
"errors"
"fmt"
stdrand "math/rand"
"os"
"testing"
"time"
wasmvm "github.com/CosmWasm/wasmvm/v3"
wasmvmtypes "github.com/CosmWasm/wasmvm/v3/types"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/rand"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
fuzz "github.com/google/gofuzz"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
sdkmath "cosmossdk.io/math"
"cosmossdk.io/store"
storemetrics "cosmossdk.io/store/metrics"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/CosmWasm/wasmd/x/wasm/keeper/testdata"
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
//go:embed testdata/hackatom.wasm
var hackatomWasm []byte
//go:embed testdata/replier.wasm
var replierWasm []byte
var AvailableCapabilities = []string{
"iterator", "staking", "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3",
"cosmwasm_1_4", "cosmwasm_2_0", "cosmwasm_2_1", "cosmwasm_2_2", "ibc2",
}
func TestNewKeeper(t *testing.T) {
_, keepers := CreateTestInput(t, false, AvailableCapabilities)
require.NotNil(t, keepers.ContractKeeper)
}
func TestCreateSuccess(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
em := sdk.NewEventManager()
contractID, _, err := keeper.Create(ctx.WithEventManager(em), creator, hackatomWasm, nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// and verify content
storedCode, err := keepers.WasmKeeper.GetByteCode(ctx, contractID)
require.NoError(t, err)
require.Equal(t, hackatomWasm, storedCode)
// and events emitted
codeHash := testdata.ChecksumHackatom
exp := sdk.Events{sdk.NewEvent("store_code", sdk.NewAttribute("code_checksum", codeHash), sdk.NewAttribute("code_id", "1"))}
assert.Equal(t, exp, em.Events())
}
func TestCreateNilCreatorAddress(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
_, _, err := keepers.ContractKeeper.Create(ctx, nil, hackatomWasm, nil)
require.Error(t, err, "nil creator is not allowed")
}
func TestWasmLimits(t *testing.T) {
one := uint32(1)
cfg := types.DefaultNodeConfig()
ctx, keepers := createTestInput(t, false, AvailableCapabilities, cfg, types.VMConfig{
WasmLimits: wasmvmtypes.WasmLimits{
MaxImports: &one, // very low limit that every contract will fail
},
}, dbm.NewMemDB())
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 1))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
_, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
assert.Error(t, err)
assert.ErrorContains(t, err, "Import")
}
func TestCreateNilWasmCode(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
_, _, err := keepers.ContractKeeper.Create(ctx, creator, nil, nil)
require.Error(t, err, "nil WASM code is not allowed")
}
func TestCreateInvalidWasmCode(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
_, _, err := keepers.ContractKeeper.Create(ctx, creator, []byte("potatoes"), nil)
require.Error(t, err, "potatoes are not valid WASM code")
}
func TestCreateStoresInstantiatePermission(t *testing.T) {
var (
deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
myAddr sdk.AccAddress = bytes.Repeat([]byte{1}, types.SDKAddrLen)
)
specs := map[string]struct {
srcPermission types.AccessType
expInstConf types.AccessConfig
}{
"default": {
srcPermission: types.DefaultParams().InstantiateDefaultPermission,
expInstConf: types.AllowEverybody,
},
"everybody": {
srcPermission: types.AccessTypeEverybody,
expInstConf: types.AllowEverybody,
},
"nobody": {
srcPermission: types.AccessTypeNobody,
expInstConf: types.AllowNobody,
},
"anyAddress with matching address": {
srcPermission: types.AccessTypeAnyOfAddresses,
expInstConf: types.AccessConfig{Permission: types.AccessTypeAnyOfAddresses, Addresses: []string{myAddr.String()}},
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
err := keepers.WasmKeeper.SetParams(ctx, types.Params{
CodeUploadAccess: types.AllowEverybody,
InstantiateDefaultPermission: spec.srcPermission,
})
require.NoError(t, err)
fundAccounts(t, ctx, accKeeper, bankKeeper, myAddr, deposit)
codeID, _, err := keeper.Create(ctx, myAddr, hackatomWasm, nil)
require.NoError(t, err)
codeInfo := keepers.WasmKeeper.GetCodeInfo(ctx, codeID)
require.NotNil(t, codeInfo)
assert.True(t, spec.expInstConf.Equals(codeInfo.InstantiateConfig), "got %#v", codeInfo.InstantiateConfig)
})
}
}
func TestCreateWithParamPermissions(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
otherAddr := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
specs := map[string]struct {
policy types.AuthorizationPolicy
chainUpload types.AccessConfig
expError *errorsmod.Error
}{
"default": {
policy: DefaultAuthorizationPolicy{},
chainUpload: types.DefaultUploadAccess,
},
"everybody": {
policy: DefaultAuthorizationPolicy{},
chainUpload: types.AllowEverybody,
},
"nobody": {
policy: DefaultAuthorizationPolicy{},
chainUpload: types.AllowNobody,
expError: sdkerrors.ErrUnauthorized,
},
"anyAddress with matching address": {
policy: DefaultAuthorizationPolicy{},
chainUpload: types.AccessTypeAnyOfAddresses.With(creator),
},
"anyAddress with non matching address": {
policy: DefaultAuthorizationPolicy{},
chainUpload: types.AccessTypeAnyOfAddresses.With(otherAddr),
expError: sdkerrors.ErrUnauthorized,
},
"gov: always allowed": {
policy: GovAuthorizationPolicy{},
chainUpload: types.AllowNobody,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
params := types.DefaultParams()
params.CodeUploadAccess = spec.chainUpload
err := keepers.WasmKeeper.SetParams(ctx, params)
require.NoError(t, err)
keeper := NewPermissionedKeeper(keepers.WasmKeeper, spec.policy)
_, _, err = keeper.Create(ctx, creator, hackatomWasm, nil)
require.True(t, spec.expError.Is(err), err)
if spec.expError != nil {
return
}
})
}
}
// ensure that the user cannot set the code instantiate permission to something more permissive
// than the default
func TestEnforceValidPermissionsOnCreate(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.WasmKeeper
contractKeeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
other := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
onlyCreator := types.AccessTypeAnyOfAddresses.With(creator)
onlyOther := types.AccessTypeAnyOfAddresses.With(other)
specs := map[string]struct {
defaultPermission types.AccessType
requestedPermission *types.AccessConfig
// grantedPermission is set iff no error
grantedPermission types.AccessConfig
// expError is nil iff the request is allowed
expError *errorsmod.Error
}{
"override everybody": {
defaultPermission: types.AccessTypeEverybody,
requestedPermission: &onlyCreator,
grantedPermission: onlyCreator,
},
"default to everybody": {
defaultPermission: types.AccessTypeEverybody,
requestedPermission: nil,
grantedPermission: types.AccessConfig{Permission: types.AccessTypeEverybody},
},
"explicitly set everybody": {
defaultPermission: types.AccessTypeEverybody,
requestedPermission: &types.AccessConfig{Permission: types.AccessTypeEverybody},
grantedPermission: types.AccessConfig{Permission: types.AccessTypeEverybody},
},
"cannot override nobody": {
defaultPermission: types.AccessTypeNobody,
requestedPermission: &onlyCreator,
expError: sdkerrors.ErrUnauthorized,
},
"default to nobody": {
defaultPermission: types.AccessTypeNobody,
requestedPermission: nil,
grantedPermission: types.AccessConfig{Permission: types.AccessTypeNobody},
},
"only defaults to code creator": {
defaultPermission: types.AccessTypeAnyOfAddresses,
requestedPermission: nil,
grantedPermission: onlyCreator,
},
"can explicitly set to code creator": {
defaultPermission: types.AccessTypeAnyOfAddresses,
requestedPermission: &onlyCreator,
grantedPermission: onlyCreator,
},
"cannot override which address in only": {
defaultPermission: types.AccessTypeAnyOfAddresses,
requestedPermission: &onlyOther,
expError: sdkerrors.ErrUnauthorized,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
params := types.DefaultParams()
params.InstantiateDefaultPermission = spec.defaultPermission
err := keeper.SetParams(ctx, params)
require.NoError(t, err)
codeID, _, err := contractKeeper.Create(ctx, creator, hackatomWasm, spec.requestedPermission)
require.True(t, spec.expError.Is(err), err)
if spec.expError == nil {
codeInfo := keeper.GetCodeInfo(ctx, codeID)
require.Equal(t, codeInfo.InstantiateConfig, spec.grantedPermission)
}
})
}
}
func TestCreateDuplicate(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
// create one copy
contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// create second copy
duplicateID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
require.Equal(t, uint64(2), duplicateID)
// and verify both content is proper
storedCode, err := keepers.WasmKeeper.GetByteCode(ctx, contractID)
require.NoError(t, err)
require.Equal(t, hackatomWasm, storedCode)
storedCode, err = keepers.WasmKeeper.GetByteCode(ctx, duplicateID)
require.NoError(t, err)
require.Equal(t, hackatomWasm, storedCode)
}
func TestCreateWithSimulation(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
ctx = ctx.WithBlockHeader(tmproto.Header{Height: 1}).
WithGasMeter(storetypes.NewInfiniteGasMeter())
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
// create this once in simulation mode
contractID, _, err := keepers.ContractKeeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// then try to create it in non-simulation mode (should not fail)
ctx, keepers = CreateTestInput(t, false, AvailableCapabilities)
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(10_000_000))
creator = keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
contractID, _, err = keepers.ContractKeeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// and verify content
code, err := keepers.WasmKeeper.GetByteCode(ctx, contractID)
require.NoError(t, err)
require.Equal(t, code, hackatomWasm)
}
func TestCreateWithGzippedPayload(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
wasmCode, err := os.ReadFile("./testdata/hackatom.wasm.gzip")
require.NoError(t, err, "reading gzipped WASM code")
contractID, _, err := keeper.Create(ctx, creator, wasmCode, nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// and verify content
storedCode, err := keepers.WasmKeeper.GetByteCode(ctx, contractID)
require.NoError(t, err)
require.Equal(t, hackatomWasm, storedCode)
}
func TestCreateWithBrokenGzippedPayload(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
wasmCode, err := os.ReadFile("./testdata/broken_crc.gzip")
require.NoError(t, err, "reading gzipped WASM code")
gm := storetypes.NewInfiniteGasMeter()
codeID, checksum, err := keeper.Create(ctx.WithGasMeter(gm), creator, wasmCode, nil)
require.Error(t, err)
assert.Empty(t, codeID)
assert.Empty(t, checksum)
assert.GreaterOrEqual(t, gm.GasConsumed(), storetypes.Gas(121384)) // 809232 * 0.15 (default uncompress costs) = 121384
}
func TestInstantiate(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
keepers.Faucet.Fund(ctx, creator, deposit...)
example := StoreHackatomExampleContract(t, ctx, keepers)
initMsg := HackatomExampleInitMsg{
Verifier: RandomAccountAddress(t),
Beneficiary: RandomAccountAddress(t),
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
gasBefore := ctx.GasMeter().GasConsumed()
em := sdk.NewEventManager()
// create with no balance is also legal
gotContractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx.WithEventManager(em), example.CodeID, creator, nil, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", gotContractAddr.String())
gasAfter := ctx.GasMeter().GasConsumed()
if types.EnableGasVerification {
require.Equal(t, uint64(0x1c527), gasAfter-gasBefore)
}
// ensure it is stored properly
info := keepers.WasmKeeper.GetContractInfo(ctx, gotContractAddr)
require.NotNil(t, info)
assert.Equal(t, creator.String(), info.Creator)
assert.Equal(t, example.CodeID, info.CodeID)
assert.Equal(t, "demo contract 1", info.Label)
exp := []types.ContractCodeHistoryEntry{{
Operation: types.ContractCodeHistoryOperationTypeInit,
CodeID: example.CodeID,
Updated: types.NewAbsoluteTxPosition(ctx),
Msg: initMsgBz,
}}
assert.Equal(t, exp, keepers.WasmKeeper.GetContractHistory(ctx, gotContractAddr))
// and events emitted
expEvt := sdk.Events{
sdk.NewEvent("instantiate",
sdk.NewAttribute("_contract_address", gotContractAddr.String()), sdk.NewAttribute("code_id", "1")),
sdk.NewEvent("wasm",
sdk.NewAttribute("_contract_address", gotContractAddr.String()), sdk.NewAttribute("Let the", "hacking begin")),
}
assert.Equal(t, expEvt, em.Events())
}
func TestInstantiateWithDeposit(t *testing.T) {
var (
bob = bytes.Repeat([]byte{1}, types.SDKAddrLen)
fred = bytes.Repeat([]byte{2}, types.SDKAddrLen)
deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100))
initMsg = mustMarshal(t, HackatomExampleInitMsg{Verifier: fred, Beneficiary: bob})
)
specs := map[string]struct {
srcActor sdk.AccAddress
expError bool
fundAddr bool
}{
"address with funds": {
srcActor: bob,
fundAddr: true,
},
"address without funds": {
srcActor: bob,
expError: true,
},
"blocked address": {
srcActor: authtypes.NewModuleAddress(authtypes.FeeCollectorName),
fundAddr: true,
expError: false,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.ContractKeeper
if spec.fundAddr {
fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, sdk.NewCoins(sdk.NewInt64Coin("denom", 200)))
}
contractID, _, err := keeper.Create(ctx, spec.srcActor, hackatomWasm, nil)
require.NoError(t, err)
// when
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, spec.srcActor, nil, initMsg, "my label", deposit)
// then
if spec.expError {
require.Error(t, err)
return
}
require.NoError(t, err)
balances := bankKeeper.GetAllBalances(ctx, addr)
assert.Equal(t, deposit, balances)
})
}
}
func TestInstantiateWithPermissions(t *testing.T) {
var (
deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
myAddr = bytes.Repeat([]byte{1}, types.SDKAddrLen)
otherAddr = bytes.Repeat([]byte{2}, types.SDKAddrLen)
anyAddr = bytes.Repeat([]byte{3}, types.SDKAddrLen)
)
initMsg := HackatomExampleInitMsg{
Verifier: anyAddr,
Beneficiary: anyAddr,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
specs := map[string]struct {
srcPermission types.AccessConfig
srcActor sdk.AccAddress
expError *errorsmod.Error
}{
"default": {
srcPermission: types.DefaultUploadAccess,
srcActor: anyAddr,
},
"everybody": {
srcPermission: types.AllowEverybody,
srcActor: anyAddr,
},
"nobody": {
srcPermission: types.AllowNobody,
srcActor: myAddr,
expError: sdkerrors.ErrUnauthorized,
},
"anyAddress with matching address": {
srcPermission: types.AccessTypeAnyOfAddresses.With(myAddr),
srcActor: myAddr,
},
"anyAddress with non matching address": {
srcActor: myAddr,
srcPermission: types.AccessTypeAnyOfAddresses.With(otherAddr),
expError: sdkerrors.ErrUnauthorized,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.ContractKeeper
fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, deposit)
contractID, _, err := keeper.Create(ctx, myAddr, hackatomWasm, &spec.srcPermission) //nolint:gosec
require.NoError(t, err)
_, _, err = keepers.ContractKeeper.Instantiate(ctx, contractID, spec.srcActor, nil, initMsgBz, "demo contract 1", nil)
assert.True(t, spec.expError.Is(err), "got %+v", err)
})
}
}
func TestInstantiateWithAccounts(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
example := StoreHackatomExampleContract(t, parentCtx, keepers)
require.Equal(t, uint64(1), example.CodeID)
initMsg := mustMarshal(t, HackatomExampleInitMsg{Verifier: RandomAccountAddress(t), Beneficiary: RandomAccountAddress(t)})
senderAddr := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(parentCtx, senderAddr, sdk.NewInt64Coin("denom", 100000000))
myTestLabel := "testing"
mySalt := []byte(`my salt`)
contractAddr := BuildContractAddressPredictable(example.Checksum, senderAddr, mySalt, []byte{})
lastAccountNumber := keepers.AccountKeeper.GetAccount(parentCtx, senderAddr).GetAccountNumber()
specs := map[string]struct {
option Option
account sdk.AccountI
initBalance sdk.Coin
deposit sdk.Coins
expErr error
expAccount sdk.AccountI
expBalance sdk.Coins
}{
"unused BaseAccount exists": {
account: authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
initBalance: sdk.NewInt64Coin("denom", 100000000),
expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+1, 0), // +1 for next seq
expBalance: sdk.NewCoins(sdk.NewInt64Coin("denom", 100000000)),
},
"BaseAccount with sequence exists": {
account: authtypes.NewBaseAccount(contractAddr, nil, 0, 1),
expErr: types.ErrAccountExists,
},
"BaseAccount with pubkey exists": {
account: authtypes.NewBaseAccount(contractAddr, &ed25519.PubKey{}, 0, 0),
expErr: types.ErrAccountExists,
},
"no account existed": {
expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+1, 0), // +1 for next seq,
expBalance: sdk.NewCoins(),
},
"no account existed before create with deposit": {
expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+1, 0), // +1 for next seq
deposit: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_000))),
expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_000))),
},
"prunable DelayedVestingAccount gets overwritten": {
account: must(vestingtypes.NewDelayedVestingAccount(
authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix())),
initBalance: sdk.NewCoin("denom", sdkmath.NewInt(1_000)),
deposit: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1))),
expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+2, 0), // +1 for next seq, +1 for spec.account created
expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1))),
},
"prunable ContinuousVestingAccount gets overwritten": {
account: must(vestingtypes.NewContinuousVestingAccount(
authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_000))), time.Now().Add(time.Hour).Unix(), time.Now().Add(2*time.Hour).Unix())),
initBalance: sdk.NewCoin("denom", sdkmath.NewInt(1_000)),
deposit: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1))),
expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+2, 0), // +1 for next seq, +1 for spec.account created
expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1))),
},
// "prunable account without balance gets overwritten": { // todo : can not initialize vesting with empty balance
// account: must(vestingtypes.NewContinuousVestingAccount(
// authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
// sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(0))), time.Now().Add(time.Hour).Unix(), time.Now().Add(2*time.Hour).Unix())),
// expAccount: authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+2, 0), // +1 for next seq, +1 for spec.account created
// expBalance: sdk.NewCoins(),
// },
"unknown account type is rejected with error": {
account: authtypes.NewModuleAccount(
authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
"testing",
),
initBalance: sdk.NewCoin("denom", sdkmath.NewInt(1_000)),
expErr: types.ErrAccountExists,
},
"with option used to set non default type to accept list": {
option: WithAcceptedAccountTypesOnContractInstantiation(&vestingtypes.DelayedVestingAccount{}),
account: must(vestingtypes.NewDelayedVestingAccount(
authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix())),
initBalance: sdk.NewCoin("denom", sdkmath.NewInt(1_000)),
deposit: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1))),
expAccount: must(vestingtypes.NewDelayedVestingAccount(authtypes.NewBaseAccount(contractAddr, nil, lastAccountNumber+1, 0),
sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix())),
expBalance: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_001))),
},
"pruning account fails": {
option: WithAccountPruner(wasmtesting.AccountPrunerMock{CleanupExistingAccountFn: func(ctx sdk.Context, existingAccount sdk.AccountI) (handled bool, err error) {
return false, types.ErrUnsupportedForContract.Wrap("testing")
}}),
account: must(vestingtypes.NewDelayedVestingAccount(
authtypes.NewBaseAccount(contractAddr, nil, 0, 0),
sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_000))), time.Now().Add(30*time.Hour).Unix())),
expErr: types.ErrUnsupportedForContract,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
if spec.account != nil {
keepers.AccountKeeper.SetAccount(ctx, keepers.AccountKeeper.NewAccount(ctx, spec.account))
}
if !spec.initBalance.IsNil() {
keepers.Faucet.Fund(ctx, spec.account.GetAddress(), spec.initBalance)
}
if spec.option != nil {
spec.option.apply(keepers.WasmKeeper)
}
defer func() {
if spec.option != nil { // reset
WithAcceptedAccountTypesOnContractInstantiation(&authtypes.BaseAccount{}).apply(keepers.WasmKeeper)
WithAccountPruner(NewVestingCoinBurner(keepers.BankKeeper)).apply(keepers.WasmKeeper)
}
}()
// when
gotAddr, _, gotErr := keepers.ContractKeeper.Instantiate2(ctx, 1, senderAddr, nil, initMsg, myTestLabel, spec.deposit, mySalt, false)
if spec.expErr != nil {
assert.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, contractAddr, gotAddr)
// and
gotAcc := keepers.AccountKeeper.GetAccount(ctx, contractAddr)
assert.Equal(t, spec.expAccount, gotAcc)
// and
gotBalance := keepers.BankKeeper.GetAllBalances(ctx, contractAddr)
assert.Equal(t, spec.expBalance, gotBalance)
})
}
}
func TestInstantiateWithNonExistingCodeID(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := keepers.Faucet.NewFundedRandomAccount(ctx, deposit...)
initMsg := HackatomExampleInitMsg{}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
const nonExistingCodeID = 9999
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, nonExistingCodeID, creator, nil, initMsgBz, "demo contract 2", nil)
require.Equal(t, types.ErrNoSuchCodeFn(nonExistingCodeID).Wrapf("code id %d", nonExistingCodeID).Error(), err.Error())
require.Nil(t, addr)
}
func TestContractErrorRedacting(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := sdk.AccAddress(bytes.Repeat([]byte{1}, address.Len))
keepers.Faucet.Fund(ctx, creator, deposit...)
example := StoreHackatomExampleContract(t, ctx, keepers)
initMsg := HackatomExampleInitMsg{
Verifier: []byte{1, 2, 3}, // invalid length
Beneficiary: RandomAccountAddress(t),
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
em := sdk.NewEventManager()
_, _, err = keepers.ContractKeeper.Instantiate(ctx.WithEventManager(em), example.CodeID, creator, nil, initMsgBz, "demo contract 1", nil)
require.Error(t, err)
require.Contains(t, err.Error(), "addr_validate errored: invalid address")
err = redactError(err)
// contract error should not be redacted
require.Contains(t, err.Error(), "addr_validate errored: invalid address")
}
func TestContractErrorGetsForwarded(t *testing.T) {
// This test makes sure that a contract gets the error message from its submessage execution
// in a non-redacted form if that error comes from the contract in the submessage.
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
reflect1 := InstantiateReflectExampleContract(t, ctx, keepers)
// reflect2 will be the contract that errors. It is owned by the other reflect contract.
// This is necessary because the reflect contract only accepts messages from its owner.
reflect2, _, err := keepers.ContractKeeper.Instantiate(ctx, reflect1.CodeID, reflect1.Contract, reflect1.Contract, []byte("{}"), "reflect2", sdk.NewCoins())
require.NoError(t, err)
const SubMsgID = 1
// Make the reflect1 contract send a submessage to reflect2. That sub-message will error.
execMsg := testdata.ReflectHandleMsg{
ReflectSubMsg: &testdata.ReflectSubPayload{
Msgs: []wasmvmtypes.SubMsg{
{
ID: SubMsgID,
Msg: wasmvmtypes.CosmosMsg{
Wasm: &wasmvmtypes.WasmMsg{
Execute: &wasmvmtypes.ExecuteMsg{
ContractAddr: reflect2.String(),
// This message will error in the reflect contract as an empty "msgs" array is not allowed
Msg: mustMarshal(t, testdata.ReflectHandleMsg{
Reflect: &testdata.ReflectPayload{
Msgs: []wasmvmtypes.CosmosMsg{},
},
}),
},
},
},
ReplyOn: wasmvmtypes.ReplyError, // we want to see the error
},
},
},
}
execMsgBz := mustMarshal(t, execMsg)
em := sdk.NewEventManager()
_, err = keepers.ContractKeeper.Execute(
ctx.WithEventManager(em),
reflect1.Contract,
reflect1.CreatorAddr,
execMsgBz,
nil,
)
require.NoError(t, err)
// now query the submessage reply
queryMsgBz := mustMarshal(t, testdata.ReflectQueryMsg{
SubMsgResult: &testdata.SubCall{
ID: SubMsgID,
},
})
queryResponse, err := keepers.WasmKeeper.QuerySmart(ctx, reflect1.Contract, queryMsgBz)
require.NoError(t, err)
var submsgReply wasmvmtypes.Reply
mustUnmarshal(t, queryResponse, &submsgReply)
assert.Equal(t, "Messages empty. Must reflect at least one message: execute wasm contract failed", submsgReply.Result.Err)
}
func TestInstantiateWithContractDataResponse(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
wasmEngineMock := &wasmtesting.MockWasmEngine{
InstantiateFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("my-response-data")}}, 0, nil
},
AnalyzeCodeFn: wasmtesting.WithoutIBCAnalyzeFn,
StoreCodeFn: wasmtesting.NoOpStoreCodeFn,
}
example := StoreRandomContract(t, ctx, keepers, wasmEngineMock)
_, data, err := keepers.ContractKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, nil, nil, "test", nil)
require.NoError(t, err)
assert.Equal(t, []byte("my-response-data"), data)
}
func TestInstantiateWithContractFactoryChildQueriesParent(t *testing.T) {
// Scenario:
// given a factory contract stored
// when instantiated, the contract creates a new child contract instance
// and the child contracts queries the senders ContractInfo on instantiation
// then the factory contract's ContractInfo should be returned to the child contract
//
// see also: https://github.com/CosmWasm/wasmd/issues/896
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.WasmKeeper
var instantiationCount int
callbacks := make([]func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error), 2)
wasmEngineMock := &wasmtesting.MockWasmEngine{
// dispatch instantiation calls to callbacks
InstantiateFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
require.Greater(t, len(callbacks), instantiationCount, "unexpected call to instantiation")
do := callbacks[instantiationCount]
instantiationCount++
return do(codeID, env, info, initMsg, store, goapi, querier, gasMeter, gasLimit, deserCost)
},
AnalyzeCodeFn: wasmtesting.WithoutIBCAnalyzeFn,
StoreCodeFn: wasmtesting.NoOpStoreCodeFn,
}
// overwrite wasmvm in router
router := baseapp.NewMsgServiceRouter()
router.SetInterfaceRegistry(keepers.EncodingConfig.InterfaceRegistry)
types.RegisterMsgServer(router, NewMsgServerImpl(keeper))
keeper.messenger = NewDefaultMessageHandler(nil, router, nil, nil, nil, keepers.EncodingConfig.Codec, nil)
// overwrite wasmvm in response handler
keeper.wasmVMResponseHandler = NewDefaultWasmVMContractResponseHandler(NewMessageDispatcher(keeper.messenger, keeper))
example := StoreRandomContract(t, ctx, keepers, wasmEngineMock)
// factory contract
callbacks[0] = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
t.Log("called factory")
return &wasmvmtypes.ContractResult{
Ok: &wasmvmtypes.Response{
Data: []byte("parent"),
Messages: []wasmvmtypes.SubMsg{
{
ID: 1, ReplyOn: wasmvmtypes.ReplyNever,
Msg: wasmvmtypes.CosmosMsg{
Wasm: &wasmvmtypes.WasmMsg{
Instantiate: &wasmvmtypes.InstantiateMsg{CodeID: example.CodeID, Msg: []byte(`{}`), Label: "child"},
},
},
},
},
},
}, 0, nil
}
// child contract
var capturedSenderAddr string
var capturedCodeInfo []byte
callbacks[1] = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
t.Log("called child")
capturedSenderAddr = info.Sender
var err error
capturedCodeInfo, err = querier.Query(wasmvmtypes.QueryRequest{
Wasm: &wasmvmtypes.WasmQuery{
ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: info.Sender},
},
}, gasLimit)
require.NoError(t, err)
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("child")}}, 0, nil
}
// when
contractAddress, data, err := keepers.ContractKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, nil, nil, "test", nil)
ibc2PortID := "wasm2" + contractAddress.String()
// then
require.NoError(t, err)
assert.Equal(t, []byte("parent"), data)
require.Equal(t, contractAddress.String(), capturedSenderAddr)
expCodeInfo := fmt.Sprintf(`{"code_id":%d,"creator":%q,"ibc2_port":%q,"pinned":false}`, example.CodeID, example.CreatorAddr.String(), ibc2PortID)
assert.JSONEq(t, expCodeInfo, string(capturedCodeInfo))
}
func TestExecute(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...)
fred := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...)
bob := RandomAccountAddress(t)
contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit)
require.NoError(t, err)
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", addr.String())
// ensure bob doesn't exist
bobAcct := accKeeper.GetAccount(ctx, bob)
require.Nil(t, bobAcct)
// ensure funder has reduced balance
creatorAcct := accKeeper.GetAccount(ctx, creator)
require.NotNil(t, creatorAcct)
// we started at 2*deposit, should have spent one above
assert.Equal(t, deposit, bankKeeper.GetAllBalances(ctx, creatorAcct.GetAddress()))
// ensure contract has updated balance
contractAcct := accKeeper.GetAccount(ctx, addr)
require.NotNil(t, contractAcct)
assert.Equal(t, deposit, bankKeeper.GetAllBalances(ctx, contractAcct.GetAddress()))
// unauthorized - trialCtx so we don't change state
trialCtx := ctx.WithMultiStore(ctx.MultiStore().CacheWrap().(storetypes.MultiStore))
_, err = keepers.ContractKeeper.Execute(trialCtx, addr, creator, []byte(`{"release":{}}`), nil)
require.Error(t, err)
require.True(t, errors.Is(err, types.ErrExecuteFailed))
require.Equal(t, "Unauthorized: execute wasm contract failed", err.Error())
// verifier can execute, and get proper gas amount
start := time.Now()
gasBefore := ctx.GasMeter().GasConsumed()
em := sdk.NewEventManager()
// when
res, err := keepers.ContractKeeper.Execute(ctx.WithEventManager(em), addr, fred, []byte(`{"release":{}}`), topUp)
diff := time.Since(start)
require.NoError(t, err)
require.NotNil(t, res)
// make sure gas is properly deducted from ctx
gasAfter := ctx.GasMeter().GasConsumed()
if types.EnableGasVerification {
require.Equal(t, uint64(0x1adc3), gasAfter-gasBefore)
}
// ensure bob now exists and got both payments released
bobAcct = accKeeper.GetAccount(ctx, bob)
require.NotNil(t, bobAcct)
balance := bankKeeper.GetAllBalances(ctx, bobAcct.GetAddress())
assert.Equal(t, deposit.Add(topUp...), balance)
// ensure contract has updated balance
contractAcct = accKeeper.GetAccount(ctx, addr)
require.NotNil(t, contractAcct)
assert.Equal(t, sdk.Coins{}, bankKeeper.GetAllBalances(ctx, contractAcct.GetAddress()))
// and events emitted
require.Len(t, em.Events(), 9)
expEvt := sdk.NewEvent("execute",
sdk.NewAttribute("_contract_address", addr.String()))
assert.Equal(t, expEvt, em.Events()[3], prettyEvents(t, em.Events()))
t.Logf("Duration: %v (%d gas)\n", diff, gasAfter-gasBefore)
}
func TestExecuteWithDeposit(t *testing.T) {
var (
bob = bytes.Repeat([]byte{1}, types.SDKAddrLen)
fred = bytes.Repeat([]byte{2}, types.SDKAddrLen)
blockedAddr = authtypes.NewModuleAddress(distributiontypes.ModuleName)
deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100))
)
specs := map[string]struct {
srcActor sdk.AccAddress
beneficiary sdk.AccAddress
newBankParams *banktypes.Params
expError bool
fundAddr bool
}{
"actor with funds": {
srcActor: bob,
fundAddr: true,
beneficiary: fred,
},
"actor without funds": {
srcActor: bob,
beneficiary: fred,
expError: true,
},
"blocked address as actor": {
srcActor: blockedAddr,
fundAddr: true,
beneficiary: fred,
expError: false,
},
"coin transfer with all transfers disabled": {
srcActor: bob,
fundAddr: true,
beneficiary: fred,
newBankParams: &banktypes.Params{DefaultSendEnabled: false},
expError: true,
},
"coin transfer with transfer denom disabled": {
srcActor: bob,
fundAddr: true,
beneficiary: fred,
newBankParams: &banktypes.Params{
DefaultSendEnabled: true,
SendEnabled: []*banktypes.SendEnabled{{Denom: "denom", Enabled: false}},
},
expError: true,
},
"blocked address as beneficiary": {
srcActor: bob,
fundAddr: true,
beneficiary: blockedAddr,
expError: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.ContractKeeper
if spec.newBankParams != nil {
err := bankKeeper.SetParams(ctx, *spec.newBankParams)
require.NoError(t, err)
}
if spec.fundAddr {
fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, sdk.NewCoins(sdk.NewInt64Coin("denom", 200)))
}
codeID, _, err := keeper.Create(ctx, spec.srcActor, hackatomWasm, nil)
require.NoError(t, err)
initMsg := HackatomExampleInitMsg{Verifier: spec.srcActor, Beneficiary: spec.beneficiary}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, spec.srcActor, nil, initMsgBz, "my label", nil)
require.NoError(t, err)
// when
_, err = keepers.ContractKeeper.Execute(ctx, contractAddr, spec.srcActor, []byte(`{"release":{}}`), deposit)
// then
if spec.expError {
require.Error(t, err)
return
}
require.NoError(t, err)
balances := bankKeeper.GetAllBalances(ctx, spec.beneficiary)
assert.Equal(t, deposit, balances)
})
}
}
func TestExecuteWithNonExistingAddress(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...)
// unauthorized - trialCtx so we don't change state
nonExistingAddress := RandomAccountAddress(t)
_, err := keeper.Execute(ctx, nonExistingAddress, creator, []byte(`{}`), nil)
require.Equal(t, types.ErrNoSuchContractFn(nonExistingAddress.String()).Wrapf("address %s", nonExistingAddress.String()).Error(), err.Error())
}
func TestExecuteWithPanic(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...)
fred := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...)
contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
_, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 4", deposit)
require.NoError(t, err)
// let's make sure we get a reasonable error, no panic/crash
_, err = keepers.ContractKeeper.Execute(ctx, addr, fred, []byte(`{"panic":{}}`), topUp)
require.Error(t, err)
require.True(t, errors.Is(err, types.ErrVMError))
// test with contains as "Display" implementation of the Wasmer "RuntimeError" is different for Mac and Linux
assert.Contains(t, err.Error(), "Error calling the VM: Error executing Wasm: Wasmer runtime error: RuntimeError: Aborted: panicked at 'This page intentionally faulted', src/contract.rs:169:5: wasmvm error")
}
func TestExecuteWithCpuLoop(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...)
fred := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...)
contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
_, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 5", deposit)
require.NoError(t, err)
// make sure we set a limit before calling
var gasLimit uint64 = 400_000
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(gasLimit))
require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed())
// ensure we get an out of gas panic
defer func() {
r := recover()
require.NotNil(t, r)
_, ok := r.(storetypes.ErrorOutOfGas)
require.True(t, ok, "%v", r)
}()
// this should throw out of gas exception (panic)
_, _ = keepers.ContractKeeper.Execute(ctx, addr, fred, []byte(`{"cpu_loop":{}}`), nil)
require.True(t, false, "We must panic before this line")
}
func TestExecuteWithStorageLoop(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...)
fred := keepers.Faucet.NewFundedRandomAccount(ctx, topUp...)
contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
_, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 6", deposit)
require.NoError(t, err)
// make sure we set a limit before calling
var gasLimit uint64 = 400_002
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(gasLimit))
require.Equal(t, uint64(0), ctx.GasMeter().GasConsumed())
// ensure we get an out of gas panic
defer func() {
r := recover()
require.NotNil(t, r)
_, ok := r.(storetypes.ErrorOutOfGas)
require.True(t, ok, "%v", r)
}()
// this should throw out of gas exception (panic)
_, _ = keepers.ContractKeeper.Execute(ctx, addr, fred, []byte(`{"storage_loop":{}}`), nil)
require.True(t, false, "We must panic before this line")
}
func TestMigrate(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(parentCtx, creator, deposit.Add(deposit...)...)
fred := DeterministicAccountAddress(t, 2)
keepers.Faucet.Fund(parentCtx, fred, topUp...)
originalCodeID := StoreHackatomExampleContract(t, parentCtx, keepers).CodeID
newCodeID := StoreHackatomExampleContract(t, parentCtx, keepers).CodeID
ibcCodeID := StoreIBCReflectContract(t, parentCtx, keepers).CodeID
require.NotEqual(t, originalCodeID, newCodeID)
restrictedCodeExample := StoreHackatomExampleContract(t, parentCtx, keepers)
require.NoError(t, keeper.SetAccessConfig(parentCtx, restrictedCodeExample.CodeID, restrictedCodeExample.CreatorAddr, types.AllowNobody))
require.NotEqual(t, originalCodeID, restrictedCodeExample.CodeID)
// store hackatom contracts with "migrate_version" attributes
hackatom42 := StoreExampleContract(t, parentCtx, keepers, "./testdata/hackatom_42.wasm")
hackatom420 := StoreExampleContract(t, parentCtx, keepers, "./testdata/hackatom_420.wasm")
anyAddr := RandomAccountAddress(t)
newVerifierAddr := RandomAccountAddress(t)
initMsgBz := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: anyAddr,
}.GetBytes(t)
migMsg := struct {
Verifier sdk.AccAddress `json:"verifier"`
}{Verifier: newVerifierAddr}
migMsgBz, err := json.Marshal(migMsg)
require.NoError(t, err)
specs := map[string]struct {
admin sdk.AccAddress
overrideContractAddr sdk.AccAddress
caller sdk.AccAddress
fromCodeID uint64
toCodeID uint64
migrateMsg []byte
expErr *errorsmod.Error
expVerifier sdk.AccAddress
expIBCPort bool
initMsg []byte
}{
"all good with same code id": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: originalCodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"all good with different code id": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: newCodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"all good with admin set": {
admin: fred,
caller: fred,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: newCodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"adds IBC port for IBC enabled contracts": {
admin: fred,
caller: fred,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: ibcCodeID,
migrateMsg: []byte(`{}`),
expIBCPort: true,
expVerifier: fred, // not updated
},
"prevent migration when admin was not set on instantiate": {
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: originalCodeID,
expErr: sdkerrors.ErrUnauthorized,
},
"prevent migration when not sent by admin": {
caller: creator,
admin: fred,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: originalCodeID,
expErr: sdkerrors.ErrUnauthorized,
},
"prevent migration when new code is restricted": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: restrictedCodeExample.CodeID,
migrateMsg: migMsgBz,
expErr: sdkerrors.ErrUnauthorized,
},
"fail with non existing code id": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: 99999,
expErr: sdkerrors.ErrInvalidRequest,
},
"fail with non existing contract addr": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
overrideContractAddr: anyAddr,
fromCodeID: originalCodeID,
toCodeID: originalCodeID,
expErr: sdkerrors.ErrInvalidRequest,
},
"fail in contract with invalid migrate msg": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: originalCodeID,
migrateMsg: bytes.Repeat([]byte{0x1}, 7),
expErr: types.ErrMigrationFailed,
},
"fail in contract without migrate msg": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: originalCodeID,
expErr: types.ErrVMError,
},
"fail when no IBC callbacks": {
admin: fred,
caller: fred,
initMsg: IBCReflectInitMsg{ReflectCodeID: StoreReflectContract(t, parentCtx, keepers).CodeID}.GetBytes(t),
fromCodeID: ibcCodeID,
toCodeID: newCodeID,
migrateMsg: migMsgBz,
expErr: types.ErrMigrationFailed,
},
"all good with migrate versions": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom42.CodeID,
toCodeID: hackatom420.CodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"all good with no migrate version to migrate version contract": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: originalCodeID,
toCodeID: hackatom42.CodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"all good with same migrate version": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom42.CodeID,
toCodeID: hackatom42.CodeID,
migrateMsg: migMsgBz,
expVerifier: fred, // not updated
},
"all good with migrate version contract to no migrate version contract": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom42.CodeID,
toCodeID: originalCodeID,
migrateMsg: migMsgBz,
expVerifier: newVerifierAddr,
},
"contract returns error when downgrading version": {
admin: creator,
caller: creator,
initMsg: initMsgBz,
fromCodeID: hackatom420.CodeID,
toCodeID: hackatom42.CodeID,
migrateMsg: migMsgBz,
expErr: types.ErrMigrationFailed,
},
}
blockHeight := parentCtx.BlockHeight()
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
// given a contract instance
ctx, _ := parentCtx.WithBlockHeight(blockHeight + 1).CacheContext()
blockHeight++
contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, spec.fromCodeID, creator, spec.admin, spec.initMsg, "demo contract", nil)
require.NoError(t, err)
if spec.overrideContractAddr != nil {
contractAddr = spec.overrideContractAddr
}
// when
_, err = keeper.Migrate(ctx, contractAddr, spec.caller, spec.toCodeID, spec.migrateMsg)
// then
require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err)
if spec.expErr != nil {
return
}
cInfo := keepers.WasmKeeper.GetContractInfo(ctx, contractAddr)
assert.Equal(t, spec.toCodeID, cInfo.CodeID)
assert.Equal(t, spec.expIBCPort, cInfo.IBCPortID != "", cInfo.IBCPortID)
expHistory := []types.ContractCodeHistoryEntry{{
Operation: types.ContractCodeHistoryOperationTypeInit,
CodeID: spec.fromCodeID,
Updated: types.NewAbsoluteTxPosition(ctx),
Msg: initMsgBz,
}, {
Operation: types.ContractCodeHistoryOperationTypeMigrate,
CodeID: spec.toCodeID,
Updated: types.NewAbsoluteTxPosition(ctx),
Msg: spec.migrateMsg,
}}
assert.Equal(t, expHistory, keepers.WasmKeeper.GetContractHistory(ctx, contractAddr))
// and verify contract state
raw := keepers.WasmKeeper.QueryRaw(ctx, contractAddr, []byte("config"))
var stored map[string]string
require.NoError(t, json.Unmarshal(raw, &stored))
require.Contains(t, stored, "verifier")
require.NoError(t, err)
assert.Equal(t, spec.expVerifier.String(), stored["verifier"])
})
}
}
func TestMigrateReplacesTheSecondIndex(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
example := InstantiateHackatomExampleContract(t, ctx, keepers)
// then assert a second index exists
store := keepers.WasmKeeper.storeService.OpenKVStore(ctx)
oldContractInfo := keepers.WasmKeeper.GetContractInfo(ctx, example.Contract)
require.NotNil(t, oldContractInfo)
createHistoryEntry := types.ContractCodeHistoryEntry{
CodeID: example.CodeID,
Updated: types.NewAbsoluteTxPosition(ctx),
}
exists, err := store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, createHistoryEntry))
require.NoError(t, err)
require.True(t, exists)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) // increment for different block
// when do migrate
newCodeExample := StoreBurnerExampleContract(t, ctx, keepers)
migMsgBz := BurnerExampleInitMsg{Payout: example.CreatorAddr}.GetBytes(t)
_, err = keepers.ContractKeeper.Migrate(ctx, example.Contract, example.CreatorAddr, newCodeExample.CodeID, migMsgBz)
require.NoError(t, err)
// then the new index exists
migrateHistoryEntry := types.ContractCodeHistoryEntry{
CodeID: newCodeExample.CodeID,
Updated: types.NewAbsoluteTxPosition(ctx),
}
exists, err = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, migrateHistoryEntry))
require.NoError(t, err)
require.True(t, exists)
// and the old index was removed
exists, err = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, createHistoryEntry))
require.NoError(t, err)
require.False(t, exists)
}
func TestMigrateWithDispatchedMessage(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...)
fred := keepers.Faucet.NewFundedRandomAccount(ctx, sdk.NewInt64Coin("denom", 5000))
burnerCode, err := os.ReadFile("./testdata/burner.wasm")
require.NoError(t, err)
originalContractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
burnerContractID, _, err := keeper.Create(ctx, creator, burnerCode, nil)
require.NoError(t, err)
require.NotEqual(t, originalContractID, burnerContractID)
_, myPayoutAddr := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: fred,
}
initMsgBz := initMsg.GetBytes(t)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, originalContractID, creator, fred, initMsgBz, "demo contract", deposit)
require.NoError(t, err)
migMsgBz := BurnerExampleInitMsg{Payout: myPayoutAddr, Delete: 100}.GetBytes(t)
ctx = ctx.WithEventManager(sdk.NewEventManager()).WithBlockHeight(ctx.BlockHeight() + 1)
_, err = keeper.Migrate(ctx, contractAddr, fred, burnerContractID, migMsgBz)
require.NoError(t, err)
type dict map[string]interface{}
expEvents := []dict{
{
"Type": "migrate",
"Attr": []dict{
{"code_id": "2"},
{"_contract_address": contractAddr},
},
},
{
"Type": "wasm",
"Attr": []dict{
{"_contract_address": contractAddr},
{"action": "migrate"},
{"payout": myPayoutAddr},
{"deleted_entries": "1"},
},
},
{
"Type": "coin_spent",
"Attr": []dict{
{"spender": contractAddr},
{"amount": "100000denom"},
},
},
{
"Type": "coin_received",
"Attr": []dict{
{"receiver": myPayoutAddr},
{"amount": "100000denom"},
},
},
{
"Type": "transfer",
"Attr": []dict{
{"recipient": myPayoutAddr},
{"sender": contractAddr},
{"amount": "100000denom"},
},
},
}
expJSONEvts := string(mustMarshal(t, expEvents))
assert.JSONEq(t, expJSONEvts, prettyEvents(t, ctx.EventManager().Events()), prettyEvents(t, ctx.EventManager().Events()))
// all persistent data cleared
m := keepers.WasmKeeper.QueryRaw(ctx, contractAddr, []byte("config"))
require.Len(t, m, 0)
// and all deposit tokens sent to myPayoutAddr
balance := keepers.BankKeeper.GetAllBalances(ctx, myPayoutAddr)
assert.Equal(t, deposit, balance)
}
func TestIterateContractsByCode(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k, c := keepers.WasmKeeper, keepers.ContractKeeper
example1 := InstantiateHackatomExampleContract(t, ctx, keepers)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
example2 := InstantiateIBCReflectContract(t, ctx, keepers)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
initMsg := HackatomExampleInitMsg{
Verifier: RandomAccountAddress(t),
Beneficiary: RandomAccountAddress(t),
}.GetBytes(t)
contractAddr3, _, err := c.Instantiate(ctx, example1.CodeID, example1.CreatorAddr, nil, initMsg, "foo", nil)
require.NoError(t, err)
specs := map[string]struct {
codeID uint64
exp []sdk.AccAddress
}{
"multiple results": {
codeID: example1.CodeID,
exp: []sdk.AccAddress{example1.Contract, contractAddr3},
},
"single results": {
codeID: example2.CodeID,
exp: []sdk.AccAddress{example2.Contract},
},
"empty results": {
codeID: 99999,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
var gotAddr []sdk.AccAddress
k.IterateContractsByCode(ctx, spec.codeID, func(address sdk.AccAddress) bool {
gotAddr = append(gotAddr, address)
return false
})
assert.Equal(t, spec.exp, gotAddr)
})
}
}
func TestIterateContractsByCodeWithMigration(t *testing.T) {
// mock migration so that it does not fail when migrate example1 to example2.codeID
mockWasmVM := wasmtesting.MockWasmEngine{
MigrateFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 1, nil
},
MigrateWithInfoFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, migrateInfo wasmvmtypes.MigrateInfo, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 1, nil
},
}
wasmtesting.MakeInstantiable(&mockWasmVM)
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithWasmEngine(&mockWasmVM))
k, c := keepers.WasmKeeper, keepers.ContractKeeper
example1 := InstantiateHackatomExampleContract(t, ctx, keepers)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
example2 := InstantiateIBCReflectContract(t, ctx, keepers)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
_, err := c.Migrate(ctx, example1.Contract, example1.CreatorAddr, example2.CodeID, []byte("{}"))
require.NoError(t, err)
// when
var gotAddr []sdk.AccAddress
k.IterateContractsByCode(ctx, example2.CodeID, func(address sdk.AccAddress) bool {
gotAddr = append(gotAddr, address)
return false
})
// then
exp := []sdk.AccAddress{example2.Contract, example1.Contract}
assert.Equal(t, exp, gotAddr)
}
type sudoMsg struct {
// This is a tongue-in-check demo command. This is not the intended purpose of Sudo.
// Here we show that some privileged Go module can make a call that should never be exposed
// to end users (via Tx/Execute).
//
// The contract developer can choose to expose anything to sudo. This functionality is not a true
// backdoor (it can never be called by end users), but allows the developers of the native blockchain
// code to make special calls. This can also be used as an authentication mechanism, if you want to expose
// some callback that only can be triggered by some system module and not faked by external users.
StealFunds stealFundsMsg `json:"steal_funds"`
}
type stealFundsMsg struct {
Recipient string `json:"recipient"`
Amount wasmvmtypes.Array[wasmvmtypes.Coin] `json:"amount"`
}
func TestSudo(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...)
contractID, _, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.NoError(t, err)
_, bob := keyPubAddr()
_, fred := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit)
require.NoError(t, err)
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", addr.String())
// the community is broke
_, community := keyPubAddr()
comAcct := accKeeper.GetAccount(ctx, community)
require.Nil(t, comAcct)
// now the community wants to get paid via sudo
msg := sudoMsg{
// This is a tongue-in-check demo command. This is not the intended purpose of Sudo.
// Here we show that some privileged Go module can make a call that should never be exposed
// to end users (via Tx/Execute).
StealFunds: stealFundsMsg{
Recipient: community.String(),
Amount: wasmvmtypes.Array[wasmvmtypes.Coin]{wasmvmtypes.NewCoin(76543, "denom")},
},
}
sudoMsg, err := json.Marshal(msg)
require.NoError(t, err)
em := sdk.NewEventManager()
// when
_, err = keepers.WasmKeeper.Sudo(ctx.WithEventManager(em), addr, sudoMsg)
require.NoError(t, err)
// ensure community now exists and got paid
comAcct = accKeeper.GetAccount(ctx, community)
require.NotNil(t, comAcct)
balance := bankKeeper.GetBalance(ctx, comAcct.GetAddress(), "denom")
assert.Equal(t, sdk.NewInt64Coin("denom", 76543), balance)
// and events emitted
require.Len(t, em.Events(), 4, prettyEvents(t, em.Events()))
expEvt := sdk.NewEvent("sudo",
sdk.NewAttribute("_contract_address", addr.String()))
assert.Equal(t, expEvt, em.Events()[0])
}
func prettyEvents(t *testing.T, events sdk.Events) string {
t.Helper()
type prettyEvent struct {
Type string
Attr []map[string]string
}
r := make([]prettyEvent, len(events))
for i, e := range events {
attr := make([]map[string]string, len(e.Attributes))
for j, a := range e.Attributes {
attr[j] = map[string]string{a.Key: a.Value}
}
r[i] = prettyEvent{Type: e.Type, Attr: attr}
}
return string(mustMarshal(t, r))
}
func mustMarshal(t *testing.T, r interface{}) []byte {
t.Helper()
bz, err := json.Marshal(r)
require.NoError(t, err)
return bz
}
func TestUpdateContractAdmin(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(parentCtx, creator, deposit.Add(deposit...)...)
fred := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...)
originalContractID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil)
require.NoError(t, err)
_, anyAddr := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: anyAddr,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
specs := map[string]struct {
instAdmin sdk.AccAddress
newAdmin sdk.AccAddress
overrideContractAddr sdk.AccAddress
caller sdk.AccAddress
expErr *errorsmod.Error
}{
"all good with admin set": {
instAdmin: fred,
newAdmin: anyAddr,
caller: fred,
},
"prevent update when admin was not set on instantiate": {
caller: creator,
newAdmin: fred,
expErr: sdkerrors.ErrUnauthorized,
},
"prevent updates from non admin address": {
instAdmin: creator,
newAdmin: fred,
caller: fred,
expErr: sdkerrors.ErrUnauthorized,
},
"fail with non existing contract addr": {
instAdmin: creator,
newAdmin: anyAddr,
caller: creator,
overrideContractAddr: anyAddr,
expErr: sdkerrors.ErrInvalidRequest,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, originalContractID, creator, spec.instAdmin, initMsgBz, "demo contract", nil)
require.NoError(t, err)
if spec.overrideContractAddr != nil {
addr = spec.overrideContractAddr
}
err = keeper.UpdateContractAdmin(ctx, addr, spec.caller, spec.newAdmin)
require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err)
if spec.expErr != nil {
return
}
cInfo := keepers.WasmKeeper.GetContractInfo(ctx, addr)
assert.Equal(t, spec.newAdmin.String(), cInfo.Admin)
})
}
}
func TestClearContractAdmin(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(parentCtx, creator, deposit.Add(deposit...)...)
fred := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...)
originalContractID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil)
require.NoError(t, err)
_, anyAddr := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: anyAddr,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
specs := map[string]struct {
instAdmin sdk.AccAddress
overrideContractAddr sdk.AccAddress
caller sdk.AccAddress
expErr *errorsmod.Error
}{
"all good when called by proper admin": {
instAdmin: fred,
caller: fred,
},
"prevent update when admin was not set on instantiate": {
caller: creator,
expErr: sdkerrors.ErrUnauthorized,
},
"prevent updates from non admin address": {
instAdmin: creator,
caller: fred,
expErr: sdkerrors.ErrUnauthorized,
},
"fail with non existing contract addr": {
instAdmin: creator,
caller: creator,
overrideContractAddr: anyAddr,
expErr: sdkerrors.ErrInvalidRequest,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, originalContractID, creator, spec.instAdmin, initMsgBz, "demo contract", nil)
require.NoError(t, err)
if spec.overrideContractAddr != nil {
addr = spec.overrideContractAddr
}
err = keeper.ClearContractAdmin(ctx, addr, spec.caller)
require.True(t, spec.expErr.Is(err), "expected %v but got %+v", spec.expErr, err)
if spec.expErr != nil {
return
}
cInfo := keepers.WasmKeeper.GetContractInfo(ctx, addr)
assert.Empty(t, cInfo.Admin)
})
}
}
func TestPinCode(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
var capturedChecksums []wasmvm.Checksum
mock := wasmtesting.MockWasmEngine{PinFn: func(checksum wasmvm.Checksum) error {
capturedChecksums = append(capturedChecksums, checksum)
return nil
}}
wasmtesting.MakeInstantiable(&mock)
myCodeID := StoreRandomContract(t, ctx, keepers, &mock).CodeID
require.Equal(t, uint64(1), myCodeID)
em := sdk.NewEventManager()
// when
gotErr := k.pinCode(ctx.WithEventManager(em), myCodeID)
// then
require.NoError(t, gotErr)
assert.NotEmpty(t, capturedChecksums)
assert.True(t, k.IsPinnedCode(ctx, myCodeID))
// and events
exp := sdk.Events{sdk.NewEvent("pin_code", sdk.NewAttribute("code_id", "1"))}
assert.Equal(t, exp, em.Events())
}
func TestUnpinCode(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
var capturedChecksums []wasmvm.Checksum
mock := wasmtesting.MockWasmEngine{
PinFn: func(checksum wasmvm.Checksum) error {
return nil
},
UnpinFn: func(checksum wasmvm.Checksum) error {
capturedChecksums = append(capturedChecksums, checksum)
return nil
},
}
wasmtesting.MakeInstantiable(&mock)
myCodeID := StoreRandomContract(t, ctx, keepers, &mock).CodeID
require.Equal(t, uint64(1), myCodeID)
err := k.pinCode(ctx, myCodeID)
require.NoError(t, err)
em := sdk.NewEventManager()
// when
gotErr := k.unpinCode(ctx.WithEventManager(em), myCodeID)
// then
require.NoError(t, gotErr)
assert.NotEmpty(t, capturedChecksums)
assert.False(t, k.IsPinnedCode(ctx, myCodeID))
// and events
exp := sdk.Events{sdk.NewEvent("unpin_code", sdk.NewAttribute("code_id", "1"))}
assert.Equal(t, exp, em.Events())
}
func TestInitializePinnedCodes(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
var capturedChecksums []wasmvm.Checksum
mock := wasmtesting.MockWasmEngine{PinFn: func(checksum wasmvm.Checksum) error {
capturedChecksums = append(capturedChecksums, checksum)
return nil
}}
wasmtesting.MakeInstantiable(&mock)
const testItems = 3
myCodeIDs := make([]uint64, testItems)
for i := 0; i < testItems; i++ {
myCodeIDs[i] = StoreRandomContract(t, ctx, keepers, &mock).CodeID
require.NoError(t, k.pinCode(ctx, myCodeIDs[i]))
}
capturedChecksums = nil
// when
gotErr := k.InitializePinnedCodes(ctx)
// then
require.NoError(t, gotErr)
require.Len(t, capturedChecksums, testItems)
for i, c := range myCodeIDs {
var exp wasmvm.Checksum = k.GetCodeInfo(ctx, c).CodeHash
assert.Equal(t, exp, capturedChecksums[i])
}
}
func TestPinnedContractLoops(t *testing.T) {
var capturedChecksums []wasmvm.Checksum
mock := wasmtesting.MockWasmEngine{PinFn: func(checksum wasmvm.Checksum) error {
capturedChecksums = append(capturedChecksums, checksum)
return nil
}}
wasmtesting.MakeInstantiable(&mock)
// a pinned contract that calls itself via submessages should terminate with an
// error at some point
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities, WithWasmEngine(&mock))
k := keepers.WasmKeeper
example := SeedNewContractInstance(t, ctx, keepers, &mock)
require.NoError(t, k.pinCode(ctx, example.CodeID))
var loops int
anyMsg := []byte(`{}`)
mock.ExecuteFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, executeMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
loops++
return &wasmvmtypes.ContractResult{
Ok: &wasmvmtypes.Response{
Messages: []wasmvmtypes.SubMsg{
{
ID: 1,
ReplyOn: wasmvmtypes.ReplyNever,
Msg: wasmvmtypes.CosmosMsg{
Wasm: &wasmvmtypes.WasmMsg{
Execute: &wasmvmtypes.ExecuteMsg{
ContractAddr: example.Contract.String(),
Msg: anyMsg,
},
},
},
},
},
},
}, 0, nil
}
ctx = ctx.WithGasMeter(storetypes.NewGasMeter(30_000))
require.PanicsWithValue(t, storetypes.ErrorOutOfGas{Descriptor: "ReadFlat"}, func() {
_, err := k.execute(ctx, example.Contract, RandomAccountAddress(t), anyMsg, nil)
require.NoError(t, err)
})
assert.True(t, ctx.GasMeter().IsOutOfGas())
assert.Greater(t, loops, 2)
}
func TestNewDefaultWasmVMContractResponseHandler(t *testing.T) {
specs := map[string]struct {
srcData []byte
setup func(m *wasmtesting.MockMsgDispatcher)
expErr bool
expData []byte
expEvts sdk.Events
}{
"submessage overwrites result when set": {
srcData: []byte("otherData"),
setup: func(m *wasmtesting.MockMsgDispatcher) {
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
return []byte("mySubMsgData"), nil
}
},
expErr: false,
expData: []byte("mySubMsgData"),
expEvts: sdk.Events{},
},
"submessage overwrites result when empty": {
srcData: []byte("otherData"),
setup: func(m *wasmtesting.MockMsgDispatcher) {
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
return []byte(""), nil
}
},
expErr: false,
expData: []byte(""),
expEvts: sdk.Events{},
},
"submessage do not overwrite result when nil": {
srcData: []byte("otherData"),
setup: func(m *wasmtesting.MockMsgDispatcher) {
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
return nil, nil
}
},
expErr: false,
expData: []byte("otherData"),
expEvts: sdk.Events{},
},
"submessage error aborts process": {
setup: func(m *wasmtesting.MockMsgDispatcher) {
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
return nil, errors.New("test - ignore")
}
},
expErr: true,
},
"message emit non message events": {
setup: func(m *wasmtesting.MockMsgDispatcher) {
m.DispatchSubmessagesFn = func(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) {
ctx.EventManager().EmitEvent(sdk.NewEvent("myEvent"))
return nil, nil
}
},
expEvts: sdk.Events{sdk.NewEvent("myEvent")},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
var msgs []wasmvmtypes.SubMsg
var mock wasmtesting.MockMsgDispatcher
spec.setup(&mock)
d := NewDefaultWasmVMContractResponseHandler(&mock)
em := sdk.NewEventManager()
// when
gotData, gotErr := d.Handle(sdk.Context{}.WithEventManager(em), RandomAccountAddress(t), "ibc-port", msgs, spec.srcData)
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.expData, gotData)
assert.Equal(t, spec.expEvts, em.Events())
})
}
}
func TestReply(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
var mock wasmtesting.MockWasmEngine
wasmtesting.MakeInstantiable(&mock)
example := SeedNewContractInstance(t, ctx, keepers, &mock)
specs := map[string]struct {
replyFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error)
expData []byte
expErr bool
expEvt sdk.Events
}{
"all good": {
replyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) {
return &wasmvmtypes.Response{Data: []byte("foo")}, 1, nil
},
expData: []byte("foo"),
expEvt: sdk.Events{sdk.NewEvent("reply", sdk.NewAttribute("_contract_address", example.Contract.String()))},
},
"with query": {
replyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) {
bzRsp, err := querier.Query(wasmvmtypes.QueryRequest{
Bank: &wasmvmtypes.BankQuery{
Balance: &wasmvmtypes.BalanceQuery{Address: env.Contract.Address, Denom: "stake"},
},
}, 10_000*types.DefaultGasMultiplier)
require.NoError(t, err)
var gotBankRsp wasmvmtypes.BalanceResponse
require.NoError(t, json.Unmarshal(bzRsp, &gotBankRsp))
assert.Equal(t, wasmvmtypes.BalanceResponse{Amount: wasmvmtypes.NewCoin(0, "stake")}, gotBankRsp)
return &wasmvmtypes.Response{Data: []byte("foo")}, 1, nil
},
expData: []byte("foo"),
expEvt: sdk.Events{sdk.NewEvent("reply", sdk.NewAttribute("_contract_address", example.Contract.String()))},
},
"with query error handled": {
replyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) {
bzRsp, err := querier.Query(wasmvmtypes.QueryRequest{}, 0)
require.Error(t, err)
assert.Nil(t, bzRsp)
return &wasmvmtypes.Response{Data: []byte("foo")}, 1, nil
},
expData: []byte("foo"),
expEvt: sdk.Events{sdk.NewEvent("reply", sdk.NewAttribute("_contract_address", example.Contract.String()))},
},
"error": {
replyFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.Response, uint64, error) {
return nil, 1, errors.New("testing")
},
expErr: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
mock.ReplyFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
resp, gasUsed, err := spec.replyFn(codeID, env, reply, store, goapi, querier, gasMeter, gasLimit, deserCost)
return &wasmvmtypes.ContractResult{
Ok: resp,
}, gasUsed, err
}
em := sdk.NewEventManager()
gotData, gotErr := k.reply(ctx.WithEventManager(em), example.Contract, wasmvmtypes.Reply{})
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.expData, gotData)
assert.Equal(t, spec.expEvt, em.Events())
})
}
}
type replierExecMsg struct {
MsgId byte `json:"msg_id"`
SetDataInExecAndReply bool `json:"set_data_in_exec_and_reply"`
ReturnOrderInReply bool `json:"return_order_in_reply"`
ExecError bool `json:"exec_error"`
ReplyError bool `json:"reply_error"`
ReplyOnNever bool `json:"reply_on_never"`
Messages []replierExecMsg `json:"messages"`
}
func defaultRepliesMsgTemplate() replierExecMsg {
return replierExecMsg{
MsgId: 1,
SetDataInExecAndReply: true,
ReturnOrderInReply: false,
ExecError: false,
ReplyError: false,
ReplyOnNever: false,
Messages: []replierExecMsg{
{
MsgId: 2,
SetDataInExecAndReply: true,
ReturnOrderInReply: false,
ExecError: false,
ReplyError: false,
ReplyOnNever: false,
Messages: []replierExecMsg{
{
MsgId: 3,
SetDataInExecAndReply: true,
ReturnOrderInReply: false,
ExecError: false,
ReplyError: false,
ReplyOnNever: false,
Messages: []replierExecMsg{},
},
},
},
{
MsgId: 4,
SetDataInExecAndReply: true,
ReturnOrderInReply: false,
ExecError: false,
ReplyError: false,
ReplyOnNever: false,
Messages: []replierExecMsg{
{
MsgId: 5,
SetDataInExecAndReply: true,
ReturnOrderInReply: false,
ExecError: false,
ReplyError: false,
ReplyOnNever: false,
Messages: []replierExecMsg{},
},
},
},
},
}
}
func repliesMsgTemplateReturnOrder() replierExecMsg {
repliesMsgTemplate := defaultRepliesMsgTemplate()
repliesMsgTemplate.ReturnOrderInReply = true
return repliesMsgTemplate
}
func repliesMsgTemplateReplyNever() replierExecMsg {
repliesMsgTemplate := defaultRepliesMsgTemplate()
repliesMsgTemplate.Messages[1].ReplyOnNever = true
return repliesMsgTemplate
}
func repliesMsgTemplateNoDataInResp() replierExecMsg {
repliesMsgTemplate := defaultRepliesMsgTemplate()
repliesMsgTemplate.Messages[1].SetDataInExecAndReply = false
return repliesMsgTemplate
}
func repliesMsgTemplateExecError() replierExecMsg {
repliesMsgTemplate := defaultRepliesMsgTemplate()
repliesMsgTemplate.Messages[0].Messages[0].ExecError = true
repliesMsgTemplate.ReturnOrderInReply = true
return repliesMsgTemplate
}
func repliesMsgTemplateReplyError() replierExecMsg {
repliesMsgTemplate := defaultRepliesMsgTemplate()
repliesMsgTemplate.Messages[0].ReplyError = true
repliesMsgTemplate.ReturnOrderInReply = true
return repliesMsgTemplate
}
var repliesTestScenarios = []struct {
name string
in replierExecMsg
out []byte
}{
{
"Assert the depth-first order of message handling",
repliesMsgTemplateReturnOrder(),
[]byte{0xee, 0x1, 0xee, 0x2, 0xee, 0x3, 0xbb, 0x2, 0xbb, 0x1, 0xee, 0x4, 0xee, 0x5, 0xbb, 0x4, 0xbb, 0x1},
},
{
"Assert that with a list of submessages the `data` field will be set by the last submessage",
defaultRepliesMsgTemplate(),
[]byte{0xa, 0x6, 0xa, 0x2, 0xee, 0x5, 0xbb, 0x4, 0xbb, 0x1},
},
{
"Assert that with a list of submessages the `data` field will be set by the last submessage that overrides it.",
repliesMsgTemplateReplyNever(),
[]byte{0xa, 0x6, 0xa, 0x2, 0xee, 0x3, 0xbb, 0x2, 0xbb, 0x1},
},
// Assert that in scenario C1 -> C4 -> C5 if C4 doesn't set `data`,
// the `data` set by C5 **is not forwarded** to the result of C1.
{
"Check data field forwarding",
repliesMsgTemplateNoDataInResp(),
[]byte{0xbb, 0x1},
},
// In this example we have the following scenario:
// `C1 -> C2 -> C3 -> reply(C2) -> reply(C1) -> C4 -> C5 -> reply(C4) -> reply(C1)`.
// The `C3` contract returns an error that is handled by reply entrypoint of `C2`.
// It means that the changes done by `C3` are reverted, but the rest of the changes are kept.
{
"Check error handling when execute fails",
repliesMsgTemplateExecError(),
[]byte{0xee, 0x1, 0xee, 0x2, 0xbb, 0x2, 0xbb, 0x1, 0xee, 0x4, 0xee, 0x5, 0xbb, 0x4, 0xbb, 0x1},
},
// The `C2` contract returns an error in reply entry-point that is handled by reply entrypoint of `C1`.
// It means that the changes done by either `C2` and `C3` are reverted, but the rest of the changes are kept.
{
"Check error handling when reply fails",
repliesMsgTemplateReplyError(),
[]byte{0xee, 0x1, 0xbb, 0x1, 0xee, 0x4, 0xee, 0x5, 0xbb, 0x4, 0xbb, 0x1},
},
}
func TestMultipleReplies(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
_, keeper, _ := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(ctx, creator, deposit.Add(deposit...)...)
creatorAddr := RandomAccountAddress(t)
contractID, _, err := keeper.Create(ctx, creator, replierWasm, nil)
require.NoError(t, err)
require.NoError(t, err)
addr, _, err := keepers.ContractKeeper.Instantiate(ctx, contractID, creator, nil, []byte("{}"), "demo contract replier", deposit)
require.NoError(t, err)
for _, tt := range repliesTestScenarios {
t.Run(tt.name, func(t *testing.T) {
execMsg, err := json.Marshal(tt.in)
require.NoError(t, err)
em := sdk.NewEventManager()
res, err := keepers.ContractKeeper.Execute(ctx.WithEventManager(em), addr, creatorAddr, execMsg, nil)
require.NoError(t, err)
require.NotNil(t, res)
assert.Equal(t, tt.out, res)
})
}
}
func TestQueryIsolation(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
var mock wasmtesting.MockWasmEngine
wasmtesting.MakeInstantiable(&mock)
example := SeedNewContractInstance(t, ctx, keepers, &mock)
WithQueryHandlerDecorator(func(other WasmVMQueryHandler) WasmVMQueryHandler {
return WasmVMQueryHandlerFn(func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) {
if request.Custom == nil {
return other.HandleQuery(ctx, caller, request)
}
// here we write to DB which should not be persisted
err := k.storeService.OpenKVStore(ctx).Set([]byte(`set_in_query`), []byte(`this_is_allowed`))
return nil, err
})
}).apply(k)
// when
mock.ReplyFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, reply wasmvmtypes.Reply, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) {
_, err := querier.Query(wasmvmtypes.QueryRequest{
Custom: []byte(`{}`),
}, 10000*types.DefaultGasMultiplier)
require.NoError(t, err)
return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 0, nil
}
em := sdk.NewEventManager()
_, gotErr := k.reply(ctx.WithEventManager(em), example.Contract, wasmvmtypes.Reply{})
require.NoError(t, gotErr)
got, err := k.storeService.OpenKVStore(ctx).Get([]byte(`set_in_query`))
require.NoError(t, err)
assert.Nil(t, got)
}
func TestSetAccessConfig(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
creatorAddr := RandomAccountAddress(t)
nonCreatorAddr := RandomAccountAddress(t)
const codeID = 1
specs := map[string]struct {
authz types.AuthorizationPolicy
chainPermission types.AccessType
newConfig types.AccessConfig
caller sdk.AccAddress
expErr bool
expEvts map[string]string
}{
"user with new permissions == chain permissions": {
authz: DefaultAuthorizationPolicy{},
chainPermission: types.AccessTypeEverybody,
newConfig: types.AllowEverybody,
caller: creatorAddr,
expEvts: map[string]string{
"code_id": "1",
"code_permission": "Everybody",
},
},
"user with new permissions < chain permissions": {
authz: DefaultAuthorizationPolicy{},
chainPermission: types.AccessTypeEverybody,
newConfig: types.AllowNobody,
caller: creatorAddr,
expEvts: map[string]string{
"code_id": "1",
"code_permission": "Nobody",
},
},
"user with new permissions > chain permissions": {
authz: DefaultAuthorizationPolicy{},
chainPermission: types.AccessTypeNobody,
newConfig: types.AllowEverybody,
caller: creatorAddr,
expErr: true,
},
"different actor": {
authz: DefaultAuthorizationPolicy{},
chainPermission: types.AccessTypeEverybody,
newConfig: types.AllowEverybody,
caller: nonCreatorAddr,
expErr: true,
},
"gov with new permissions == chain permissions": {
authz: GovAuthorizationPolicy{},
chainPermission: types.AccessTypeEverybody,
newConfig: types.AllowEverybody,
caller: creatorAddr,
expEvts: map[string]string{
"code_id": "1",
"code_permission": "Everybody",
},
},
"gov with new permissions < chain permissions": {
authz: GovAuthorizationPolicy{},
chainPermission: types.AccessTypeEverybody,
newConfig: types.AllowNobody,
caller: creatorAddr,
expEvts: map[string]string{
"code_id": "1",
"code_permission": "Nobody",
},
},
"gov with new permissions > chain permissions - multiple addresses": {
authz: GovAuthorizationPolicy{},
chainPermission: types.AccessTypeNobody,
newConfig: types.AccessTypeAnyOfAddresses.With(creatorAddr, nonCreatorAddr),
caller: creatorAddr,
expEvts: map[string]string{
"code_id": "1",
"code_permission": "AnyOfAddresses",
"authorized_addresses": creatorAddr.String() + "," + nonCreatorAddr.String(),
},
},
"gov without actor": {
authz: GovAuthorizationPolicy{},
chainPermission: types.AccessTypeEverybody,
newConfig: types.AllowEverybody,
expEvts: map[string]string{
"code_id": "1",
"code_permission": "Everybody",
},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
em := sdk.NewEventManager()
ctx = ctx.WithEventManager(em)
newParams := types.DefaultParams()
newParams.InstantiateDefaultPermission = spec.chainPermission
err := k.SetParams(ctx, newParams)
require.NoError(t, err)
k.mustStoreCodeInfo(ctx, codeID, types.NewCodeInfo(nil, creatorAddr, types.AllowNobody))
// when
gotErr := k.setAccessConfig(ctx, codeID, spec.caller, spec.newConfig, spec.authz)
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
// and event emitted
require.Len(t, em.Events(), 1)
assert.Equal(t, "update_code_access_config", em.Events()[0].Type)
assert.Equal(t, spec.expEvts, attrsToStringMap(em.Events()[0].Attributes))
})
}
}
func TestAppendToContractHistory(t *testing.T) {
f := fuzz.New().Funcs(ModelFuzzers...)
pCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
variableLengthAddresses := []sdk.AccAddress{
bytes.Repeat([]byte{0x1}, types.ContractAddrLen),
append([]byte{0x00}, bytes.Repeat([]byte{0x1}, types.ContractAddrLen-1)...),
append(bytes.Repeat([]byte{0x1}, types.ContractAddrLen-1), 0x00),
append([]byte{0xff}, bytes.Repeat([]byte{0x1}, types.ContractAddrLen-1)...),
append(bytes.Repeat([]byte{0x1}, types.ContractAddrLen-1), 0xff),
bytes.Repeat([]byte{0x1}, types.SDKAddrLen),
append([]byte{0x00}, bytes.Repeat([]byte{0x1}, types.SDKAddrLen-1)...),
append(bytes.Repeat([]byte{0x1}, types.SDKAddrLen-1), 0x00),
append([]byte{0xff}, bytes.Repeat([]byte{0x1}, types.SDKAddrLen-1)...),
append(bytes.Repeat([]byte{0x1}, types.SDKAddrLen-1), 0xff),
}
sRandom := stdrand.New(stdrand.NewSource(0))
for n := 0; n < 100; n++ {
t.Run(fmt.Sprintf("iteration %d", n), func(t *testing.T) {
sRandom.Seed(int64(n))
sRandom.Shuffle(len(variableLengthAddresses), func(i, j int) {
variableLengthAddresses[i], variableLengthAddresses[j] = variableLengthAddresses[j], variableLengthAddresses[i]
})
orderedEntries := make([][]types.ContractCodeHistoryEntry, len(variableLengthAddresses))
ctx, _ := pCtx.CacheContext()
for j, addr := range variableLengthAddresses {
for i := 0; i < 10; i++ {
var entry types.ContractCodeHistoryEntry
f.RandSource(sRandom).Fuzz(&entry)
require.NoError(t, k.appendToContractHistory(ctx, addr, entry))
orderedEntries[j] = append(orderedEntries[j], entry)
}
}
// when
for j, addr := range variableLengthAddresses {
gotHistory := k.GetContractHistory(ctx, addr)
assert.Equal(t, orderedEntries[j], gotHistory, "%d: %X", j, addr)
assert.Equal(t, orderedEntries[j][len(orderedEntries[j])-1], k.mustGetLastContractHistoryEntry(ctx, addr))
}
})
}
}
func TestCoinBurnerPruneBalances(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
amts := sdk.NewCoins(sdk.NewInt64Coin("denom", 100))
senderAddr := keepers.Faucet.NewFundedRandomAccount(parentCtx, amts...)
// create vesting account
var vestingAddr sdk.AccAddress = rand.Bytes(types.ContractAddrLen)
msgCreateVestingAccount := vestingtypes.NewMsgCreateVestingAccount(senderAddr, vestingAddr, amts, time.Now().Add(time.Minute).Unix(), false)
_, err := vesting.NewMsgServerImpl(keepers.AccountKeeper, keepers.BankKeeper).CreateVestingAccount(parentCtx, msgCreateVestingAccount)
require.NoError(t, err)
myVestingAccount := keepers.AccountKeeper.GetAccount(parentCtx, vestingAddr)
require.NotNil(t, myVestingAccount)
specs := map[string]struct {
setupAcc func(t *testing.T, ctx sdk.Context) sdk.AccountI
expBalances sdk.Coins
expHandled bool
expErr *errorsmod.Error
}{
"vesting account - all removed": {
setupAcc: func(t *testing.T, ctx sdk.Context) sdk.AccountI { return myVestingAccount },
expBalances: sdk.NewCoins(),
expHandled: true,
},
"vesting account with other tokens - only original denoms removed": {
setupAcc: func(t *testing.T, ctx sdk.Context) sdk.AccountI {
keepers.Faucet.Fund(ctx, vestingAddr, sdk.NewCoin("other", sdkmath.NewInt(2)))
return myVestingAccount
},
expBalances: sdk.NewCoins(sdk.NewCoin("other", sdkmath.NewInt(2))),
expHandled: true,
},
"non vesting account - not handled": {
setupAcc: func(t *testing.T, ctx sdk.Context) sdk.AccountI {
return &authtypes.BaseAccount{Address: myVestingAccount.GetAddress().String()}
},
expBalances: sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(100))),
expHandled: false,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
existingAccount := spec.setupAcc(t, ctx)
// overwrite account in store as in keeper before calling prune
keepers.AccountKeeper.SetAccount(ctx, keepers.AccountKeeper.NewAccountWithAddress(ctx, vestingAddr))
// when
noGasCtx := ctx.WithGasMeter(storetypes.NewGasMeter(0)) // should not use callers gas
gotHandled, gotErr := NewVestingCoinBurner(keepers.BankKeeper).CleanupExistingAccount(noGasCtx, existingAccount)
// then
if spec.expErr != nil {
require.ErrorIs(t, gotErr, spec.expErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.expBalances, keepers.BankKeeper.GetAllBalances(ctx, vestingAddr))
assert.Equal(t, spec.expHandled, gotHandled)
// and no out of gas panic
})
}
}
func TestIteratorAllContract(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, AvailableCapabilities)
example1 := InstantiateHackatomExampleContract(t, ctx, keepers)
example2 := InstantiateHackatomExampleContract(t, ctx, keepers)
example3 := InstantiateHackatomExampleContract(t, ctx, keepers)
example4 := InstantiateHackatomExampleContract(t, ctx, keepers)
var allContract []string
keepers.WasmKeeper.IterateContractInfo(ctx, func(addr sdk.AccAddress, _ types.ContractInfo) bool {
allContract = append(allContract, addr.String())
return false
})
// IterateContractInfo not ordering
expContracts := []string{example4.Contract.String(), example2.Contract.String(), example1.Contract.String(), example3.Contract.String()}
require.Equal(t, allContract, expContracts)
}
func TestIteratorContractByCreator(t *testing.T) {
// setup test
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
keeper := keepers.ContractKeeper
depositFund := sdk.NewCoins(sdk.NewInt64Coin("denom", 1000000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := DeterministicAccountAddress(t, 1)
keepers.Faucet.Fund(parentCtx, creator, depositFund.Add(depositFund...)...)
mockAddress1 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...)
mockAddress2 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...)
mockAddress3 := keepers.Faucet.NewFundedRandomAccount(parentCtx, topUp...)
contract1ID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil)
require.NoError(t, err)
contract2ID, _, err := keeper.Create(parentCtx, creator, hackatomWasm, nil)
require.NoError(t, err)
initMsgBz := HackatomExampleInitMsg{
Verifier: mockAddress1,
Beneficiary: mockAddress1,
}.GetBytes(t)
depositContract := sdk.NewCoins(sdk.NewCoin("denom", sdkmath.NewInt(1_000)))
gotAddr1, _, _ := keepers.ContractKeeper.Instantiate(parentCtx, contract1ID, mockAddress1, nil, initMsgBz, "label", depositContract)
ctx := parentCtx.WithBlockHeight(parentCtx.BlockHeight() + 1)
gotAddr2, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract1ID, mockAddress2, nil, initMsgBz, "label", depositContract)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
gotAddr3, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract1ID, gotAddr1, nil, initMsgBz, "label", depositContract)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
gotAddr4, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract2ID, mockAddress2, nil, initMsgBz, "label", depositContract)
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
gotAddr5, _, _ := keepers.ContractKeeper.Instantiate(ctx, contract2ID, mockAddress2, nil, initMsgBz, "label", depositContract)
specs := map[string]struct {
creatorAddr sdk.AccAddress
contractsAddr []string
}{
"single contract": {
creatorAddr: mockAddress1,
contractsAddr: []string{gotAddr1.String()},
},
"multiple contracts": {
creatorAddr: mockAddress2,
contractsAddr: []string{gotAddr2.String(), gotAddr4.String(), gotAddr5.String()},
},
"contractAddress": {
creatorAddr: gotAddr1,
contractsAddr: []string{gotAddr3.String()},
},
"no contracts- unknown": {
creatorAddr: mockAddress3,
contractsAddr: nil,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
var allContract []string
keepers.WasmKeeper.IterateContractsByCreator(parentCtx, spec.creatorAddr, func(addr sdk.AccAddress) bool {
allContract = append(allContract, addr.String())
return false
})
require.Equal(t,
allContract,
spec.contractsAddr,
)
})
}
}
func TestSetContractAdmin(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
myAddr := RandomAccountAddress(t)
example := InstantiateReflectExampleContract(t, parentCtx, keepers)
specs := map[string]struct {
newAdmin sdk.AccAddress
caller sdk.AccAddress
policy types.AuthorizationPolicy
expAdmin string
expErr bool
}{
"update admin": {
newAdmin: myAddr,
caller: example.CreatorAddr,
policy: DefaultAuthorizationPolicy{},
expAdmin: myAddr.String(),
},
"update admin - unauthorized": {
newAdmin: myAddr,
caller: RandomAccountAddress(t),
policy: DefaultAuthorizationPolicy{},
expErr: true,
},
"clear admin - default policy": {
caller: example.CreatorAddr,
policy: DefaultAuthorizationPolicy{},
expAdmin: "",
},
"clear admin - unauthorized": {
expAdmin: "",
policy: DefaultAuthorizationPolicy{},
caller: RandomAccountAddress(t),
expErr: true,
},
"clear admin - gov policy": {
newAdmin: nil,
policy: GovAuthorizationPolicy{},
caller: example.CreatorAddr,
expAdmin: "",
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
em := sdk.NewEventManager()
ctx = ctx.WithEventManager(em)
gotErr := k.setContractAdmin(ctx, example.Contract, spec.caller, spec.newAdmin, spec.policy)
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.expAdmin, k.GetContractInfo(ctx, example.Contract).Admin)
// and event emitted
require.Len(t, em.Events(), 1)
assert.Equal(t, "update_contract_admin", em.Events()[0].Type)
exp := map[string]string{
"_contract_address": example.Contract.String(),
"new_admin_address": spec.expAdmin,
}
assert.Equal(t, exp, attrsToStringMap(em.Events()[0].Attributes))
})
}
}
func TestGasConsumed(t *testing.T) {
specs := map[string]struct {
originalMeter storetypes.GasMeter
gasRegister types.WasmGasRegister
consumeGas storetypes.Gas
expPanic bool
expMultipliedGasConsumed uint64
}{
"all good": {
originalMeter: storetypes.NewGasMeter(100),
gasRegister: types.NewWasmGasRegister(types.DefaultGasRegisterConfig()),
consumeGas: storetypes.Gas(1),
expMultipliedGasConsumed: 140000,
},
"consumeGas = limit": {
originalMeter: storetypes.NewGasMeter(1),
gasRegister: types.NewWasmGasRegister(types.DefaultGasRegisterConfig()),
consumeGas: storetypes.Gas(1),
expMultipliedGasConsumed: 140000,
},
"consumeGas > limit": {
originalMeter: storetypes.NewGasMeter(10),
gasRegister: types.NewWasmGasRegister(types.DefaultGasRegisterConfig()),
consumeGas: storetypes.Gas(11),
expPanic: true,
},
"nil original meter": {
gasRegister: types.NewWasmGasRegister(types.DefaultGasRegisterConfig()),
consumeGas: storetypes.Gas(1),
expPanic: true,
},
"nil gas register": {
originalMeter: storetypes.NewGasMeter(100),
consumeGas: storetypes.Gas(1),
expPanic: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
m := NewMultipliedGasMeter(spec.originalMeter, spec.gasRegister)
if spec.expPanic {
assert.Panics(t, func() {
m.originalMeter.ConsumeGas(spec.consumeGas, "test-panic")
_ = m.GasConsumed()
})
return
}
m.originalMeter.ConsumeGas(spec.consumeGas, "test")
assert.Equal(t, spec.expMultipliedGasConsumed, m.GasConsumed())
})
}
}
func TestSetContractLabel(t *testing.T) {
parentCtx, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
example := InstantiateReflectExampleContract(t, parentCtx, keepers)
specs := map[string]struct {
newLabel string
caller sdk.AccAddress
policy types.AuthorizationPolicy
contract sdk.AccAddress
expErr bool
}{
"update label - default policy": {
newLabel: "new label",
caller: example.CreatorAddr,
policy: DefaultAuthorizationPolicy{},
contract: example.Contract,
},
"update label - gov policy": {
newLabel: "new label",
policy: GovAuthorizationPolicy{},
caller: RandomAccountAddress(t),
contract: example.Contract,
},
"update label - unauthorized": {
newLabel: "new label",
caller: RandomAccountAddress(t),
policy: DefaultAuthorizationPolicy{},
contract: example.Contract,
expErr: true,
},
"update label - unknown contract": {
newLabel: "new label",
caller: example.CreatorAddr,
policy: DefaultAuthorizationPolicy{},
contract: RandomAccountAddress(t),
expErr: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx, _ := parentCtx.CacheContext()
em := sdk.NewEventManager()
ctx = ctx.WithEventManager(em)
gotErr := k.setContractLabel(ctx, spec.contract, spec.caller, spec.newLabel, spec.policy)
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.newLabel, k.GetContractInfo(ctx, spec.contract).Label)
// and event emitted
require.Len(t, em.Events(), 1)
assert.Equal(t, "update_contract_label", em.Events()[0].Type)
exp := map[string]string{
"_contract_address": spec.contract.String(),
"new_label": spec.newLabel,
}
assert.Equal(t, exp, attrsToStringMap(em.Events()[0].Attributes))
})
}
}
func attrsToStringMap(attrs []abci.EventAttribute) map[string]string {
r := make(map[string]string, len(attrs))
for _, v := range attrs {
r[v.Key] = v.Value
}
return r
}
func must[t any](s t, err error) t {
if err != nil {
panic(err)
}
return s
}
func TestCheckDiscountEligibility(t *testing.T) {
_, keepers := CreateTestInput(t, false, AvailableCapabilities)
k := keepers.WasmKeeper
db := dbm.NewMemDB()
ms := store.NewCommitMultiStore(db, log.NewTestLogger(t), storemetrics.NewNoOpMetrics())
specs := map[string]struct {
isPinned bool
initCtx func() sdk.Context
checksum []byte
expDiscount bool
expLenTxContracts int
expNilContracts bool
}{
"checksum pinned": {
isPinned: true,
checksum: []byte("pinned checksum"),
initCtx: func() sdk.Context {
ctx := sdk.NewContext(ms, cmtproto.Header{
Height: 100,
Time: time.Now(),
}, false, log.NewNopLogger())
return types.WithTxContracts(ctx, types.NewTxContracts())
},
expDiscount: true,
expLenTxContracts: 0,
},
"checksum unpinned - not in ctx": {
isPinned: false,
checksum: []byte("unpinned checksum"),
initCtx: func() sdk.Context {
ctx := sdk.NewContext(ms, cmtproto.Header{
Height: 100,
Time: time.Now(),
}, false, log.NewNopLogger())
return types.WithTxContracts(ctx, types.NewTxContracts())
},
expDiscount: false,
expLenTxContracts: 1,
},
"checksum unpinned - already in ctx": {
isPinned: false,
checksum: []byte("unpinned checksum"),
initCtx: func() sdk.Context {
txContracts := types.NewTxContracts()
txContracts.AddContract([]byte("unpinned checksum"))
ctx := sdk.NewContext(ms, cmtproto.Header{
Height: 100,
Time: time.Now(),
}, false, log.NewNopLogger())
return types.WithTxContracts(ctx, txContracts)
},
expDiscount: true,
expLenTxContracts: 1,
},
"no discount when tx contracts are not initialized": {
isPinned: false,
checksum: []byte("unpinned checksum"),
initCtx: func() sdk.Context {
ctx := sdk.NewContext(ms, cmtproto.Header{
Height: 100,
Time: time.Now(),
}, false, log.NewNopLogger())
return ctx
},
expDiscount: false,
expNilContracts: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
ctx := spec.initCtx()
ctx, discount := k.checkDiscountEligibility(ctx, spec.checksum, spec.isPinned)
assert.Equal(t, spec.expDiscount, discount)
txContracts, ok := types.TxContractsFromContext(ctx)
if spec.expNilContracts {
require.False(t, ok)
assert.Nil(t, txContracts.GetContracts())
return
}
require.True(t, ok)
assert.NotNil(t, txContracts.GetContracts())
assert.Equal(t, spec.expLenTxContracts, len(txContracts.GetContracts()))
})
}
}