mirror of https://github.com/certusone/wasmd.git
288 lines
11 KiB
Go
288 lines
11 KiB
Go
package wasm_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
wasmvm "github.com/CosmWasm/wasmvm"
|
|
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
|
ibctransfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
|
|
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
|
|
ibctesting "github.com/cosmos/ibc-go/v7/testing"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/CosmWasm/wasmd/app"
|
|
wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting"
|
|
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
|
|
"github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting"
|
|
"github.com/CosmWasm/wasmd/x/wasm/types"
|
|
)
|
|
|
|
func TestOnChanOpenInitVersion(t *testing.T) {
|
|
const v1 = "v1"
|
|
specs := map[string]struct {
|
|
startVersion string
|
|
contractRsp *wasmvmtypes.IBC3ChannelOpenResponse
|
|
expVersion string
|
|
expErr bool
|
|
}{
|
|
"different version": {
|
|
startVersion: v1,
|
|
contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{Version: "v2"},
|
|
expVersion: "v2",
|
|
},
|
|
"no response": {
|
|
startVersion: v1,
|
|
expVersion: v1,
|
|
},
|
|
"empty result": {
|
|
startVersion: v1,
|
|
contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{},
|
|
expVersion: v1,
|
|
},
|
|
"empty versions should fail": {
|
|
startVersion: "",
|
|
contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{},
|
|
expErr: true,
|
|
},
|
|
}
|
|
for name, spec := range specs {
|
|
t.Run(name, func(t *testing.T) {
|
|
myContract := &wasmtesting.MockIBCContractCallbacks{
|
|
IBCChannelOpenFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) {
|
|
return spec.contractRsp, 0, nil
|
|
},
|
|
}
|
|
var (
|
|
chainAOpts = []wasmkeeper.Option{
|
|
wasmkeeper.WithWasmEngine(
|
|
wasmtesting.NewIBCContractMockWasmEngine(myContract)),
|
|
}
|
|
coordinator = wasmibctesting.NewCoordinator(t, 2, chainAOpts)
|
|
chainA = coordinator.GetChain(wasmibctesting.GetChainID(1))
|
|
chainB = coordinator.GetChain(wasmibctesting.GetChainID(2))
|
|
myContractAddr = chainA.SeedNewContractInstance()
|
|
appA = chainA.App.(*app.WasmApp)
|
|
contractInfo = appA.WasmKeeper.GetContractInfo(chainA.GetContext(), myContractAddr)
|
|
)
|
|
|
|
path := wasmibctesting.NewPath(chainA, chainB)
|
|
coordinator.SetupConnections(path)
|
|
|
|
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: contractInfo.IBCPortID,
|
|
Version: spec.startVersion,
|
|
Order: channeltypes.UNORDERED,
|
|
}
|
|
// when
|
|
gotErr := path.EndpointA.ChanOpenInit()
|
|
// then
|
|
if spec.expErr {
|
|
require.Error(t, gotErr)
|
|
return
|
|
}
|
|
require.NoError(t, gotErr)
|
|
assert.Equal(t, spec.expVersion, path.EndpointA.ChannelConfig.Version)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOnChanOpenTryVersion(t *testing.T) {
|
|
const startVersion = ibctransfertypes.Version
|
|
specs := map[string]struct {
|
|
contractRsp *wasmvmtypes.IBC3ChannelOpenResponse
|
|
expVersion string
|
|
}{
|
|
"different version": {
|
|
contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{Version: "v2"},
|
|
expVersion: "v2",
|
|
},
|
|
"no response": {
|
|
expVersion: startVersion,
|
|
},
|
|
"empty result": {
|
|
contractRsp: &wasmvmtypes.IBC3ChannelOpenResponse{},
|
|
expVersion: startVersion,
|
|
},
|
|
}
|
|
for name, spec := range specs {
|
|
t.Run(name, func(t *testing.T) {
|
|
myContract := &wasmtesting.MockIBCContractCallbacks{
|
|
IBCChannelOpenFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) {
|
|
return spec.contractRsp, 0, nil
|
|
},
|
|
}
|
|
var (
|
|
chainAOpts = []wasmkeeper.Option{
|
|
wasmkeeper.WithWasmEngine(
|
|
wasmtesting.NewIBCContractMockWasmEngine(myContract)),
|
|
}
|
|
coordinator = wasmibctesting.NewCoordinator(t, 2, chainAOpts)
|
|
chainA = coordinator.GetChain(wasmibctesting.GetChainID(1))
|
|
chainB = coordinator.GetChain(wasmibctesting.GetChainID(2))
|
|
myContractAddr = chainA.SeedNewContractInstance()
|
|
contractInfo = chainA.ContractInfo(myContractAddr)
|
|
)
|
|
|
|
path := wasmibctesting.NewPath(chainA, chainB)
|
|
coordinator.SetupConnections(path)
|
|
|
|
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: contractInfo.IBCPortID,
|
|
Version: startVersion,
|
|
Order: channeltypes.UNORDERED,
|
|
}
|
|
path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: ibctransfertypes.PortID,
|
|
Version: ibctransfertypes.Version,
|
|
Order: channeltypes.UNORDERED,
|
|
}
|
|
|
|
require.NoError(t, path.EndpointB.ChanOpenInit())
|
|
require.NoError(t, path.EndpointA.ChanOpenTry())
|
|
assert.Equal(t, spec.expVersion, path.EndpointA.ChannelConfig.Version)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOnIBCPacketReceive(t *testing.T) {
|
|
// given 2 chains with a mock on chain A to control the IBC flow
|
|
// and the ibc-reflect contract on chain B
|
|
// when the test package is relayed
|
|
// then the contract executes the flow defined for the packet data
|
|
// and the ibc Ack captured is what we expect
|
|
specs := map[string]struct {
|
|
packetData []byte
|
|
expAck []byte
|
|
expPacketNotHandled bool
|
|
}{
|
|
"all good": {
|
|
packetData: []byte(`{"who_am_i":{}}`),
|
|
expAck: []byte(`{"ok":{"account":"cosmos1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrs2zhgh2"}}`),
|
|
},
|
|
"with result err": {
|
|
packetData: []byte(`{"return_err": {"text": "my error"}}`),
|
|
expAck: []byte(`{"error":"invalid packet: Generic error: my error"}`),
|
|
},
|
|
"with returned msg fails": {
|
|
packetData: []byte(`{"return_msgs": {"msgs": [{"bank":{"send":{"to_address": "invalid-address", "amount": [{"denom": "ALX", "amount": "1"}]}}}]}}`),
|
|
expAck: []byte(`{"error":"ABCI code: 7: error handling packet: see events for details"}`),
|
|
},
|
|
"with contract panic": {
|
|
packetData: []byte(`{"panic":{}}`),
|
|
expPacketNotHandled: true,
|
|
},
|
|
}
|
|
for name, spec := range specs {
|
|
t.Run(name, func(t *testing.T) {
|
|
mockContractEngine := NewCaptureAckTestContractEngine()
|
|
chainAOpts := []wasmkeeper.Option{
|
|
wasmkeeper.WithWasmEngine(mockContractEngine),
|
|
}
|
|
var (
|
|
coord = wasmibctesting.NewCoordinator(t, 2, chainAOpts)
|
|
chainA = coord.GetChain(wasmibctesting.GetChainID(1))
|
|
chainB = coord.GetChain(wasmibctesting.GetChainID(2))
|
|
)
|
|
// setup chain A contract metadata for mock
|
|
myMockContractAddr := chainA.SeedNewContractInstance() // setups env but uses mock contract
|
|
|
|
// setup chain B contracts
|
|
reflectID := chainB.StoreCodeFile("./keeper/testdata/reflect.wasm").CodeID
|
|
initMsg := wasmkeeper.IBCReflectInitMsg{ReflectCodeID: reflectID}.GetBytes(t)
|
|
codeID := chainB.StoreCodeFile("./keeper/testdata/ibc_reflect.wasm").CodeID
|
|
ibcReflectContractAddr := chainB.InstantiateContract(codeID, initMsg)
|
|
|
|
// establish IBC channels
|
|
var (
|
|
sourcePortID = chainA.ContractInfo(myMockContractAddr).IBCPortID
|
|
counterpartPortID = chainB.ContractInfo(ibcReflectContractAddr).IBCPortID
|
|
path = wasmibctesting.NewPath(chainA, chainB)
|
|
)
|
|
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: sourcePortID, Version: "ibc-reflect-v1", Order: channeltypes.ORDERED,
|
|
}
|
|
path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: counterpartPortID, Version: "ibc-reflect-v1", Order: channeltypes.ORDERED,
|
|
}
|
|
|
|
coord.SetupConnections(path)
|
|
coord.CreateChannels(path)
|
|
coord.CommitBlock(chainA, chainB)
|
|
require.Equal(t, 0, len(chainA.PendingSendPackets))
|
|
require.Equal(t, 0, len(chainB.PendingSendPackets))
|
|
|
|
// when an ibc packet is sent from chain A to chain B
|
|
capturedAck := mockContractEngine.SubmitIBCPacket(t, path, chainA, myMockContractAddr, spec.packetData)
|
|
coord.CommitBlock(chainA, chainB)
|
|
|
|
require.Equal(t, 1, len(chainA.PendingSendPackets))
|
|
require.Equal(t, 0, len(chainB.PendingSendPackets))
|
|
|
|
err := coord.RelayAndAckPendingPackets(path)
|
|
|
|
// then
|
|
if spec.expPacketNotHandled {
|
|
const contractPanicToErrMsg = `recovered: Error calling the VM: Error executing Wasm: Wasmer runtime error: RuntimeError: Aborted: panicked at 'This page intentionally faulted'`
|
|
assert.ErrorContains(t, err, contractPanicToErrMsg)
|
|
require.Nil(t, *capturedAck)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
if spec.expAck != nil {
|
|
assert.Equal(t, spec.expAck, *capturedAck, string(*capturedAck))
|
|
} else {
|
|
require.Nil(t, *capturedAck)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// mock to submit an ibc data package from given chain and capture the ack
|
|
type captureAckTestContractEngine struct {
|
|
*wasmtesting.MockWasmEngine
|
|
}
|
|
|
|
// NewCaptureAckTestContractEngine constructor
|
|
func NewCaptureAckTestContractEngine() *captureAckTestContractEngine {
|
|
m := wasmtesting.NewIBCContractMockWasmEngine(&wasmtesting.MockIBCContractCallbacks{
|
|
IBCChannelOpenFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelOpenMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBC3ChannelOpenResponse, uint64, error) {
|
|
return &wasmvmtypes.IBC3ChannelOpenResponse{}, 0, nil
|
|
},
|
|
IBCChannelConnectFn: func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCChannelConnectMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) {
|
|
return &wasmvmtypes.IBCBasicResponse{}, 0, nil
|
|
},
|
|
})
|
|
return &captureAckTestContractEngine{m}
|
|
}
|
|
|
|
// SubmitIBCPacket starts an IBC packet transfer on given chain and captures the ack returned
|
|
func (x *captureAckTestContractEngine) SubmitIBCPacket(t *testing.T, path *wasmibctesting.Path, chainA *wasmibctesting.TestChain, senderContractAddr sdk.AccAddress, packetData []byte) *[]byte {
|
|
t.Helper()
|
|
// prepare a bridge to send an ibc packet by an ordinary wasm execute message
|
|
x.MockWasmEngine.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) {
|
|
return &wasmvmtypes.Response{
|
|
Messages: []wasmvmtypes.SubMsg{{ID: 1, ReplyOn: wasmvmtypes.ReplyNever, Msg: wasmvmtypes.CosmosMsg{IBC: &wasmvmtypes.IBCMsg{SendPacket: &wasmvmtypes.SendPacketMsg{
|
|
ChannelID: path.EndpointA.ChannelID, Data: executeMsg, Timeout: wasmvmtypes.IBCTimeout{Block: &wasmvmtypes.IBCTimeoutBlock{Revision: 1, Height: 10000000}},
|
|
}}}}},
|
|
}, 0, nil
|
|
}
|
|
// capture acknowledgement
|
|
var gotAck []byte
|
|
x.MockWasmEngine.IBCPacketAckFn = func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketAckMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error) {
|
|
gotAck = msg.Acknowledgement.Data
|
|
return &wasmvmtypes.IBCBasicResponse{}, 0, nil
|
|
}
|
|
|
|
// start the process
|
|
_, err := chainA.SendMsgs(&types.MsgExecuteContract{
|
|
Sender: chainA.SenderAccount.GetAddress().String(),
|
|
Contract: senderContractAddr.String(),
|
|
Msg: packetData,
|
|
})
|
|
require.NoError(t, err)
|
|
return &gotAck
|
|
}
|