mirror of https://github.com/certusone/wasmd.git
1753 lines
58 KiB
Go
1753 lines
58 KiB
Go
package keeper
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"io/ioutil"
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
|
|
|
|
wasmvm "github.com/CosmWasm/wasmvm"
|
|
wasmvmtypes "github.com/CosmWasm/wasmvm/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"
|
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
|
|
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
|
|
"github.com/CosmWasm/wasmd/x/wasm/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 = "iterator,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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(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
|
|
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 := keepers.Faucet.NewFundedAccount(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, SupportedFeatures)
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(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,
|
|
},
|
|
"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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit...)
|
|
otherAddr := keepers.Faucet.NewFundedAccount(ctx, 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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(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, SupportedFeatures)
|
|
|
|
ctx = ctx.WithBlockHeader(tmproto.Header{Height: 1}).
|
|
WithGasMeter(stypes.NewInfiniteGasMeter())
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(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, SupportedFeatures)
|
|
ctx = ctx.WithGasMeter(sdk.NewGasMeter(10_000_000))
|
|
creator = keepers.Faucet.NewFundedAccount(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 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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, 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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, 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, "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", gotContractAddr.String())
|
|
|
|
gasAfter := ctx.GasMeter().GasConsumed()
|
|
if types.EnableGasVerification {
|
|
require.Equal(t, uint64(0x18dab), 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: 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 = 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: false,
|
|
},
|
|
}
|
|
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}, 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 *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)
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(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.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 := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
fred := keepers.Faucet.NewFundedAccount(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 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().(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(0x17cd2), 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, 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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
|
|
// unauthorized - trialCtx so we don't change state
|
|
nonExistingAddress := RandomAccountAddress(t)
|
|
_, 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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
fred := keepers.Faucet.NewFundedAccount(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.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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
fred := keepers.Faucet.NewFundedAccount(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(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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
fred := keepers.Faucet.NewFundedAccount(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(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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
fred := keepers.Faucet.NewFundedAccount(ctx, 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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
fred := keepers.Faucet.NewFundedAccount(ctx, 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": "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, 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 := keepers.Faucet.NewFundedAccount(ctx, 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 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(), 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{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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
fred := keepers.Faucet.NewFundedAccount(ctx, 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)
|
|
keeper := keepers.ContractKeeper
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
|
|
creator := keepers.Faucet.NewFundedAccount(ctx, deposit.Add(deposit...)...)
|
|
fred := keepers.Faucet.NewFundedAccount(ctx, 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) {
|
|
var capturedChecksums []wasmvm.Checksum
|
|
mock := wasmtesting.MockWasmer{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, SupportedFeatures, 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.Response, uint64, error) {
|
|
loops++
|
|
return &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(sdk.NewGasMeter(20000))
|
|
require.PanicsWithValue(t, sdk.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, SupportedFeatures)
|
|
k := keepers.WasmKeeper
|
|
var mock wasmtesting.MockWasmer
|
|
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*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 = spec.replyFn
|
|
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 TestQueryIsolation(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)
|
|
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
|
|
ctx.KVStore(k.storeKey).Set([]byte(`set_in_query`), []byte(`this_is_allowed`))
|
|
return nil, nil
|
|
})
|
|
}).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.Response, uint64, error) {
|
|
_, err := querier.Query(wasmvmtypes.QueryRequest{
|
|
Custom: []byte(`{}`),
|
|
}, 10000*DefaultGasMultiplier)
|
|
require.NoError(t, err)
|
|
return &wasmvmtypes.Response{}, 0, nil
|
|
}
|
|
em := sdk.NewEventManager()
|
|
_, gotErr := k.reply(ctx.WithEventManager(em), example.Contract, wasmvmtypes.Reply{})
|
|
require.NoError(t, gotErr)
|
|
assert.Nil(t, ctx.KVStore(k.storeKey).Get([]byte(`set_in_query`)))
|
|
}
|
|
|
|
func TestBuildContractAddress(t *testing.T) {
|
|
specs := map[string]struct {
|
|
srcCodeID uint64
|
|
srcInstanceID uint64
|
|
expectedAddr string
|
|
}{
|
|
"initial contract": {
|
|
srcCodeID: 1,
|
|
srcInstanceID: 1,
|
|
expectedAddr: "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr",
|
|
},
|
|
"demo value": {
|
|
srcCodeID: 1,
|
|
srcInstanceID: 100,
|
|
expectedAddr: "cosmos1mujpjkwhut9yjw4xueyugc02evfv46y0dtmnz4lh8xxkkdapym9stu5qm8",
|
|
},
|
|
"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: "cosmos1673hrexz4h6s0ft04l96ygq667djzh2nsr335kstjp49x5dk6rpsf5t0le",
|
|
},
|
|
"instanceID > max u32": {
|
|
srcCodeID: 22,
|
|
srcInstanceID: math.MaxUint32 + 1,
|
|
expectedAddr: "cosmos10q3pgfvmeyy0veekgtqhxujxkhz0vm9zmalqgc7evrhj68q3l62qrdfg4m",
|
|
},
|
|
}
|
|
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())
|
|
}
|
|
})
|
|
}
|
|
}
|