Files
wasmd/x/wasm/internal/keeper/keeper_test.go
2020-05-13 22:55:07 +02:00

469 lines
16 KiB
Go

package keeper
import (
"encoding/binary"
"encoding/json"
"io/ioutil"
"os"
"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"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
)
const SupportedFeatures = "staking"
func TestNewKeeper(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
_, _, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
require.NotNil(t, keeper)
}
func TestCreate(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "cosmwasm-opt:0.5.2")
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 TestCreateDuplicate(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/contract.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", "cosmwasm-opt:0.5.2")
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", "cosmwasm-opt:0.5.2")
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) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
ctx = ctx.WithBlockHeader(abci.Header{Height: 1}).
WithGasMeter(stypes.NewInfiniteGasMeter())
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/contract.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", "confio/cosmwasm-opt:0.57.2")
require.NoError(t, err)
require.Equal(t, uint64(1), contractID)
// then try to create it in non-simulation mode (should not fail)
ctx, accKeeper, keeper = CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
contractID, err = keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "confio/cosmwasm-opt:0.7.2")
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(abci.Header{}).WithGasMeter(stypes.NewInfiniteGasMeter()),
exp: false,
},
"any regular block": {
ctx: sdk.Context{}.WithBlockHeader(abci.Header{Height: 1}).WithGasMeter(stypes.NewGasMeter(10000000)),
exp: false,
},
"simulation": {
ctx: sdk.Context{}.WithBlockHeader(abci.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) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/contract.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", "")
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/contract.wasm")
require.NoError(t, err)
require.Equal(t, rawCode, storedCode)
}
func TestInstantiate(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "https://github.com/CosmWasm/wasmd/blob/master/x/wasm/testdata/escrow.wasm", "")
require.NoError(t, err)
_, _, bob := keyPubAddr()
_, _, fred := keyPubAddr()
initMsg := InitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
gasBefore := ctx.GasMeter().GasConsumed()
// create with no balance is also legal
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 1", nil)
require.NoError(t, err)
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String())
gasAfter := ctx.GasMeter().GasConsumed()
require.Equal(t, uint64(0x61f7), gasAfter-gasBefore)
// ensure it is stored properly
info := keeper.GetContractInfo(ctx, addr)
require.NotNil(t, info)
assert.Equal(t, info.Creator, creator)
assert.Equal(t, info.CodeID, contractID)
assert.Equal(t, info.InitMsg, json.RawMessage(initMsgBz))
assert.Equal(t, info.Label, "demo contract 1")
}
func TestInstantiateWithNonExistingCodeID(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit)
require.NoError(t, err)
initMsg := InitMsg{}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
const nonExistingCodeID = 9999
addr, err := keeper.Instantiate(ctx, nonExistingCodeID, creator, initMsgBz, "demo contract 2", nil)
require.True(t, types.ErrNotFound.Is(err), err)
require.Nil(t, addr)
}
func TestExecute(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(ctx, accKeeper, topUp)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
_, _, bob := keyPubAddr()
initMsg := InitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, 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, creatorAcct.GetCoins())
// ensure contract has updated balance
contractAcct := accKeeper.GetAccount(ctx, addr)
require.NotNil(t, contractAcct)
assert.Equal(t, deposit, contractAcct.GetCoins())
// 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.Contains(t, err.Error(), "unauthorized")
// 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()
require.Equal(t, uint64(0x7b20), gasAfter-gasBefore)
// ensure bob now exists and got both payments released
bobAcct = accKeeper.GetAccount(ctx, bob)
require.NotNil(t, bobAcct)
balance := bobAcct.GetCoins()
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), contractAcct.GetCoins())
t.Logf("Duration: %v (31728 gas)\n", diff)
}
func TestExecuteWithNonExistingAddress(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, 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) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(ctx, accKeeper, topUp)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
_, _, bob := keyPubAddr()
initMsg := InitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, 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)
}
func TestExecuteWithCpuLoop(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(ctx, accKeeper, topUp)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
_, _, bob := keyPubAddr()
initMsg := InitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, 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())
// this must fail
_, err = keeper.Execute(ctx, addr, fred, []byte(`{"cpu_loop":{}}`), nil)
assert.Error(t, err)
// make sure gas ran out
// TODO: wasmer doesn't return gas used on error. we should consume it (for error on metering failure)
// require.Equal(t, gasLimit, ctx.GasMeter().GasConsumed())
}
func TestExecuteWithStorageLoop(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir, SupportedFeatures, nil, nil)
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit.Add(deposit...))
fred := createFakeFundedAccount(ctx, accKeeper, topUp)
wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
contractID, err := keeper.Create(ctx, creator, wasmCode, "", "")
require.NoError(t, err)
_, _, bob := keyPubAddr()
initMsg := InitMsg{
Verifier: fred,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
addr, err := keeper.Instantiate(ctx, contractID, creator, initMsgBz, "demo contract 6", 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)
// TODO: ensure it is out of gas error
_, 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")
}
type InitMsg struct {
Verifier sdk.AccAddress `json:"verifier"`
Beneficiary sdk.AccAddress `json:"beneficiary"`
}
func createFakeFundedAccount(ctx sdk.Context, am auth.AccountKeeper, coins sdk.Coins) sdk.AccAddress {
_, _, addr := keyPubAddr()
baseAcct := auth.NewBaseAccountWithAddress(addr)
_ = baseAcct.SetCoins(coins)
am.SetAccount(ctx, &baseAcct)
return addr
}
var keyCounter uint64 = 0
// we need to make this deterministic (same every test run), as encoded address size and thus gas cost,
// depends on the actual bytes (due to ugly CanonicalAddress encoding)
func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) {
keyCounter++
seed := make([]byte, 8)
binary.BigEndian.PutUint64(seed, keyCounter)
key := ed25519.GenPrivKeyFromSecret(seed)
pub := key.PubKey()
addr := sdk.AccAddress(pub.Address())
return key, pub, addr
}