mirror of https://github.com/certusone/wasmd.git
2988 lines
106 KiB
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()))
|
|
})
|
|
}
|
|
}
|