Future duplicated variables across the entire module can also be added to it, centralizing therefore all os those kind of values.
507 lines
14 KiB
Go
507 lines
14 KiB
Go
package wasm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
wasmTypes "github.com/CosmWasm/go-cosmwasm/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/cosmos-sdk/types/module"
|
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
|
"github.com/tendermint/tendermint/libs/kv"
|
|
|
|
"github.com/CosmWasm/wasmd/x/wasm/internal/keeper"
|
|
)
|
|
|
|
type testData struct {
|
|
module module.AppModule
|
|
ctx sdk.Context
|
|
acctKeeper auth.AccountKeeper
|
|
keeper Keeper
|
|
}
|
|
|
|
// returns a cleanup function, which must be defered on
|
|
func setupTest(t *testing.T) (testData, func()) {
|
|
tempDir, err := ioutil.TempDir("", "wasm")
|
|
require.NoError(t, err)
|
|
|
|
ctx, keepers := CreateTestInput(t, false, tempDir, "staking", nil, nil)
|
|
acctKeeper, keeper := keepers.AccountKeeper, keepers.WasmKeeper
|
|
data := testData{
|
|
module: NewAppModule(keeper),
|
|
ctx: ctx,
|
|
acctKeeper: acctKeeper,
|
|
keeper: keeper,
|
|
}
|
|
cleanup := func() { os.RemoveAll(tempDir) }
|
|
return data, cleanup
|
|
}
|
|
|
|
func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) {
|
|
key := ed25519.GenPrivKey()
|
|
pub := key.PubKey()
|
|
addr := sdk.AccAddress(pub.Address())
|
|
return key, pub, addr
|
|
}
|
|
|
|
func mustLoad(path string) []byte {
|
|
bz, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return bz
|
|
}
|
|
|
|
var (
|
|
key1, pub1, addr1 = keyPubAddr()
|
|
testContract = mustLoad("./internal/keeper/testdata/contract.wasm")
|
|
maskContract = mustLoad("./internal/keeper/testdata/reflect.wasm")
|
|
oldContract = mustLoad("./testdata/escrow_0.7.wasm")
|
|
)
|
|
|
|
func TestHandleCreate(t *testing.T) {
|
|
cases := map[string]struct {
|
|
msg sdk.Msg
|
|
isValid bool
|
|
}{
|
|
"empty": {
|
|
msg: MsgStoreCode{},
|
|
isValid: false,
|
|
},
|
|
"invalid wasm": {
|
|
msg: MsgStoreCode{
|
|
Sender: addr1,
|
|
WASMByteCode: []byte("foobar"),
|
|
},
|
|
isValid: false,
|
|
},
|
|
"valid wasm": {
|
|
msg: MsgStoreCode{
|
|
Sender: addr1,
|
|
WASMByteCode: testContract,
|
|
},
|
|
isValid: true,
|
|
},
|
|
"other valid wasm": {
|
|
msg: MsgStoreCode{
|
|
Sender: addr1,
|
|
WASMByteCode: maskContract,
|
|
},
|
|
isValid: true,
|
|
},
|
|
"old wasm (0.7)": {
|
|
msg: MsgStoreCode{
|
|
Sender: addr1,
|
|
WASMByteCode: oldContract,
|
|
},
|
|
isValid: false,
|
|
},
|
|
}
|
|
|
|
for name, tc := range cases {
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
data, cleanup := setupTest(t)
|
|
defer cleanup()
|
|
|
|
h := data.module.NewHandler()
|
|
q := data.module.NewQuerierHandler()
|
|
|
|
res, err := h(data.ctx, tc.msg)
|
|
if !tc.isValid {
|
|
require.Error(t, err, "%#v", res)
|
|
assertCodeList(t, q, data.ctx, 0)
|
|
assertCodeBytes(t, q, data.ctx, 1, nil)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
assertCodeList(t, q, data.ctx, 1)
|
|
})
|
|
}
|
|
}
|
|
|
|
type initMsg struct {
|
|
Verifier sdk.AccAddress `json:"verifier"`
|
|
Beneficiary sdk.AccAddress `json:"beneficiary"`
|
|
}
|
|
|
|
type state struct {
|
|
Verifier wasmTypes.CanonicalAddress `json:"verifier"`
|
|
Beneficiary wasmTypes.CanonicalAddress `json:"beneficiary"`
|
|
Funder wasmTypes.CanonicalAddress `json:"funder"`
|
|
}
|
|
|
|
func TestHandleInstantiate(t *testing.T) {
|
|
data, cleanup := setupTest(t)
|
|
defer cleanup()
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
creator := createFakeFundedAccount(data.ctx, data.acctKeeper, deposit)
|
|
|
|
h := data.module.NewHandler()
|
|
q := data.module.NewQuerierHandler()
|
|
|
|
msg := MsgStoreCode{
|
|
Sender: creator,
|
|
WASMByteCode: testContract,
|
|
}
|
|
res, err := h(data.ctx, msg)
|
|
require.NoError(t, err)
|
|
require.Equal(t, res.Data, []byte("1"))
|
|
|
|
_, _, bob := keyPubAddr()
|
|
_, _, fred := keyPubAddr()
|
|
|
|
initMsg := initMsg{
|
|
Verifier: fred,
|
|
Beneficiary: bob,
|
|
}
|
|
initMsgBz, err := json.Marshal(initMsg)
|
|
require.NoError(t, err)
|
|
|
|
// create with no balance is also legal
|
|
initCmd := MsgInstantiateContract{
|
|
Sender: creator,
|
|
CodeID: CID,
|
|
InitMsg: initMsgBz,
|
|
InitFunds: nil,
|
|
}
|
|
res, err = h(data.ctx, initCmd)
|
|
require.NoError(t, err)
|
|
contractAddr := sdk.AccAddress(res.Data)
|
|
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String())
|
|
// this should be standard x/wasm init event, nothing from contract
|
|
require.Equal(t, 2, len(res.Events), prettyEvents(res.Events))
|
|
assert.Equal(t, "wasm", res.Events[0].Type)
|
|
assertAttribute(t, "contract_address", contractAddr.String(), res.Events[0].Attributes[0])
|
|
assert.Equal(t, "message", res.Events[1].Type)
|
|
assertAttribute(t, "module", "wasm", res.Events[1].Attributes[0])
|
|
|
|
assertCodeList(t, q, data.ctx, 1)
|
|
assertCodeBytes(t, q, data.ctx, 1, testContract)
|
|
|
|
assertContractList(t, q, data.ctx, 1, []string{contractAddr.String()})
|
|
assertContractInfo(t, q, data.ctx, contractAddr, 1, creator)
|
|
assertContractState(t, q, data.ctx, contractAddr, state{
|
|
Verifier: []byte(fred),
|
|
Beneficiary: []byte(bob),
|
|
Funder: []byte(creator),
|
|
})
|
|
}
|
|
|
|
func TestHandleExecute(t *testing.T) {
|
|
data, cleanup := setupTest(t)
|
|
defer cleanup()
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
|
|
creator := createFakeFundedAccount(data.ctx, data.acctKeeper, deposit.Add(deposit...))
|
|
fred := createFakeFundedAccount(data.ctx, data.acctKeeper, topUp)
|
|
|
|
h := data.module.NewHandler()
|
|
q := data.module.NewQuerierHandler()
|
|
|
|
msg := MsgStoreCode{
|
|
Sender: creator,
|
|
WASMByteCode: testContract,
|
|
}
|
|
res, err := h(data.ctx, msg)
|
|
require.NoError(t, err)
|
|
require.Equal(t, res.Data, []byte("1"))
|
|
|
|
_, _, bob := keyPubAddr()
|
|
initMsg := initMsg{
|
|
Verifier: fred,
|
|
Beneficiary: bob,
|
|
}
|
|
initMsgBz, err := json.Marshal(initMsg)
|
|
require.NoError(t, err)
|
|
|
|
initCmd := MsgInstantiateContract{
|
|
Sender: creator,
|
|
CodeID: CID,
|
|
InitMsg: initMsgBz,
|
|
InitFunds: deposit,
|
|
}
|
|
res, err = h(data.ctx, initCmd)
|
|
require.NoError(t, err)
|
|
contractAddr := sdk.AccAddress(res.Data)
|
|
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String())
|
|
// this should be standard x/wasm init event, plus a bank send event (2), with no custom contract events
|
|
require.Equal(t, 3, len(res.Events), prettyEvents(res.Events))
|
|
assert.Equal(t, "transfer", res.Events[0].Type)
|
|
assert.Equal(t, "wasm", res.Events[1].Type)
|
|
assertAttribute(t, "contract_address", contractAddr.String(), res.Events[1].Attributes[0])
|
|
assert.Equal(t, "message", res.Events[2].Type)
|
|
assertAttribute(t, "module", "wasm", res.Events[2].Attributes[0])
|
|
|
|
// ensure bob doesn't exist
|
|
bobAcct := data.acctKeeper.GetAccount(data.ctx, bob)
|
|
require.Nil(t, bobAcct)
|
|
|
|
// ensure funder has reduced balance
|
|
creatorAcct := data.acctKeeper.GetAccount(data.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 := data.acctKeeper.GetAccount(data.ctx, contractAddr)
|
|
require.NotNil(t, contractAcct)
|
|
assert.Equal(t, deposit, contractAcct.GetCoins())
|
|
|
|
execCmd := MsgExecuteContract{
|
|
Sender: fred,
|
|
Contract: contractAddr,
|
|
Msg: []byte(`{"release":{}}`),
|
|
SentFunds: topUp,
|
|
}
|
|
res, err = h(data.ctx, execCmd)
|
|
require.NoError(t, err)
|
|
// this should be standard x/wasm init event, plus 2 bank send event, plus a special event from the contract
|
|
require.Equal(t, 4, len(res.Events), prettyEvents(res.Events))
|
|
assert.Equal(t, "transfer", res.Events[0].Type)
|
|
assertAttribute(t, "recipient", contractAddr.String(), res.Events[0].Attributes[0])
|
|
assertAttribute(t, "sender", fred.String(), res.Events[0].Attributes[1])
|
|
assertAttribute(t, "amount", "5000denom", res.Events[0].Attributes[2])
|
|
// custom contract event
|
|
assert.Equal(t, "wasm", res.Events[1].Type)
|
|
assertAttribute(t, "contract_address", contractAddr.String(), res.Events[1].Attributes[0])
|
|
assertAttribute(t, "action", "release", res.Events[1].Attributes[1])
|
|
// second transfer (this without conflicting message)
|
|
assert.Equal(t, "transfer", res.Events[2].Type)
|
|
assertAttribute(t, "recipient", bob.String(), res.Events[2].Attributes[0])
|
|
assertAttribute(t, "sender", contractAddr.String(), res.Events[2].Attributes[1])
|
|
assertAttribute(t, "amount", "105000denom", res.Events[2].Attributes[2])
|
|
// finally, standard x/wasm tag
|
|
assert.Equal(t, "message", res.Events[3].Type)
|
|
assertAttribute(t, "module", "wasm", res.Events[3].Attributes[0])
|
|
|
|
// ensure bob now exists and got both payments released
|
|
bobAcct = data.acctKeeper.GetAccount(data.ctx, bob)
|
|
require.NotNil(t, bobAcct)
|
|
balance := bobAcct.GetCoins()
|
|
assert.Equal(t, deposit.Add(topUp...), balance)
|
|
|
|
// ensure contract has updated balance
|
|
contractAcct = data.acctKeeper.GetAccount(data.ctx, contractAddr)
|
|
require.NotNil(t, contractAcct)
|
|
assert.Equal(t, sdk.Coins(nil), contractAcct.GetCoins())
|
|
|
|
// ensure all contract state is as after init
|
|
assertCodeList(t, q, data.ctx, 1)
|
|
assertCodeBytes(t, q, data.ctx, 1, testContract)
|
|
|
|
assertContractList(t, q, data.ctx, 1, []string{contractAddr.String()})
|
|
assertContractInfo(t, q, data.ctx, contractAddr, 1, creator)
|
|
assertContractState(t, q, data.ctx, contractAddr, state{
|
|
Verifier: []byte(fred),
|
|
Beneficiary: []byte(bob),
|
|
Funder: []byte(creator),
|
|
})
|
|
}
|
|
|
|
func TestHandleExecuteEscrow(t *testing.T) {
|
|
data, cleanup := setupTest(t)
|
|
defer cleanup()
|
|
|
|
deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
|
|
topUp := sdk.NewCoins(sdk.NewInt64Coin("denom", 5000))
|
|
creator := createFakeFundedAccount(data.ctx, data.acctKeeper, deposit.Add(deposit...))
|
|
fred := createFakeFundedAccount(data.ctx, data.acctKeeper, topUp)
|
|
|
|
h := data.module.NewHandler()
|
|
|
|
msg := MsgStoreCode{
|
|
Sender: creator,
|
|
WASMByteCode: testContract,
|
|
}
|
|
res, err := h(data.ctx, msg)
|
|
require.NoError(t, err)
|
|
require.Equal(t, res.Data, []byte("1"))
|
|
|
|
_, _, bob := keyPubAddr()
|
|
initMsg := map[string]interface{}{
|
|
"verifier": fred.String(),
|
|
"beneficiary": bob.String(),
|
|
}
|
|
initMsgBz, err := json.Marshal(initMsg)
|
|
require.NoError(t, err)
|
|
|
|
initCmd := MsgInstantiateContract{
|
|
Sender: creator,
|
|
CodeID: CID,
|
|
InitMsg: initMsgBz,
|
|
InitFunds: deposit,
|
|
}
|
|
res, err = h(data.ctx, initCmd)
|
|
require.NoError(t, err)
|
|
contractAddr := sdk.AccAddress(res.Data)
|
|
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", contractAddr.String())
|
|
|
|
handleMsg := map[string]interface{}{
|
|
"release": map[string]interface{}{},
|
|
}
|
|
handleMsgBz, err := json.Marshal(handleMsg)
|
|
require.NoError(t, err)
|
|
|
|
execCmd := MsgExecuteContract{
|
|
Sender: fred,
|
|
Contract: contractAddr,
|
|
Msg: handleMsgBz,
|
|
SentFunds: topUp,
|
|
}
|
|
res, err = h(data.ctx, execCmd)
|
|
require.NoError(t, err)
|
|
|
|
// ensure bob now exists and got both payments released
|
|
bobAcct := data.acctKeeper.GetAccount(data.ctx, bob)
|
|
require.NotNil(t, bobAcct)
|
|
balance := bobAcct.GetCoins()
|
|
assert.Equal(t, deposit.Add(topUp...), balance)
|
|
|
|
// ensure contract has updated balance
|
|
contractAcct := data.acctKeeper.GetAccount(data.ctx, contractAddr)
|
|
require.NotNil(t, contractAcct)
|
|
assert.Equal(t, sdk.Coins(nil), contractAcct.GetCoins())
|
|
}
|
|
|
|
type prettyEvent struct {
|
|
Type string
|
|
Attr []sdk.Attribute
|
|
}
|
|
|
|
func prettyEvents(evts sdk.Events) string {
|
|
res := make([]prettyEvent, len(evts))
|
|
for i, e := range evts {
|
|
res[i] = prettyEvent{
|
|
Type: e.Type,
|
|
Attr: prettyAttrs(e.Attributes),
|
|
}
|
|
}
|
|
bz, err := json.MarshalIndent(res, "", " ")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return string(bz)
|
|
}
|
|
|
|
func prettyAttrs(attrs []kv.Pair) []sdk.Attribute {
|
|
pretty := make([]sdk.Attribute, len(attrs))
|
|
for i, a := range attrs {
|
|
pretty[i] = prettyAttr(a)
|
|
}
|
|
return pretty
|
|
}
|
|
|
|
func prettyAttr(attr kv.Pair) sdk.Attribute {
|
|
return sdk.NewAttribute(string(attr.Key), string(attr.Value))
|
|
}
|
|
|
|
func assertAttribute(t *testing.T, key string, value string, attr kv.Pair) {
|
|
assert.Equal(t, key, string(attr.Key), prettyAttr(attr))
|
|
assert.Equal(t, value, string(attr.Value), prettyAttr(attr))
|
|
}
|
|
|
|
func assertCodeList(t *testing.T, q sdk.Querier, ctx sdk.Context, expectedNum int) {
|
|
bz, sdkerr := q(ctx, []string{QueryListCode}, abci.RequestQuery{})
|
|
require.NoError(t, sdkerr)
|
|
|
|
if len(bz) == 0 {
|
|
require.Equal(t, expectedNum, 0)
|
|
return
|
|
}
|
|
|
|
var res []CodeInfo
|
|
err := json.Unmarshal(bz, &res)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expectedNum, len(res))
|
|
}
|
|
|
|
func assertCodeBytes(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64, expectedBytes []byte) {
|
|
path := []string{QueryGetCode, fmt.Sprintf("%d", codeID)}
|
|
bz, sdkerr := q(ctx, path, abci.RequestQuery{})
|
|
require.NoError(t, sdkerr)
|
|
|
|
if len(bz) == 0 {
|
|
require.Equal(t, len(expectedBytes), 0)
|
|
return
|
|
}
|
|
|
|
var res GetCodeResponse
|
|
err := json.Unmarshal(bz, &res)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, expectedBytes, res.Data)
|
|
assert.Equal(t, codeID, res.ID)
|
|
}
|
|
|
|
func assertContractList(t *testing.T, q sdk.Querier, ctx sdk.Context, codeID uint64, addrs []string) {
|
|
bz, sdkerr := q(ctx, []string{QueryListContractByCode, fmt.Sprintf("%d", codeID)}, abci.RequestQuery{})
|
|
require.NoError(t, sdkerr)
|
|
|
|
if len(bz) == 0 {
|
|
require.Equal(t, len(addrs), 0)
|
|
return
|
|
}
|
|
|
|
var res []ContractInfoWithAddress
|
|
err := json.Unmarshal(bz, &res)
|
|
require.NoError(t, err)
|
|
|
|
var hasAddrs = make([]string, len(res))
|
|
for i, r := range res {
|
|
hasAddrs[i] = r.Address.String()
|
|
}
|
|
|
|
assert.Equal(t, hasAddrs, addrs)
|
|
}
|
|
|
|
func assertContractState(t *testing.T, q sdk.Querier, ctx sdk.Context, addr sdk.AccAddress, expected state) {
|
|
path := []string{QueryGetContractState, addr.String(), keeper.QueryMethodContractStateAll}
|
|
bz, sdkerr := q(ctx, path, abci.RequestQuery{})
|
|
require.NoError(t, sdkerr)
|
|
|
|
var res []Model
|
|
err := json.Unmarshal(bz, &res)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(res), "#v", res)
|
|
require.Equal(t, []byte("config"), []byte(res[0].Key))
|
|
|
|
expectedBz, err := json.Marshal(expected)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expectedBz, res[0].Value)
|
|
}
|
|
|
|
func assertContractInfo(t *testing.T, q sdk.Querier, ctx sdk.Context, addr sdk.AccAddress, codeID uint64, creator sdk.AccAddress) {
|
|
path := []string{QueryGetContract, addr.String()}
|
|
bz, sdkerr := q(ctx, path, abci.RequestQuery{})
|
|
require.NoError(t, sdkerr)
|
|
|
|
var res ContractInfo
|
|
err := json.Unmarshal(bz, &res)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, codeID, res.CodeID)
|
|
assert.Equal(t, creator, res.Creator)
|
|
}
|
|
|
|
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
|
|
}
|