wasmd/x/wasm/internal/keeper/keeper_test.go

1185 lines
40 KiB
Go

package keeper
import (
"bytes"
"encoding/json"
"errors"
"github.com/CosmWasm/wasmd/x/wasm/internal/keeper/wasmtesting"
"io/ioutil"
"testing"
"time"
"github.com/CosmWasm/wasmd/x/wasm/internal/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"
)
const SupportedFeatures = "staking,stargate"
func TestNewKeeper(t *testing.T) {
_, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
require.NotNil(t, keepers.WasmKeeper)
}
func TestCreate(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "any/builder:tag", nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// and verify content
storedCode, err := keeper.GetByteCode(ctx, contractID)
require.NoError(t, err)
require.Equal(t, wasmCode, storedCode)
}
func TestCreateStoresInstantiatePermission(t *testing.T) {
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
keeper.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, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "any/builder:tag", nil)
require.NoError(t, err)
codeInfo := keeper.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, nil, nil)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.WasmKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
otherAddr := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
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
keeper.setParams(ctx, params)
_, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "any/builder:tag", 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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
// create one copy
contractID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "any/builder:tag", nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// create second copy
duplicateID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "any/builder:tag", nil)
require.NoError(t, err)
require.Equal(t, uint64(2), duplicateID)
// and verify both content is proper
storedCode, err := keeper.GetByteCode(ctx, contractID)
require.NoError(t, err)
require.Equal(t, wasmCode, storedCode)
storedCode, err = keeper.GetByteCode(ctx, duplicateID)
require.NoError(t, err)
require.Equal(t, wasmCode, storedCode)
}
func TestCreateWithSimulation(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
// create this once in simulation mode
contractID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "any/builder:tag", 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, nil, nil)
accKeeper, keeper = keepers.AccountKeeper, keepers.WasmKeeper
contractID, err = keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "any/builder:tag", nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// and verify content
code, err := keeper.GetByteCode(ctx, contractID)
require.NoError(t, err)
require.Equal(t, code, wasmCode)
}
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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)
contractID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "", nil)
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// and verify content
storedCode, err := keeper.GetByteCode(ctx, contractID)
require.NoError(t, err)
rawCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
require.Equal(t, rawCode, storedCode)
}
func TestInstantiate(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
codeID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "", 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()
// create with no balance is also legal
contractAddr, err := keeper.Instantiate(ctx, codeID, creator, nil, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String())
gasAfter := ctx.GasMeter().GasConsumed()
if types.EnableGasVerification {
require.Equal(t, uint64(0x118c2), gasAfter-gasBefore)
}
// ensure it is stored properly
info := keeper.GetContractInfo(ctx, contractAddr)
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, keeper.GetContractHistory(ctx, contractAddr))
}
func TestInstantiateWithDeposit(t *testing.T) {
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
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, nil, nil)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.WasmKeeper
if spec.fundAddr {
fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, sdk.NewCoins(sdk.NewInt64Coin("denom", 200)))
}
contractID, err := keeper.Create(ctx, spec.srcActor, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "", nil)
require.NoError(t, err)
// when
addr, err := keeper.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) {
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
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, nil, nil)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.WasmKeeper
fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, deposit)
contractID, err := keeper.Create(ctx, myAddr, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "", &spec.srcPermission)
require.NoError(t, err)
_, err = keeper.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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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 := keeper.Instantiate(ctx, nonExistingCodeID, creator, nil, initMsgBz, "demo contract 2", nil)
require.True(t, types.ErrNotFound.Is(err), err)
require.Nil(t, addr)
}
func TestInstantiateWithCallbackToContract(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
var (
executeCalled bool
err error
)
wasmerMock := wasmtesting.SelfCallingInstMockWasmer(&executeCalled)
keepers.WasmKeeper.wasmer = wasmerMock
example := StoreHackatomExampleContract(t, ctx, keepers)
_, err = keepers.WasmKeeper.Instantiate(ctx, example.CodeID, example.CreatorAddr, nil, nil, "test", nil)
require.NoError(t, err)
assert.True(t, executeCalled)
}
func TestExecute(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "", nil)
require.NoError(t, err)
_, _, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, nil, initMsgBz, "demo contract 3", deposit)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", 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 := keeper.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()
res, err = keeper.Execute(ctx, 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(0x11d3f), 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()))
t.Logf("Duration: %v (%d gas)\n", diff, gasAfter-gasBefore)
}
func TestExecuteWithDeposit(t *testing.T) {
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
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
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,
},
"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, nil, nil)
accKeeper, bankKeeper, keeper := keepers.AccountKeeper, keepers.BankKeeper, keepers.WasmKeeper
if spec.fundAddr {
fundAccounts(t, ctx, accKeeper, bankKeeper, spec.srcActor, sdk.NewCoins(sdk.NewInt64Coin("denom", 200)))
}
codeID, err := keeper.Create(ctx, spec.srcActor, wasmCode, "https://example.com/escrow.wasm", "", nil)
require.NoError(t, err)
initMsg := HackatomExampleInitMsg{Verifier: spec.srcActor, Beneficiary: spec.beneficiary}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
contractAddr, err := keeper.Instantiate(ctx, codeID, spec.srcActor, nil, initMsgBz, "my label", nil)
require.NoError(t, err)
// when
_, err = keeper.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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "", nil)
require.NoError(t, err)
_, _, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.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 = keeper.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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "", nil)
require.NoError(t, err)
_, _, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.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 = keeper.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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "", nil)
require.NoError(t, err)
_, _, bob := keyPubAddr()
initMsg := HackatomExampleInitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.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 = keeper.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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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 := keeper.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 := keeper.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, keeper.GetContractHistory(ctx, contractAddr))
// and verify contract state
raw := keeper.QueryRaw(ctx, contractAddr, []byte("config"))
var stored map[string][]byte
require.NoError(t, json.Unmarshal(raw, &stored))
require.Contains(t, stored, "verifier")
require.NoError(t, err)
assert.Equal(t, spec.expVerifier, sdk.AccAddress(stored["verifier"]))
})
}
}
func TestMigrateReplacesTheSecondIndex(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
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)
exists := store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, oldContractInfo))
require.True(t, exists)
// when do migrate
newCodeExample := StoreBurnerExampleContract(t, ctx, keepers)
migMsgBz := BurnerExampleInitMsg{Payout: example.CreatorAddr}.GetBytes(t)
_, err := keepers.WasmKeeper.Migrate(ctx, example.Contract, example.CreatorAddr, newCodeExample.CodeID, migMsgBz)
require.NoError(t, err)
// then the new index exists
newContractInfo := keepers.WasmKeeper.GetContractInfo(ctx, example.Contract)
exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, newContractInfo))
require.True(t, exists)
// and the old index was removed
exists = store.Has(types.GetContractByCreatedSecondaryIndexKey(example.Contract, oldContractInfo))
require.False(t, exists)
}
func TestMigrateWithDispatchedMessage(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)))
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
burnerCode, err := ioutil.ReadFile("./testdata/burner.wasm")
require.NoError(t, err)
originalContractID, err := keeper.Create(ctx, creator, wasmCode, "", "", 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 := keeper.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)
res, err := keeper.Migrate(ctx, contractAddr, fred, burnerContractID, migMsgBz)
require.NoError(t, err)
assert.Equal(t, "burnt 1 keys", string(res.Data))
assert.Equal(t, "", res.Log)
type dict map[string]interface{}
expEvents := []dict{
{
"Type": "wasm",
"Attr": []dict{
{"contract_address": contractAddr},
{"action": "burn"},
{"payout": myPayoutAddr},
},
},
{
"Type": "transfer",
"Attr": []dict{
{"recipient": myPayoutAddr},
{"sender": contractAddr},
{"amount": "100000denom"},
},
},
{
"Type": "message",
"Attr": []dict{
{"sender": contractAddr},
},
},
{
"Type": "message",
"Attr": []dict{
{"module": "bank"},
},
},
}
expJSONEvts := string(mustMarshal(t, expEvents))
assert.JSONEq(t, expJSONEvts, prettyEvents(t, ctx.EventManager().Events()))
// all persistent data cleared
m := keeper.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 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, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
originalContractID, err := keeper.Create(ctx, creator, wasmCode, "", "", 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 := keeper.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 := keeper.GetContractInfo(ctx, addr)
assert.Equal(t, spec.newAdmin.String(), cInfo.Admin)
})
}
}
func TestClearContractAdmin(t *testing.T) {
ctx, keepers := CreateTestInput(t, false, SupportedFeatures, nil, nil)
accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, 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)
wasmCode, err := ioutil.ReadFile("./testdata/hackatom.wasm")
require.NoError(t, err)
originalContractID, err := keeper.Create(ctx, creator, wasmCode, "", "", 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 := keeper.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 := keeper.GetContractInfo(ctx, addr)
assert.Empty(t, cInfo.Admin)
})
}
}