mirror of https://github.com/certusone/wasmd.git
222 lines
8.1 KiB
Go
222 lines
8.1 KiB
Go
package e2e_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
wasmvmtypes "github.com/CosmWasm/wasmvm/v3/types"
|
|
ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"
|
|
channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"
|
|
ibctesting "github.com/cosmos/ibc-go/v10/testing"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/CosmWasm/wasmd/tests/e2e"
|
|
wasmibctesting "github.com/CosmWasm/wasmd/tests/wasmibctesting"
|
|
"github.com/CosmWasm/wasmd/x/wasm/types"
|
|
)
|
|
|
|
func TestIBCCallbacks(t *testing.T) {
|
|
// scenario:
|
|
// given two chains
|
|
// with an ics-20 channel established
|
|
// and an ibc-callbacks contract deployed on chain A and B each
|
|
// when the contract on A sends an IBCMsg::Transfer to the contract on B
|
|
// then the contract on B should receive a destination chain callback
|
|
// and the contract on A should receive a source chain callback with the result (ack or timeout)
|
|
coord := wasmibctesting.NewCoordinator(t, 2)
|
|
chainA := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(1)))
|
|
chainB := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(2)))
|
|
|
|
actorChainA := sdk.AccAddress(chainA.SenderPrivKey.PubKey().Address())
|
|
oneToken := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(1)))
|
|
|
|
path := wasmibctesting.NewWasmPath(chainA, chainB)
|
|
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: ibctransfertypes.PortID,
|
|
Version: ibctransfertypes.V1,
|
|
Order: channeltypes.UNORDERED,
|
|
}
|
|
path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: ibctransfertypes.PortID,
|
|
Version: ibctransfertypes.V1,
|
|
Order: channeltypes.UNORDERED,
|
|
}
|
|
// with an ics-20 transfer channel setup between both chains
|
|
coord.Setup(&path.Path)
|
|
|
|
// with an ibc-callbacks contract deployed on chain A
|
|
codeIDonA := chainA.StoreCodeFile("./testdata/ibc_callbacks.wasm").CodeID
|
|
|
|
// and on chain B
|
|
codeIDonB := chainB.StoreCodeFile("./testdata/ibc_callbacks.wasm").CodeID
|
|
|
|
type TransferExecMsg struct {
|
|
ToAddress string `json:"to_address"`
|
|
ChannelID string `json:"channel_id"`
|
|
TimeoutSeconds uint32 `json:"timeout_seconds"`
|
|
}
|
|
// ExecuteMsg is the ibc-callbacks contract's execute msg
|
|
type ExecuteMsg struct {
|
|
Transfer *TransferExecMsg `json:"transfer"`
|
|
}
|
|
type QueryMsg struct {
|
|
CallbackStats struct{} `json:"callback_stats"`
|
|
}
|
|
type QueryResp struct {
|
|
IBCAckCallbacks []wasmvmtypes.IBCPacketAckMsg `json:"ibc_ack_callbacks"`
|
|
IBCTimeoutCallbacks []wasmvmtypes.IBCPacketTimeoutMsg `json:"ibc_timeout_callbacks"`
|
|
IBCDestinationCallbacks []wasmvmtypes.IBCDestinationCallbackMsg `json:"ibc_destination_callbacks"`
|
|
}
|
|
|
|
specs := map[string]struct {
|
|
contractMsg ExecuteMsg
|
|
// expAck is true if the packet is relayed, false if it times out
|
|
expAck bool
|
|
}{
|
|
"success": {
|
|
contractMsg: ExecuteMsg{
|
|
Transfer: &TransferExecMsg{
|
|
ChannelID: path.EndpointA.ChannelID,
|
|
TimeoutSeconds: 100,
|
|
},
|
|
},
|
|
expAck: true,
|
|
},
|
|
"timeout": {
|
|
contractMsg: ExecuteMsg{
|
|
Transfer: &TransferExecMsg{
|
|
ChannelID: path.EndpointA.ChannelID,
|
|
TimeoutSeconds: 1,
|
|
},
|
|
},
|
|
expAck: false,
|
|
},
|
|
}
|
|
|
|
for name, spec := range specs {
|
|
t.Run(name, func(t *testing.T) {
|
|
contractAddrA := chainA.InstantiateContract(codeIDonA, []byte(`{}`))
|
|
require.NotEmpty(t, contractAddrA)
|
|
contractAddrB := chainB.InstantiateContract(codeIDonB, []byte(`{}`))
|
|
require.NotEmpty(t, contractAddrB)
|
|
|
|
if spec.contractMsg.Transfer != nil && spec.contractMsg.Transfer.ToAddress == "" {
|
|
spec.contractMsg.Transfer.ToAddress = contractAddrB.String()
|
|
}
|
|
contractMsgBz, err := json.Marshal(spec.contractMsg)
|
|
require.NoError(t, err)
|
|
|
|
// when the contract on chain A sends an IBCMsg::Transfer to the contract on chain B
|
|
execMsg := types.MsgExecuteContract{
|
|
Sender: actorChainA.String(),
|
|
Contract: contractAddrA.String(),
|
|
Msg: contractMsgBz,
|
|
Funds: oneToken,
|
|
}
|
|
_, err = chainA.SendMsgs(&execMsg)
|
|
require.NoError(t, err)
|
|
|
|
if spec.expAck {
|
|
// and the packet is relayed
|
|
wasmibctesting.RelayAndAckPendingPackets(path)
|
|
|
|
// then the contract on chain B should receive a receive callback
|
|
var response QueryResp
|
|
chainB.SmartQuery(contractAddrB.String(), QueryMsg{CallbackStats: struct{}{}}, &response)
|
|
assert.Empty(t, response.IBCAckCallbacks)
|
|
assert.Empty(t, response.IBCTimeoutCallbacks)
|
|
assert.Len(t, response.IBCDestinationCallbacks, 1)
|
|
|
|
// and the receive callback should contain the ack
|
|
assert.Equal(t, []byte("{\"result\":\"AQ==\"}"), response.IBCDestinationCallbacks[0].Ack.Data)
|
|
|
|
// and the contract on chain A should receive a callback with the ack
|
|
chainA.SmartQuery(contractAddrA.String(), QueryMsg{CallbackStats: struct{}{}}, &response)
|
|
assert.Len(t, response.IBCAckCallbacks, 1)
|
|
assert.Empty(t, response.IBCTimeoutCallbacks)
|
|
assert.Empty(t, response.IBCDestinationCallbacks)
|
|
|
|
// and the ack result should be the ics20 success ack
|
|
assert.Equal(t, []byte(`{"result":"AQ=="}`), response.IBCAckCallbacks[0].Acknowledgement.Data)
|
|
} else {
|
|
// and the packet times out
|
|
require.NoError(t, wasmibctesting.TimeoutPendingPackets(coord, path))
|
|
|
|
// then the contract on chain B should not receive anything
|
|
var response QueryResp
|
|
chainB.SmartQuery(contractAddrB.String(), QueryMsg{CallbackStats: struct{}{}}, &response)
|
|
assert.Empty(t, response.IBCAckCallbacks)
|
|
assert.Empty(t, response.IBCTimeoutCallbacks)
|
|
assert.Empty(t, response.IBCDestinationCallbacks)
|
|
|
|
// and the contract on chain A should receive a callback with the timeout result
|
|
chainA.SmartQuery(contractAddrA.String(), QueryMsg{CallbackStats: struct{}{}}, &response)
|
|
assert.Empty(t, response.IBCAckCallbacks)
|
|
assert.Len(t, response.IBCTimeoutCallbacks, 1)
|
|
assert.Empty(t, response.IBCDestinationCallbacks)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIBCCallbacksWithoutEntrypoints(t *testing.T) {
|
|
// scenario:
|
|
// given two chains
|
|
// with an ics-20 channel established
|
|
// and a reflect contract deployed on chain A and B each
|
|
// when the contract on A sends an IBCMsg::Transfer to the contract on B
|
|
// then the VM should try to call the callback on B and fail gracefully
|
|
// and should try to call the callback on A and fail gracefully
|
|
coord := wasmibctesting.NewCoordinator(t, 2)
|
|
chainA := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(1)))
|
|
chainB := wasmibctesting.NewWasmTestChain(coord.GetChain(ibctesting.GetChainID(2)))
|
|
|
|
oneToken := sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(1))
|
|
|
|
path := wasmibctesting.NewWasmPath(chainA, chainB)
|
|
path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: ibctransfertypes.PortID,
|
|
Version: ibctransfertypes.V1,
|
|
Order: channeltypes.UNORDERED,
|
|
}
|
|
path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{
|
|
PortID: ibctransfertypes.PortID,
|
|
Version: ibctransfertypes.V1,
|
|
Order: channeltypes.UNORDERED,
|
|
}
|
|
// with an ics-20 transfer channel setup between both chains
|
|
coord.Setup(&path.Path)
|
|
|
|
// with a reflect contract deployed on chain A and B
|
|
contractAddrA := e2e.InstantiateReflectContract(t, chainA)
|
|
chainA.Fund(contractAddrA, oneToken.Amount)
|
|
contractAddrB := e2e.InstantiateReflectContract(t, chainA)
|
|
|
|
// when the contract on A sends an IBCMsg::Transfer to the contract on B
|
|
memo := fmt.Sprintf(`{"src_callback":{"address":"%v"},"dest_callback":{"address":"%v"}}`, contractAddrA.String(), contractAddrB.String())
|
|
e2e.MustExecViaReflectContract(t, chainA, contractAddrA, wasmvmtypes.CosmosMsg{
|
|
IBC: &wasmvmtypes.IBCMsg{
|
|
Transfer: &wasmvmtypes.TransferMsg{
|
|
ToAddress: contractAddrB.String(),
|
|
ChannelID: path.EndpointA.ChannelID,
|
|
Amount: wasmvmtypes.NewCoin(oneToken.Amount.Uint64(), oneToken.Denom),
|
|
Timeout: wasmvmtypes.IBCTimeout{
|
|
Timestamp: uint64(chainA.LatestCommittedHeader.GetTime().Add(time.Second * 100).UnixNano()),
|
|
},
|
|
Memo: memo,
|
|
},
|
|
},
|
|
})
|
|
|
|
// and the packet is relayed without problems
|
|
wasmibctesting.RelayAndAckPendingPackets(path)
|
|
assert.Empty(t, *chainA.PendingSendPackets)
|
|
}
|