Files
wasmd/x/wasm/keeper/keeper_test.go
Piotr Husiatyński b2fc913398 Keeper tests (#590)
* More keeper tests

x/wasm/keeper tests are extended to test various input validation.
Keeper input is validated before passing to the keeper method when used
within wasmd application. We cannot ensure such validation when this
keeper is used outside of wasmd application. To keep it safe, fully
validate keeper methods input.

hackatom.wasm is loaded into memory during initialization to avoid
reading file in each test separately. Once migrated to go 1.16, embed
package should be used instead.

Run goimport on certain files.

Some comments fixed or removed.

* ensure that creator address is not nil
2021-08-27 15:37:07 +02:00

1675 lines
56 KiB
Go

package keeper
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"math"
"testing"
"time"
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
wasmvm "github.com/CosmWasm/wasmvm"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/CosmWasm/wasmd/x/wasm/types"
stypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
// When migrated to go 1.16, embed package should be used instead.
func init() {
b, err := ioutil.ReadFile("./testdata/hackatom.wasm")
if err != nil {
panic(err)
}
hackatomWasm = b
}
var hackatomWasm []byte
const SupportedFeatures = "staking,stargate"
func TestNewKeeper(t *testing.T) {
_, keepers := CreateTestInput(t, false, SupportedFeatures)
require.NotNil(t, keepers.ContractKeeper)
}
func TestCreateSuccess(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, 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
exp := sdk.Events{sdk.NewEvent("store_code", sdk.NewAttribute("code_id", "1"))}
assert.Equal(t, exp, em.Events())
}
func TestCreateNilCreatorAddress(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
_, err := keepers.ContractKeeper.Create(ctx, nil, hackatomWasm, nil)
require.Error(t, err, "nil creator is not allowed")
}
func TestCreateNilWasmCode(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, 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, SupportedFeatures)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, keepers.AccountKeeper, keepers.BankKeeper, 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}, sdk.AddrLen)
)
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,
},
"onlyAddress with matching address": {
srcPermission: types.AccessTypeOnlyAddress,
expInstConf: types.AccessConfig{Permission: types.AccessTypeOnlyAddress, Address: myAddr.String()},
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
keepers.WasmKeeper.setParams(ctx, types.Params{
CodeUploadAccess: types.AllowEverybody,
InstantiateDefaultPermission: spec.srcPermission,
MaxWasmCodeSize: types.DefaultMaxWasmCodeSize,
})
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, SupportedFeatures)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.ContractKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
otherAddr := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
specs := map[string]struct {
srcPermission types.AccessConfig
expError *sdkerrors.Error
}{
"default": {
srcPermission: types.DefaultUploadAccess,
},
"everybody": {
srcPermission: types.AllowEverybody,
},
"nobody": {
srcPermission: types.AllowNobody,
expError: sdkerrors.ErrUnauthorized,
},
"onlyAddress with matching address": {
srcPermission: types.AccessTypeOnlyAddress.With(creator),
},
"onlyAddress with non matching address": {
srcPermission: types.AccessTypeOnlyAddress.With(otherAddr),
expError: sdkerrors.ErrUnauthorized,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
params := types.DefaultParams()
params.CodeUploadAccess = spec.srcPermission
keepers.WasmKeeper.setParams(ctx, params)
_, err := keeper.Create(ctx, creator, hackatomWasm, nil)
require.True(t, spec.expError.Is(err), err)
if spec.expError != nil {
return
}
})
}
}
func TestCreateDuplicate(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, 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, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
ctx = ctx.WithBlockHeader(tmproto.Header{Height: 1}).
WithGasMeter(stypes.NewInfiniteGasMeter())
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
// create this once in simulation mode
contractID, err := keeper.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, SupportedFeatures)
accKeeper, keeper = keepers.AccountKeeper, keepers.ContractKeeper
contractID, err = keeper.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 TestIsSimulationMode(t *testing.T) {
specs := map[string]struct {
ctx sdk.Context
exp bool
}{
"genesis block": {
ctx: sdk.Context{}.WithBlockHeader(tmproto.Header{}).WithGasMeter(stypes.NewInfiniteGasMeter()),
exp: false,
},
"any regular block": {
ctx: sdk.Context{}.WithBlockHeader(tmproto.Header{Height: 1}).WithGasMeter(stypes.NewGasMeter(10000000)),
exp: false,
},
"simulation": {
ctx: sdk.Context{}.WithBlockHeader(tmproto.Header{Height: 1}).WithGasMeter(stypes.NewInfiniteGasMeter()),
exp: true,
},
}
for msg := range specs {
t.Run(msg, func(t *testing.T) {
//assert.Equal(t, spec.exp, isSimulationMode(spec.ctx))
})
}
}
func TestCreateWithGzippedPayload(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
wasmCode, err := ioutil.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 TestInstantiate(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
codeID, 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)
gasBefore := ctx.GasMeter().GasConsumed()
em := sdk.NewEventManager()
// create with no balance is also legal
gotContractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx.WithEventManager(em), codeID, creator, nil, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhuc53mp6", gotContractAddr.String())
gasAfter := ctx.GasMeter().GasConsumed()
if types.EnableGasVerification {
require.Equal(t, uint64(0x12206), 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, codeID, info.CodeID)
assert.Equal(t, "demo contract 1", info.Label)
exp := []types.ContractCodeHistoryEntry{{
Operation: types.ContractCodeHistoryOperationTypeInit,
CodeID: codeID,
Updated: types.NewAbsoluteTxPosition(ctx),
Msg: json.RawMessage(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}, sdk.AddrLen)
fred = bytes.Repeat([]byte{2}, sdk.AddrLen)
deposit = sdk.NewCoins(sdk.NewInt64Coin("denom", 100))
initMsg = HackatomExampleInitMsg{Verifier: fred, Beneficiary: bob}
)
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
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: true,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
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, initMsgBz, "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}, sdk.AddrLen)
otherAddr = bytes.Repeat([]byte{2}, sdk.AddrLen)
anyAddr = bytes.Repeat([]byte{3}, sdk.AddrLen)
)
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 *sdkerrors.Error
}{
"default": {
srcPermission: types.DefaultUploadAccess,
srcActor: anyAddr,
},
"everybody": {
srcPermission: types.AllowEverybody,
srcActor: anyAddr,
},
"nobody": {
srcPermission: types.AllowNobody,
srcActor: myAddr,
expError: sdkerrors.ErrUnauthorized,
},
"onlyAddress with matching address": {
srcPermission: types.AccessTypeOnlyAddress.With(myAddr),
srcActor: myAddr,
},
"onlyAddress with non matching address": {
srcPermission: types.AccessTypeOnlyAddress.With(otherAddr),
expError: sdkerrors.ErrUnauthorized,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
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)
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 TestInstantiateWithNonExistingCodeID(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, bankKeeper := keepers.AccountKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, 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.True(t, types.ErrNotFound.Is(err), err)
require.Nil(t, addr)
}
func TestInstantiateWithContractDataResponse(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
wasmerMock := &wasmtesting.MockWasmer{
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.Response, uint64, error) {
return &wasmvmtypes.Response{Data: []byte("my-response-data")}, 0, nil
},
AnalyzeCodeFn: wasmtesting.WithoutIBCAnalyzeFn,
CreateFn: wasmtesting.NoOpCreateFn,
}
example := StoreRandomContract(t, ctx, keepers, wasmerMock)
_, 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 TestExecute(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, 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 3", deposit)
require.NoError(t, err)
require.Equal(t, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhuc53mp6", 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().(sdk.MultiStore))
res, 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.Now().Sub(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(0x12af1), 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(nil), bankKeeper.GetAllBalances(ctx, contractAcct.GetAddress()))
// and events emitted
require.Len(t, em.Events(), 5)
expEvt := sdk.NewEvent("execute",
sdk.NewAttribute("_contract_address", addr.String()))
assert.Equal(t, expEvt, em.Events()[1])
t.Logf("Duration: %v (%d gas)\n", diff, gasAfter-gasBefore)
}
func TestExecuteWithDeposit(t *testing.T) {
var (
bob = bytes.Repeat([]byte{1}, sdk.AddrLen)
fred = bytes.Repeat([]byte{2}, sdk.AddrLen)
blockedAddr = authtypes.NewModuleAddress(authtypes.FeeCollectorName)
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: true,
},
"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, SupportedFeatures)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.ContractKeeper
if spec.newBankParams != nil {
bankKeeper.SetParams(ctx, *spec.newBankParams)
}
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, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
// unauthorized - trialCtx so we don't change state
nonExistingAddress := addrFromUint64(9999)
_, err := keeper.Execute(ctx, nonExistingAddress, creator, []byte(`{}`), nil)
require.True(t, types.ErrNotFound.Is(err), err)
}
func TestExecuteWithPanic(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, 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.ErrExecuteFailed))
// 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: unreachable")
}
func TestExecuteWithCpuLoop(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, 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(sdk.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.(sdk.ErrorOutOfGas)
require.True(t, ok, "%v", r)
}()
// this should throw out of gas exception (panic)
_, err = 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, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, 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(sdk.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.(sdk.ErrorOutOfGas)
require.True(t, ok, "%v", r)
}()
// this should throw out of gas exception (panic)
_, err = 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) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, topUp)
originalCodeID := StoreHackatomExampleContract(t, ctx, keepers).CodeID
newCodeID := StoreHackatomExampleContract(t, ctx, keepers).CodeID
ibcCodeID := StoreIBCReflectContract(t, ctx, keepers).CodeID
require.NotEqual(t, originalCodeID, newCodeID)
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 *sdkerrors.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,
},
"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.ErrMigrationFailed,
},
"fail when no IBC callbacks": {
admin: fred,
caller: fred,
initMsg: IBCReflectInitMsg{ReflectCodeID: StoreReflectContract(t, ctx, keepers)}.GetBytes(t),
fromCodeID: ibcCodeID,
toCodeID: newCodeID,
migrateMsg: migMsgBz,
expErr: types.ErrMigrationFailed,
},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
// given a contract instance
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1)
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, SupportedFeatures)
example := InstantiateHackatomExampleContract(t, ctx, keepers)
// then assert a second index exists
store := ctx.KVStore(keepers.WasmKeeper.storeKey)
oldContractInfo := keepers.WasmKeeper.GetContractInfo(ctx, example.Contract)
require.NotNil(t, oldContractInfo)
createHistoryEntry := types.ContractCodeHistoryEntry{
CodeID: example.CodeID,
Updated: types.NewAbsoluteTxPosition(ctx),
}
exists := store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, createHistoryEntry))
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 = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, migrateHistoryEntry))
require.True(t, exists)
// and the old index was removed
exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, createHistoryEntry))
require.False(t, exists)
}
func TestMigrateWithDispatchedMessage(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, sdk.NewCoins(sdk.NewInt64Coin("denom", 5000)))
burnerCode, err := ioutil.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}.GetBytes(t)
ctx = ctx.WithEventManager(sdk.NewEventManager()).WithBlockHeight(ctx.BlockHeight() + 1)
data, err := keeper.Migrate(ctx, contractAddr, fred, burnerContractID, migMsgBz)
require.NoError(t, err)
assert.Equal(t, "burnt 1 keys", string(data))
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": "burn"},
{"payout": myPayoutAddr},
},
},
{
"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 := bankKeeper.GetAllBalances(ctx, myPayoutAddr)
assert.Equal(t, deposit, balance)
}
func TestIterateContractsByCode(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
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.MockWasmer{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.Response, uint64, error) {
return &wasmvmtypes.Response{}, 1, nil
}}
wasmtesting.MakeInstantiable(&mockWasmVM)
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, 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 priviledged 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.Coins `json:"amount"`
}
func TestSudo(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, 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, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhuc53mp6", 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 priviledged Go module can make a call that should never be exposed
// to end users (via Tx/Execute).
StealFunds: stealFundsMsg{
Recipient: community.String(),
Amount: wasmvmtypes.Coins{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(), 2)
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{string(a.Key): string(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) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, topUp)
originalContractID, err := keeper.Create(ctx, 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 *sdkerrors.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) {
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) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, topUp)
originalContractID, err := keeper.Create(ctx, 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 *sdkerrors.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) {
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, SupportedFeatures)
k := keepers.WasmKeeper
var capturedChecksums []wasmvm.Checksum
mock := wasmtesting.MockWasmer{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, SupportedFeatures)
k := keepers.WasmKeeper
var capturedChecksums []wasmvm.Checksum
mock := wasmtesting.MockWasmer{
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, SupportedFeatures)
k := keepers.WasmKeeper
var capturedChecksums []wasmvm.Checksum
mock := wasmtesting.MockWasmer{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) {
// a pinned contract that calls itself via submessages should terminate with an
// error at some point
ctx, keepers := CreateTestInput(t, false, SupportedFeatures)
k := keepers.WasmKeeper
var capturedChecksums []wasmvm.Checksum
mock := wasmtesting.MockWasmer{PinFn: func(checksum wasmvm.Checksum) error {
capturedChecksums = append(capturedChecksums, checksum)
return nil
}}
wasmtesting.MakeInstantiable(&mock)
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.Response, uint64, error) {
loops++
return &wasmvmtypes.Response{
Messages: []wasmvmtypes.SubMsg{
{
ID: 1,
ReplyOn: wasmvmtypes.ReplyError,
Msg: wasmvmtypes.CosmosMsg{
Wasm: &wasmvmtypes.WasmMsg{
Execute: &wasmvmtypes.ExecuteMsg{
ContractAddr: example.Contract.String(),
Msg: anyMsg,
},
},
},
},
},
}, 0, nil
}
ctx = ctx.WithGasMeter(sdk.NewGasMeter(20000))
require.PanicsWithValue(t, sdk.ErrorOutOfGas{Descriptor: "ReadFlat"}, func() {
k.execute(ctx, example.Contract, RandomAccountAddress(t), anyMsg, nil)
})
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, SupportedFeatures)
k := keepers.WasmKeeper
var mock wasmtesting.MockWasmer
wasmtesting.MakeInstantiable(&mock)
example := SeedNewContractInstance(t, ctx, keepers, &mock)
specs := map[string]struct {
rsp wasmvmtypes.Response
expData []byte
expErr bool
expEvt sdk.Events
}{
"all good": {
rsp: wasmvmtypes.Response{Data: []byte("foo")},
expData: []byte("foo"),
expEvt: sdk.Events{sdk.NewEvent("reply", sdk.NewAttribute("_contract_address", example.Contract.String()))},
},
"error": {
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.Response, uint64, error) {
if spec.expErr {
return nil, 1, errors.New("testing")
}
return &spec.rsp, 1, nil
}
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())
})
}
}
func TestBuildContractAddress(t *testing.T) {
specs := map[string]struct {
srcCodeID uint64
srcInstanceID uint64
expectedAddr string
}{
"initial contract": {
srcCodeID: 1,
srcInstanceID: 1,
expectedAddr: "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhuc53mp6",
},
"demo value": {
srcCodeID: 1,
srcInstanceID: 100,
expectedAddr: "cosmos1mujpjkwhut9yjw4xueyugc02evfv46y04aervg",
},
"both below max": {
srcCodeID: math.MaxUint32 - 1,
srcInstanceID: math.MaxUint32 - 1,
},
"both at max": {
srcCodeID: math.MaxUint32,
srcInstanceID: math.MaxUint32,
},
"codeID > max u32": {
srcCodeID: math.MaxUint32 + 1,
srcInstanceID: 17,
expectedAddr: "cosmos1673hrexz4h6s0ft04l96ygq667djzh2nvy7fsu",
},
"instanceID > max u32": {
srcCodeID: 22,
srcInstanceID: math.MaxUint32 + 1,
expectedAddr: "cosmos10q3pgfvmeyy0veekgtqhxujxkhz0vm9z65ckqh",
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
gotAddr := BuildContractAddress(spec.srcCodeID, spec.srcInstanceID)
require.NotNil(t, gotAddr)
assert.Nil(t, sdk.VerifyAddressFormat(gotAddr))
if len(spec.expectedAddr) > 0 {
require.Equal(t, spec.expectedAddr, gotAddr.String())
}
})
}
}