diff --git a/x/wasm/alias.go b/x/wasm/alias.go index 5f2e52f5fc..17a769dea6 100644 --- a/x/wasm/alias.go +++ b/x/wasm/alias.go @@ -110,7 +110,7 @@ type ( MsgMigrateContract = types.MsgMigrateContract MsgUpdateAdmin = types.MsgUpdateAdmin MsgClearAdmin = types.MsgClearAdmin - MsgWasmIBCCall = types.MsgWasmIBCCall + MsgWasmIBCCall = types.MsgIBCSend Model = types.Model CodeInfo = types.CodeInfo ContractInfo = types.ContractInfo diff --git a/x/wasm/ibc.go b/x/wasm/ibc.go index f9097c2549..b77d7b2860 100644 --- a/x/wasm/ibc.go +++ b/x/wasm/ibc.go @@ -29,9 +29,11 @@ func (i IBCHandler) OnChanOpenInit(ctx sdk.Context, order channeltypes.Order, co return sdkerrors.Wrapf(err, "contract port id") } - err = i.keeper.OnOpenChannel(ctx, contractAddr, order, version, connectionHops, cosmwasm.IBCInfo{ - Port: portID, - Channel: channelID, + err = i.keeper.OnOpenChannel(ctx, contractAddr, cosmwasm.IBCChannel{ + Endpoint: cosmwasm.IBCEndpoint{Port: portID, Channel: channelID}, + CounterpartyEndpoint: cosmwasm.IBCEndpoint{Port: counterParty.PortId, Channel: counterParty.ChannelId}, + Order: order, + Version: version, }) if err != nil { return err @@ -50,9 +52,12 @@ func (i IBCHandler) OnChanOpenTry(ctx sdk.Context, order channeltypes.Order, con return sdkerrors.Wrapf(err, "contract port id") } - err = i.keeper.OnOpenChannel(ctx, contractAddr, order, version, connectionHops, cosmwasm.IBCInfo{ - Port: portID, - Channel: channelID, + err = i.keeper.OnOpenChannel(ctx, contractAddr, cosmwasm.IBCChannel{ + Endpoint: cosmwasm.IBCEndpoint{Port: portID, Channel: channelID}, + CounterpartyEndpoint: cosmwasm.IBCEndpoint{Port: counterParty.PortId, Channel: counterParty.ChannelId}, + Order: order, + Version: version, + CounterpartyVersion: &counterpartyVersion, }) if err != nil { return err @@ -73,9 +78,12 @@ func (i IBCHandler) OnChanOpenAck(ctx sdk.Context, portID, channelID string, cou if !ok { return sdkerrors.Wrap(types.ErrInvalidCounterparty, "not found") } - return i.keeper.OnConnectChannel(ctx, contractAddr, channelInfo.Counterparty, counterpartyVersion, cosmwasm.IBCInfo{ - Port: portID, - Channel: channelID, + return i.keeper.OnConnectChannel(ctx, contractAddr, cosmwasm.IBCChannel{ + Endpoint: cosmwasm.IBCEndpoint{Port: portID, Channel: channelID}, + CounterpartyEndpoint: cosmwasm.IBCEndpoint{Port: channelInfo.Counterparty.PortId, Channel: channelInfo.Counterparty.ChannelId}, + Order: channelInfo.Ordering, + Version: channelInfo.Version, + CounterpartyVersion: &counterpartyVersion, }) } @@ -88,9 +96,11 @@ func (i IBCHandler) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) if !ok { return sdkerrors.Wrap(types.ErrInvalidCounterparty, "not found") } - return i.keeper.OnConnectChannel(ctx, contractAddr, channelInfo.Counterparty, channelInfo.Version, cosmwasm.IBCInfo{ - Port: portID, - Channel: channelID, + return i.keeper.OnConnectChannel(ctx, contractAddr, cosmwasm.IBCChannel{ + Endpoint: cosmwasm.IBCEndpoint{Port: portID, Channel: channelID}, + CounterpartyEndpoint: cosmwasm.IBCEndpoint{Port: channelInfo.Counterparty.PortId, Channel: channelInfo.Counterparty.ChannelId}, + Order: channelInfo.Ordering, + Version: channelInfo.Version, }) } @@ -116,7 +126,7 @@ func (i IBCHandler) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) (* if err != nil { return nil, nil, sdkerrors.Wrapf(err, "contract port id") } - msgBz, err := i.keeper.OnRecvPacket(ctx, contractAddr, packet.Data, ibcInfoFromPacket(packet)) + msgBz, err := i.keeper.OnRecvPacket(ctx, contractAddr, newIBCPacket(packet)) if err != nil { return nil, nil, err } @@ -140,12 +150,15 @@ func (i IBCHandler) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) (* } func (i IBCHandler) OnAcknowledgementPacket(ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte) (*sdk.Result, error) { - contractAddr, err := ContractFromPortID(packet.DestinationPort) + contractAddr, err := ContractFromPortID(packet.SourcePort) if err != nil { return nil, sdkerrors.Wrapf(err, "contract port id") } - err = i.keeper.OnAckPacket(ctx, contractAddr, packet.Data, acknowledgement, ibcInfoFromPacket(packet)) + err = i.keeper.OnAckPacket(ctx, contractAddr, cosmwasm.IBCAcknowledgement{ + Acknowledgement: acknowledgement, + OriginalPacket: newIBCPacket(packet), + }) if err != nil { return nil, err } @@ -180,7 +193,7 @@ func (i IBCHandler) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet) if err != nil { return nil, sdkerrors.Wrapf(err, "contract port id") } - err = i.keeper.OnTimeoutPacket(ctx, contractAddr, packet.Data, ibcInfoFromPacket(packet)) + err = i.keeper.OnTimeoutPacket(ctx, contractAddr, newIBCPacket(packet)) if err != nil { return nil, err } @@ -200,10 +213,13 @@ func (i IBCHandler) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet) } -func ibcInfoFromPacket(packet channeltypes.Packet) cosmwasm.IBCInfo { - return cosmwasm.IBCInfo{ - Port: packet.DestinationPort, - Channel: packet.DestinationChannel, - Packet: cosmwasm.NewIBCPacketInfo(packet.Sequence, packet.SourcePort, packet.SourceChannel, packet.TimeoutHeight, packet.TimeoutTimestamp), +func newIBCPacket(packet channeltypes.Packet) cosmwasm.IBCPacket { + return cosmwasm.IBCPacket{ + Data: packet.Data, + Source: cosmwasm.IBCEndpoint{Channel: packet.SourceChannel, Port: packet.SourcePort}, + Destination: cosmwasm.IBCEndpoint{Channel: packet.DestinationChannel, Port: packet.DestinationPort}, + Sequence: packet.Sequence, + TimeoutHeight: packet.TimeoutHeight, + TimeoutTimestamp: packet.TimeoutTimestamp, } } diff --git a/x/wasm/ibc_testing/chain.go b/x/wasm/ibc_testing/chain.go index f29d635763..d466b315ea 100644 --- a/x/wasm/ibc_testing/chain.go +++ b/x/wasm/ibc_testing/chain.go @@ -44,13 +44,13 @@ const ( TrustingPeriod time.Duration = time.Hour * 24 * 7 * 2 UnbondingPeriod time.Duration = time.Hour * 24 * 7 * 3 MaxClockDrift time.Duration = time.Second * 10 - - ChannelVersion = ibctransfertypes.Version - InvalidID = "IDisInvalid" + InvalidID = "IDisInvalid" ConnectionIDPrefix = "connectionid" ) +//var ChannelVersion = ibctransfertypes.Version + // Default params variables used to create a TM client var ( DefaultTrustLevel ibctmtypes.Fraction = ibctmtypes.DefaultTrustLevel @@ -334,10 +334,11 @@ func (chain *TestChain) AddTestConnection(clientID, counterpartyClientID string) // format: // connectionid func (chain *TestChain) ConstructNextTestConnection(clientID, counterpartyClientID string) *TestConnection { - connectionID := ConnectionIDPrefix + strconv.Itoa(len(chain.Connections)) + connectionID := chain.ChainID + ConnectionIDPrefix + strconv.Itoa(len(chain.Connections)) return &TestConnection{ ID: connectionID, ClientID: clientID, + NextChannelVersion: ibctransfertypes.Version, CounterpartyClientID: counterpartyClientID, } } @@ -590,7 +591,7 @@ func (chain *TestChain) ChanOpenInit( ) error { msg := channeltypes.NewMsgChannelOpenInit( ch.PortID, ch.ID, - ChannelVersion, order, []string{connectionID}, + ch.Version, order, []string{connectionID}, counterparty.PortID, counterparty.ID, chain.SenderAccount.GetAddress(), ) @@ -608,9 +609,9 @@ func (chain *TestChain) ChanOpenTry( msg := channeltypes.NewMsgChannelOpenTry( ch.PortID, ch.ID, - ChannelVersion, order, []string{connectionID}, + ch.Version, order, []string{connectionID}, counterpartyCh.PortID, counterpartyCh.ID, - ChannelVersion, + counterpartyCh.Version, proof, height, chain.SenderAccount.GetAddress(), ) @@ -626,7 +627,7 @@ func (chain *TestChain) ChanOpenAck( msg := channeltypes.NewMsgChannelOpenAck( ch.PortID, ch.ID, - ChannelVersion, + counterpartyCh.Version, proof, height, chain.SenderAccount.GetAddress(), ) diff --git a/x/wasm/ibc_testing/types.go b/x/wasm/ibc_testing/types.go index 71898e01e2..d0c3e61780 100644 --- a/x/wasm/ibc_testing/types.go +++ b/x/wasm/ibc_testing/types.go @@ -10,6 +10,7 @@ type TestConnection struct { ID string ClientID string CounterpartyClientID string + NextChannelVersion string Channels []TestChannel } @@ -32,6 +33,7 @@ func (conn *TestConnection) AddTestChannel(portID string) TestChannel { func (conn *TestConnection) NextTestChannel(portID string) TestChannel { channelID := fmt.Sprintf("%s%d", conn.ID, len(conn.Channels)) return TestChannel{ + Version: conn.NextChannelVersion, PortID: portID, ID: channelID, ClientID: conn.ClientID, @@ -58,4 +60,5 @@ type TestChannel struct { ID string ClientID string CounterpartyClientID string + Version string } diff --git a/x/wasm/internal/keeper/cosmwasm/contract.go b/x/wasm/internal/keeper/cosmwasm/contract.go index 0cce7cddf5..c145dc751f 100644 --- a/x/wasm/internal/keeper/cosmwasm/contract.go +++ b/x/wasm/internal/keeper/cosmwasm/contract.go @@ -1,42 +1,74 @@ package cosmwasm import ( - wasmTypes "github.com/CosmWasm/go-cosmwasm/types" - sdk "github.com/cosmos/cosmos-sdk/types" + cosmwasmv1 "github.com/CosmWasm/go-cosmwasm/types" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" ) +type IBCEndpoint struct { + Channel string `json:"channel"` + Port string `json:"port"` +} + +type IBCChannel struct { + Endpoint IBCEndpoint + CounterpartyEndpoint IBCEndpoint + Order channeltypes.Order + Version string + // CounterpartyVersion can be nil when not known this context, yet + CounterpartyVersion *string `json:"counterparty_version,omitempty"` +} + +type IBCPacket struct { + Data []byte + // identifies the channel and port on the sending chain. + Source IBCEndpoint + // identifies the channel and port on the receiving chain. + Destination IBCEndpoint + Sequence uint64 + // block height after which the packet times out + TimeoutHeight uint64 + // block timestamp (in nanoseconds) after which the packet times out + TimeoutTimestamp uint64 +} + +type IBCAcknowledgement struct { + Acknowledgement []byte `json:"acknowledgement"` + OriginalPacket IBCPacket `json:"original_packet"` +} + type IBCPacketReceiveResponse struct { // Acknowledgement contains the data to acknowledge the ibc packet execution Acknowledgement []byte `json:"acknowledgement"` // Messages comes directly from the contract and is it's request for action - Messages []sdk.Msg `json:"messages,omitempty"` + Messages []CosmosMsg `json:"messages,omitempty"` // Log contains event attributes to expose over abci interface - Log []wasmTypes.LogAttribute `json:"log,omitempty"` + Log []cosmwasmv1.LogAttribute `json:"log,omitempty"` } type IBCPacketAcknowledgementResponse struct { - Messages []sdk.Msg `json:"messages"` - Log []wasmTypes.LogAttribute `json:"log"` + Messages []CosmosMsg `json:"messages"` + Log []cosmwasmv1.LogAttribute `json:"log"` } type IBCPacketTimeoutResponse struct { - Messages []sdk.Msg `json:"messages"` - Log []wasmTypes.LogAttribute `json:"log"` + Messages []CosmosMsg `json:"messages"` + Log []cosmwasmv1.LogAttribute `json:"log"` } type IBCChannelOpenResponse struct { - // Result contains a boolean if the channel would be accepted - Result bool `json:"result"` + // Success contains a boolean if the channel would be accepted + Success bool `json:"result"` // Reason optional description why it was not accepted Reason string `json:"reason"` } type IBCChannelConnectResponse struct { - Messages []sdk.Msg `json:"messages"` - Log []wasmTypes.LogAttribute `json:"log"` + Messages []CosmosMsg `json:"messages"` + Log []cosmwasmv1.LogAttribute `json:"log"` } type IBCChannelCloseResponse struct { - Messages []sdk.Msg `json:"messages"` - Log []wasmTypes.LogAttribute `json:"log"` + Messages []CosmosMsg `json:"messages"` + Log []cosmwasmv1.LogAttribute `json:"log"` } diff --git a/x/wasm/internal/keeper/cosmwasm/env.go b/x/wasm/internal/keeper/cosmwasm/env.go index 2506735b91..1766879e21 100644 --- a/x/wasm/internal/keeper/cosmwasm/env.go +++ b/x/wasm/internal/keeper/cosmwasm/env.go @@ -4,15 +4,12 @@ import ( wasmtypes "github.com/CosmWasm/go-cosmwasm/types" "github.com/CosmWasm/wasmd/x/wasm/internal/types" sdk "github.com/cosmos/cosmos-sdk/types" - channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" ) type Env struct { Block wasmtypes.BlockInfo `json:"block"` Message wasmtypes.MessageInfo `json:"message"` Contract wasmtypes.ContractInfo `json:"contract"` - // optional IBC meta data only set when called in IBC context - IBC *IBCInfo `json:"ibc,omitempty"` } func NewEnv(ctx sdk.Context, creator sdk.AccAddress, deposit sdk.Coins, contractAddr sdk.AccAddress) Env { @@ -38,45 +35,3 @@ func NewEnv(ctx sdk.Context, creator sdk.AccAddress, deposit sdk.Coins, contract }, } } - -type IBCInfo struct { - // Port of the contract - Port string `json:"port"` - // Channel to the contract - Channel string `json:"channel"` - // Optional packet meta data when called on IBC packet functions - Packet *IBCPacketInfo `json:"packet,omitempty"` -} - -func (i IBCInfo) AsPacket(data []byte) channeltypes.Packet { - r := channeltypes.Packet{ - DestinationPort: i.Port, - DestinationChannel: i.Channel, - } - if i.Packet == nil { - return r - } - r.TimeoutHeight = i.Packet.TimeoutHeight - r.Sequence = i.Packet.Sequence - r.SourcePort = i.Packet.SourcePort - r.SourceChannel = i.Packet.SourceChannel - r.TimeoutTimestamp = i.Packet.TimeoutTimestamp - r.Data = data - return r -} - -type IBCPacketInfo struct { - Sequence uint64 - // identifies the port on the sending chain. - SourcePort string - // identifies the channel end on the sending chain. - SourceChannel string - // block height after which the packet times out - TimeoutHeight uint64 - // block timestamp (in nanoseconds) after which the packet times out - TimeoutTimestamp uint64 -} - -func NewIBCPacketInfo(sequence uint64, sourcePort string, sourceChannel string, timeoutHeight uint64, timeoutTimestamp uint64) *IBCPacketInfo { - return &IBCPacketInfo{Sequence: sequence, SourcePort: sourcePort, SourceChannel: sourceChannel, TimeoutHeight: timeoutHeight, TimeoutTimestamp: timeoutTimestamp} -} diff --git a/x/wasm/internal/keeper/cosmwasm/msg.go b/x/wasm/internal/keeper/cosmwasm/msg.go new file mode 100644 index 0000000000..59511e41c5 --- /dev/null +++ b/x/wasm/internal/keeper/cosmwasm/msg.go @@ -0,0 +1,58 @@ +package cosmwasm + +import ( + "encoding/json" + + cosmwasmv1 "github.com/CosmWasm/go-cosmwasm/types" +) + +// CosmosMsg is an rust enum and only (exactly) one of the fields should be set +// Should we do a cleaner approach in Go? (type/data?) +type CosmosMsg struct { + Bank *cosmwasmv1.BankMsg `json:"bank,omitempty"` + Custom json.RawMessage `json:"custom,omitempty"` + Staking *cosmwasmv1.StakingMsg `json:"staking,omitempty"` + Wasm *cosmwasmv1.WasmMsg `json:"wasm,omitempty"` + IBC *IBCMsg `json:"wasm_ibc,omitempty"` +} + +type IBCMsg struct { + SendPacket *IBCSendMsg `json:"execute,omitempty"` + CloseChannel *IBCCloseChannelMsg `json:"instantiate,omitempty"` + // Transfer starts an ics-20 transfer from a contract using the sdk ibc-transfer module. + Transfer *IBCTransferMsg `json:"instantiate,omitempty"` +} + +type IBCSendMsg struct { + // This is our contract-local ID + ChannelID string + Data []byte + // optional fields (or do we need exactly/at least one of these?) + TimeoutHeight uint64 + TimeoutTimestamp uint64 +} + +type IBCCloseChannelMsg struct { + ChannelID string +} + +type IBCTransferMsg struct { // TODO: impl +} + +//------- Results / Msgs ------------- + +// HandleResult is the raw response from the handle call +type HandleResult struct { + Ok *HandleResponse `json:"Ok,omitempty"` + Err *cosmwasmv1.StdError `json:"Err,omitempty"` +} + +// HandleResponse defines the return value on a successful handle +type HandleResponse struct { + // Messages comes directly from the contract and is it's request for action + Messages []CosmosMsg `json:"messages"` + // base64-encoded bytes to return as ABCI.Data field + Data []byte `json:"data"` + // log message to return over abci interface + Log []cosmwasmv1.LogAttribute `json:"log"` +} diff --git a/x/wasm/internal/keeper/cosmwasm/query.go b/x/wasm/internal/keeper/cosmwasm/query.go index 3c03e5b56f..4f8c1e26cc 100644 --- a/x/wasm/internal/keeper/cosmwasm/query.go +++ b/x/wasm/internal/keeper/cosmwasm/query.go @@ -12,7 +12,3 @@ type ChannelEndQuery struct { type ChannelClientStateQuery struct { PortID, ChannelID string } - -//type AllChannelsToContractQuery struct { -// address string -//} diff --git a/x/wasm/internal/keeper/handler_plugin.go b/x/wasm/internal/keeper/handler_plugin.go index bbd408c8fe..79ff0a764b 100644 --- a/x/wasm/internal/keeper/handler_plugin.go +++ b/x/wasm/internal/keeper/handler_plugin.go @@ -4,12 +4,16 @@ import ( "encoding/json" "fmt" - wasmTypes "github.com/CosmWasm/go-cosmwasm/types" + cosmwasmv1 "github.com/CosmWasm/go-cosmwasm/types" + cosmwasmv2 "github.com/CosmWasm/wasmd/x/wasm/internal/keeper/cosmwasm" "github.com/CosmWasm/wasmd/x/wasm/internal/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + ibctransfertypes "github.com/cosmos/cosmos-sdk/x/ibc-transfer/types" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -19,32 +23,46 @@ type MessageHandler struct { } func NewMessageHandler(router sdk.Router, customEncoders *MessageEncoders) MessageHandler { - encoders := DefaultEncoders().Merge(customEncoders) + encoders := DefaultEncoders(nil, nil).Merge(customEncoders) return MessageHandler{ router: router, encoders: encoders, } } -type BankEncoder func(sender sdk.AccAddress, msg *wasmTypes.BankMsg) ([]sdk.Msg, error) +func NewMessageHandlerV2(router sdk.Router, channelKeeper types.ChannelKeeper, capabilityKeeper types.CapabilityKeeper, customEncoders *MessageEncoders) MessageHandler { + encoders := DefaultEncoders(channelKeeper, capabilityKeeper).Merge(customEncoders) + return MessageHandler{ + router: router, + encoders: encoders, + } +} + +type BankEncoder func(sender sdk.AccAddress, msg *cosmwasmv1.BankMsg) ([]sdk.Msg, error) type CustomEncoder func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) -type StakingEncoder func(sender sdk.AccAddress, msg *wasmTypes.StakingMsg) ([]sdk.Msg, error) -type WasmEncoder func(sender sdk.AccAddress, msg *wasmTypes.WasmMsg) ([]sdk.Msg, error) +type StakingEncoder func(sender sdk.AccAddress, msg *cosmwasmv1.StakingMsg) ([]sdk.Msg, error) +type WasmEncoder func(sender sdk.AccAddress, msg *cosmwasmv1.WasmMsg) ([]sdk.Msg, error) +type IBCEncoder func(ctx sdk.Context, sender sdk.AccAddress, source cosmwasmv2.IBCEndpoint, msg *cosmwasmv2.IBCMsg) ([]sdk.Msg, error) type MessageEncoders struct { Bank BankEncoder Custom CustomEncoder Staking StakingEncoder Wasm WasmEncoder + IBC IBCEncoder } -func DefaultEncoders() MessageEncoders { - return MessageEncoders{ +func DefaultEncoders(channelKeeper types.ChannelKeeper, capabilityKeeper types.CapabilityKeeper) MessageEncoders { + e := MessageEncoders{ Bank: EncodeBankMsg, Custom: NoCustomMsg, Staking: EncodeStakingMsg, Wasm: EncodeWasmMsg, } + if channelKeeper != nil { // todo: quick hack to keep tests happy + e.IBC = EncodeIBCMsg(channelKeeper, capabilityKeeper) + } + return e } func (e MessageEncoders) Merge(o *MessageEncoders) MessageEncoders { @@ -63,10 +81,28 @@ func (e MessageEncoders) Merge(o *MessageEncoders) MessageEncoders { if o.Wasm != nil { e.Wasm = o.Wasm } + if o.IBC != nil { + e.IBC = o.IBC + } return e } -func (e MessageEncoders) Encode(contractAddr sdk.AccAddress, msg wasmTypes.CosmosMsg) ([]sdk.Msg, error) { +func (e MessageEncoders) Encode(contractAddr sdk.AccAddress, msg cosmwasmv1.CosmosMsg) ([]sdk.Msg, error) { + switch { + case msg.Bank != nil: + return e.Bank(contractAddr, msg.Bank) + case msg.Custom != nil: + return e.Custom(contractAddr, msg.Custom) + case msg.Staking != nil: + return e.Staking(contractAddr, msg.Staking) + case msg.Wasm != nil: + return e.Wasm(contractAddr, msg.Wasm) + } + return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Wasm") +} + +// todo: quick hack cloned method to keep tests happy. +func (e MessageEncoders) EncodeV2(ctx sdk.Context, contractAddr sdk.AccAddress, source cosmwasmv2.IBCEndpoint, msg cosmwasmv2.CosmosMsg) ([]sdk.Msg, error) { switch { case msg.Bank != nil: return e.Bank(contractAddr, msg.Bank) @@ -76,11 +112,13 @@ func (e MessageEncoders) Encode(contractAddr sdk.AccAddress, msg wasmTypes.Cosmo return e.Staking(contractAddr, msg.Staking) case msg.Wasm != nil: return e.Wasm(contractAddr, msg.Wasm) + case msg.IBC != nil: + return e.IBC(ctx, contractAddr, source, msg.IBC) } return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Wasm") } -func EncodeBankMsg(sender sdk.AccAddress, msg *wasmTypes.BankMsg) ([]sdk.Msg, error) { +func EncodeBankMsg(sender sdk.AccAddress, msg *cosmwasmv1.BankMsg) ([]sdk.Msg, error) { if msg.Send == nil { return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Bank") } @@ -111,7 +149,7 @@ func NoCustomMsg(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Custom variant not supported") } -func EncodeStakingMsg(sender sdk.AccAddress, msg *wasmTypes.StakingMsg) ([]sdk.Msg, error) { +func EncodeStakingMsg(sender sdk.AccAddress, msg *cosmwasmv1.StakingMsg) ([]sdk.Msg, error) { if msg.Delegate != nil { validator, err := sdk.ValAddressFromBech32(msg.Delegate.Validator) if err != nil { @@ -191,7 +229,7 @@ func EncodeStakingMsg(sender sdk.AccAddress, msg *wasmTypes.StakingMsg) ([]sdk.M return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Staking") } -func EncodeWasmMsg(sender sdk.AccAddress, msg *wasmTypes.WasmMsg) ([]sdk.Msg, error) { +func EncodeWasmMsg(sender sdk.AccAddress, msg *cosmwasmv1.WasmMsg) ([]sdk.Msg, error) { if msg.Execute != nil { contractAddr, err := sdk.AccAddressFromBech32(msg.Execute.ContractAddr) if err != nil { @@ -229,7 +267,62 @@ func EncodeWasmMsg(sender sdk.AccAddress, msg *wasmTypes.WasmMsg) ([]sdk.Msg, er return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Wasm") } -func (h MessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, msg wasmTypes.CosmosMsg) error { +func EncodeIBCMsg(channelKeeper types.ChannelKeeper, capabilityKeeper types.CapabilityKeeper) IBCEncoder { + return func(ctx sdk.Context, sender sdk.AccAddress, source cosmwasmv2.IBCEndpoint, msg *cosmwasmv2.IBCMsg) ([]sdk.Msg, error) { + if msg.SendPacket != nil { + sequence, found := channelKeeper.GetNextSequenceSend(ctx, source.Port, source.Channel) + if !found { + return nil, sdkerrors.Wrapf( + channeltypes.ErrSequenceSendNotFound, + "source port: %s, source channel: %s", source.Port, source.Channel, + ) + } + + channelInfo, ok := channelKeeper.GetChannel(ctx, source.Port, source.Channel) + if !ok { + return nil, sdkerrors.Wrap(channeltypes.ErrInvalidChannel, "not found") + } + channelCap, ok := capabilityKeeper.GetCapability(ctx, host.ChannelCapabilityPath(source.Port, source.Channel)) + if !ok { + return nil, sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") + } + + packet := channeltypes.NewPacket( + msg.SendPacket.Data, + sequence, + source.Port, + source.Channel, + channelInfo.Counterparty.PortId, + channelInfo.Counterparty.ChannelId, + msg.SendPacket.TimeoutHeight, + msg.SendPacket.TimeoutTimestamp, + ) + return nil, channelKeeper.SendPacket(ctx, channelCap, packet) + } + if msg.CloseChannel != nil { + return []sdk.Msg{&channeltypes.MsgChannelCloseInit{ + PortId: PortIDForContract(sender), + ChannelId: msg.CloseChannel.ChannelID, + Signer: sender, + }}, nil + } + if msg.Transfer != nil { // TODO: implement proper + panic("not implemented") + return []sdk.Msg{&ibctransfertypes.MsgTransfer{ + SourcePort: "", + SourceChannel: "", + Token: sdk.Coin{}, + Sender: sender, + Receiver: "", + TimeoutHeight: 0, + TimeoutTimestamp: 0, + }}, nil + } + return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of IBC") + } +} + +func (h MessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, msg cosmwasmv1.CosmosMsg) error { sdkMsgs, err := h.encoders.Encode(contractAddr, msg) if err != nil { return err @@ -242,6 +335,21 @@ func (h MessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, m return nil } +func (h MessageHandler) DispatchV2(ctx sdk.Context, contractAddr sdk.AccAddress, source cosmwasmv2.IBCEndpoint, msgs ...cosmwasmv2.CosmosMsg) error { + for _, msg := range msgs { + sdkMsgs, err := h.encoders.EncodeV2(ctx, contractAddr, source, msg) + if err != nil { + return err + } + for _, sdkMsg := range sdkMsgs { + if err := h.handleSdkMessage(ctx, contractAddr, sdkMsg); err != nil { + return err + } + } + } + return nil +} + func (h MessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) error { // make sure this account can send it for _, acct := range msg.GetSigners() { @@ -270,7 +378,7 @@ func (h MessageHandler) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Addre return nil } -func convertWasmCoinsToSdkCoins(coins []wasmTypes.Coin) (sdk.Coins, error) { +func convertWasmCoinsToSdkCoins(coins []cosmwasmv1.Coin) (sdk.Coins, error) { var toSend sdk.Coins for _, coin := range coins { c, err := convertWasmCoinToSdkCoin(coin) @@ -282,7 +390,7 @@ func convertWasmCoinsToSdkCoins(coins []wasmTypes.Coin) (sdk.Coins, error) { return toSend, nil } -func convertWasmCoinToSdkCoin(coin wasmTypes.Coin) (sdk.Coin, error) { +func convertWasmCoinToSdkCoin(coin cosmwasmv1.Coin) (sdk.Coin, error) { amount, ok := sdk.NewIntFromString(coin.Amount) if !ok { return sdk.Coin{}, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, coin.Amount+coin.Denom) diff --git a/x/wasm/internal/keeper/handler_plugin_test.go b/x/wasm/internal/keeper/handler_plugin_test.go index 9d7eb8f371..3e3b381f74 100644 --- a/x/wasm/internal/keeper/handler_plugin_test.go +++ b/x/wasm/internal/keeper/handler_plugin_test.go @@ -258,7 +258,7 @@ func TestEncoding(t *testing.T) { }, } - encoder := DefaultEncoders() + encoder := DefaultEncoders(nil, nil) for name, tc := range cases { tc := tc t.Run(name, func(t *testing.T) { diff --git a/x/wasm/internal/keeper/ibc.go b/x/wasm/internal/keeper/ibc.go index 8cfaf7b2c3..5bc3cb4eb5 100644 --- a/x/wasm/internal/keeper/ibc.go +++ b/x/wasm/internal/keeper/ibc.go @@ -4,13 +4,13 @@ import ( "strings" wasm "github.com/CosmWasm/go-cosmwasm" + wasmTypes "github.com/CosmWasm/go-cosmwasm/types" "github.com/CosmWasm/wasmd/x/wasm/internal/keeper/cosmwasm" "github.com/CosmWasm/wasmd/x/wasm/internal/types" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" ) @@ -69,24 +69,25 @@ type IBCContractCallbacks interface { // Package livecycle // OnIBCPacketReceive handles an incoming IBC package - OnIBCPacketReceive(hash []byte, params cosmwasm.Env, msg []byte, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCPacketReceiveResponse, uint64, error) + OnIBCPacketReceive(hash []byte, params cosmwasm.Env, packet cosmwasm.IBCPacket, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCPacketReceiveResponse, uint64, error) // OnIBCPacketAcknowledgement handles a IBC package execution on the counterparty chain - OnIBCPacketAcknowledgement(hash []byte, params cosmwasm.Env, originalData []byte, acknowledgement []byte, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCPacketAcknowledgementResponse, uint64, error) + OnIBCPacketAcknowledgement(hash []byte, params cosmwasm.Env, packetAck cosmwasm.IBCAcknowledgement, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCPacketAcknowledgementResponse, uint64, error) // OnIBCPacketTimeout reverts state when the IBC package execution does not come in time - OnIBCPacketTimeout(hash []byte, params cosmwasm.Env, msg []byte, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCPacketTimeoutResponse, uint64, error) + OnIBCPacketTimeout(hash []byte, params cosmwasm.Env, packet cosmwasm.IBCPacket, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCPacketTimeoutResponse, uint64, error) // channel livecycle // OnIBCChannelOpen does the protocol version negotiation during channel handshake phase - OnIBCChannelOpen(hash []byte, params cosmwasm.Env, order channeltypes.Order, version string, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCChannelOpenResponse, uint64, error) + OnIBCChannelOpen(hash []byte, params cosmwasm.Env, channel cosmwasm.IBCChannel, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCChannelOpenResponse, uint64, error) // OnIBCChannelConnect callback when a IBC channel is established - OnIBCChannelConnect(hash []byte, params cosmwasm.Env, counterpartyPortID, counterpartyChannelID string, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCChannelConnectResponse, uint64, error) + OnIBCChannelConnect(hash []byte, params cosmwasm.Env, channel cosmwasm.IBCChannel, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCChannelConnectResponse, uint64, error) // OnIBCChannelConnect callback when a IBC channel is closed - OnIBCChannelClose(ctx sdk.Context, hash []byte, params cosmwasm.Env, counterpartyPortID, counterpartyChannelID string, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCChannelCloseResponse, uint64, error) + OnIBCChannelClose(ctx sdk.Context, hash []byte, params cosmwasm.Env, channel cosmwasm.IBCChannel, meter sdk.GasMeter, gas uint64) (*cosmwasm.IBCChannelCloseResponse, uint64, error) + Execute(hash []byte, params wasmTypes.Env, msg []byte, store prefix.Store, api wasm.GoAPI, querier QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasm.HandleResponse, uint64, error) } var MockContracts = make(map[string]IBCContractCallbacks, 0) -func (k Keeper) OnOpenChannel(ctx sdk.Context, contractAddr sdk.AccAddress, order channeltypes.Order, version string, connectionHops []string, ibcInfo cosmwasm.IBCInfo) error { +func (k Keeper) OnOpenChannel(ctx sdk.Context, contractAddr sdk.AccAddress, channel cosmwasm.IBCChannel) error { codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) if err != nil { return err @@ -94,7 +95,6 @@ func (k Keeper) OnOpenChannel(ctx sdk.Context, contractAddr sdk.AccAddress, orde var sender sdk.AccAddress // we don't know the sender params := cosmwasm.NewEnv(ctx, sender, nil, contractAddr) - params.IBC = &ibcInfo querier := QueryHandler{ Ctx: ctx, @@ -106,18 +106,18 @@ func (k Keeper) OnOpenChannel(ctx sdk.Context, contractAddr sdk.AccAddress, orde if !ok { // hack for testing without wasmer panic("not supported") } - res, gasUsed, execErr := mock.OnIBCChannelOpen(codeInfo.CodeHash, params, order, version, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) + res, gasUsed, execErr := mock.OnIBCChannelOpen(codeInfo.CodeHash, params, channel, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) consumeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) } - if !res.Result { // todo: would it make more sense to let the contract return an error instead? + if !res.Success { // todo: would it make more sense to let the contract return an error instead? return sdkerrors.Wrap(types.ErrInvalid, res.Reason) } return nil } -func (k Keeper) OnRecvPacket(ctx sdk.Context, contractAddr sdk.AccAddress, payloadData []byte, ibcInfo cosmwasm.IBCInfo) ([]byte, error) { +func (k Keeper) OnRecvPacket(ctx sdk.Context, contractAddr sdk.AccAddress, packet cosmwasm.IBCPacket) ([]byte, error) { codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) if err != nil { return nil, err @@ -125,7 +125,6 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, contractAddr sdk.AccAddress, paylo var sender sdk.AccAddress // we don't know the sender params := cosmwasm.NewEnv(ctx, sender, nil, contractAddr) - params.IBC = &ibcInfo querier := QueryHandler{ Ctx: ctx, @@ -137,7 +136,7 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, contractAddr sdk.AccAddress, paylo if !ok { // hack for testing without wasmer panic("not supported") } - res, gasUsed, execErr := mock.OnIBCPacketReceive(codeInfo.CodeHash, params, payloadData, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) + res, gasUsed, execErr := mock.OnIBCPacketReceive(codeInfo.CodeHash, params, packet, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) consumeGas(ctx, gasUsed) if execErr != nil { return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) @@ -147,16 +146,13 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, contractAddr sdk.AccAddress, paylo events := types.ParseEvents(res.Log, contractAddr) ctx.EventManager().EmitEvents(events) - // hack: use sdk messages here for simplicity - for _, m := range res.Messages { - if err := k.messenger.handleSdkMessage(ctx, contractAddr, m); err != nil { - return nil, err - } + if err := k.messenger.DispatchV2(ctx, contractAddr, packet.Destination, res.Messages...); err != nil { + return nil, err } return res.Acknowledgement, nil } -func (k Keeper) OnAckPacket(ctx sdk.Context, contractAddr sdk.AccAddress, payloadData []byte, acknowledgement []byte, ibcInfo cosmwasm.IBCInfo) error { +func (k Keeper) OnAckPacket(ctx sdk.Context, contractAddr sdk.AccAddress, acknowledgement cosmwasm.IBCAcknowledgement) error { codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) if err != nil { return err @@ -164,7 +160,6 @@ func (k Keeper) OnAckPacket(ctx sdk.Context, contractAddr sdk.AccAddress, payloa var sender sdk.AccAddress // we don't know the sender params := cosmwasm.NewEnv(ctx, sender, nil, contractAddr) - params.IBC = &ibcInfo querier := QueryHandler{ Ctx: ctx, @@ -176,7 +171,7 @@ func (k Keeper) OnAckPacket(ctx sdk.Context, contractAddr sdk.AccAddress, payloa if !ok { panic("not supported") } - res, gasUsed, execErr := mock.OnIBCPacketAcknowledgement(codeInfo.CodeHash, params, payloadData, acknowledgement, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) + res, gasUsed, execErr := mock.OnIBCPacketAcknowledgement(codeInfo.CodeHash, params, acknowledgement, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) consumeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) @@ -186,16 +181,13 @@ func (k Keeper) OnAckPacket(ctx sdk.Context, contractAddr sdk.AccAddress, payloa events := types.ParseEvents(res.Log, contractAddr) ctx.EventManager().EmitEvents(events) - // hack: use sdk messages here for simplicity - for _, m := range res.Messages { - if err := k.messenger.handleSdkMessage(ctx, contractAddr, m); err != nil { - return err - } + if err := k.messenger.DispatchV2(ctx, contractAddr, acknowledgement.OriginalPacket.Source, res.Messages...); err != nil { + return err } return nil } -func (k Keeper) OnTimeoutPacket(ctx sdk.Context, contractAddr sdk.AccAddress, payloadData []byte, ibcInfo cosmwasm.IBCInfo) error { +func (k Keeper) OnTimeoutPacket(ctx sdk.Context, contractAddr sdk.AccAddress, packet cosmwasm.IBCPacket) error { codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) if err != nil { return err @@ -203,7 +195,6 @@ func (k Keeper) OnTimeoutPacket(ctx sdk.Context, contractAddr sdk.AccAddress, pa var sender sdk.AccAddress // we don't know the sender params := cosmwasm.NewEnv(ctx, sender, nil, contractAddr) - params.IBC = &ibcInfo querier := QueryHandler{ Ctx: ctx, @@ -215,7 +206,7 @@ func (k Keeper) OnTimeoutPacket(ctx sdk.Context, contractAddr sdk.AccAddress, pa if !ok { // hack for testing without wasmer panic("not supported") } - res, gasUsed, execErr := mock.OnIBCPacketTimeout(codeInfo.CodeHash, params, payloadData, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) + res, gasUsed, execErr := mock.OnIBCPacketTimeout(codeInfo.CodeHash, params, packet, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) consumeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) @@ -225,16 +216,13 @@ func (k Keeper) OnTimeoutPacket(ctx sdk.Context, contractAddr sdk.AccAddress, pa events := types.ParseEvents(res.Log, contractAddr) ctx.EventManager().EmitEvents(events) - // hack: use sdk messages here for simplicity - for _, m := range res.Messages { - if err := k.messenger.handleSdkMessage(ctx, contractAddr, m); err != nil { - return err - } + if err := k.messenger.DispatchV2(ctx, contractAddr, packet.Source, res.Messages...); err != nil { + return err } return nil } -func (k Keeper) OnConnectChannel(ctx sdk.Context, contractAddr sdk.AccAddress, counterparty channeltypes.Counterparty, version string, ibcInfo cosmwasm.IBCInfo) error { +func (k Keeper) OnConnectChannel(ctx sdk.Context, contractAddr sdk.AccAddress, channel cosmwasm.IBCChannel) error { codeInfo, prefixStore, err := k.contractInstance(ctx, contractAddr) if err != nil { return err @@ -242,7 +230,6 @@ func (k Keeper) OnConnectChannel(ctx sdk.Context, contractAddr sdk.AccAddress, c var sender sdk.AccAddress // we don't know the sender params := cosmwasm.NewEnv(ctx, sender, nil, contractAddr) - params.IBC = &ibcInfo querier := QueryHandler{ Ctx: ctx, @@ -254,7 +241,7 @@ func (k Keeper) OnConnectChannel(ctx sdk.Context, contractAddr sdk.AccAddress, c if !ok { // hack for testing without wasmer panic("not supported") } - res, gasUsed, execErr := mock.OnIBCChannelConnect(codeInfo.CodeHash, params, counterparty.PortId, counterparty.ChannelId, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) + res, gasUsed, execErr := mock.OnIBCChannelConnect(codeInfo.CodeHash, params, channel, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) consumeGas(ctx, gasUsed) if execErr != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) @@ -264,11 +251,8 @@ func (k Keeper) OnConnectChannel(ctx sdk.Context, contractAddr sdk.AccAddress, c events := types.ParseEvents(res.Log, contractAddr) ctx.EventManager().EmitEvents(events) - // hack: use sdk messages here for simplicity - for _, m := range res.Messages { - if err := k.messenger.handleSdkMessage(ctx, contractAddr, m); err != nil { - return err - } + if err := k.messenger.DispatchV2(ctx, contractAddr, channel.Endpoint, res.Messages...); err != nil { + return err } return nil } diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index cb7db38184..70045b4ef6 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -7,6 +7,7 @@ import ( wasm "github.com/CosmWasm/go-cosmwasm" wasmTypes "github.com/CosmWasm/go-cosmwasm/types" + cosmwasmv2 "github.com/CosmWasm/wasmd/x/wasm/internal/keeper/cosmwasm" "github.com/CosmWasm/wasmd/x/wasm/internal/types" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" @@ -89,6 +90,8 @@ func NewKeeper( paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) } + // todo: revisit: DefaultEncoders are used twice now + quickHack := DefaultEncoders(channelKeeper, scopedKeeper).Merge(customEncoders) keeper := Keeper{ storeKey: storeKey, cdc: cdc, @@ -98,7 +101,7 @@ func NewKeeper( ChannelKeeper: channelKeeper, PortKeeper: portKeeper, ScopedKeeper: scopedKeeper, - messenger: NewMessageHandler(router, customEncoders), + messenger: NewMessageHandler(router, &quickHack), queryGasLimit: wasmConfig.SmartQueryGasLimit, authZPolicy: DefaultAuthorizationPolicy{}, paramSpace: paramSpace, @@ -307,6 +310,27 @@ func (k Keeper) Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller } gas := gasForContract(ctx) + + mock, ok := MockContracts[contractAddress.String()] + if ok { + res, gasUsed, execErr := mock.Execute(codeInfo.CodeHash, params, msg, prefixStore, cosmwasmAPI, querier, ctx.GasMeter(), gas) + consumeGas(ctx, gasUsed) + if execErr != nil { + return nil, sdkerrors.Wrap(types.ErrExecuteFailed, execErr.Error()) + } + + // emit all events from this contract itself + events := types.ParseEvents(res.Log, contractAddress) + ctx.EventManager().EmitEvents(events) + + if err := k.messenger.DispatchV2(ctx, contractAddress, cosmwasmv2.IBCEndpoint{}, res.Messages...); err != nil { + return nil, err + } + return &sdk.Result{ + Data: res.Data, + }, nil + } + res, gasUsed, execErr := k.wasmer.Execute(codeInfo.CodeHash, params, msg, prefixStore, cosmwasmAPI, querier, gasMeter(ctx), gas) consumeGas(ctx, gasUsed) if execErr != nil { diff --git a/x/wasm/internal/keeper/reflect_test.go b/x/wasm/internal/keeper/reflect_test.go index c200019f3b..98a0dec0ee 100644 --- a/x/wasm/internal/keeper/reflect_test.go +++ b/x/wasm/internal/keeper/reflect_test.go @@ -137,7 +137,7 @@ func TestMaskReflectContractSend(t *testing.T) { } func TestMaskReflectCustomMsg(t *testing.T) { - t.Skip("Alex: fails with `cannot protobuf JSON decode unsupported type: *types.Msg: failed to unmarshal JSON bytes`") + t.Skip("Alex: fails with `cannot protobuf JSON decode unsupported type: *types.Data: failed to unmarshal JSON bytes`") tempDir, err := ioutil.TempDir("", "wasm") require.NoError(t, err) defer os.RemoveAll(tempDir) @@ -332,7 +332,7 @@ func maskEncoders(cdc codec.Marshaler) *MessageEncoders { } } -// fromMaskRawMsg decodes msg.Data to an sdk.Msg using amino json encoding. +// fromMaskRawMsg decodes msg.Data to an sdk.Data using amino json encoding. // this needs to be registered on the Encoders func fromMaskRawMsg(cdc codec.Marshaler) CustomEncoder { return func(_sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { diff --git a/x/wasm/internal/types/codec.go b/x/wasm/internal/types/codec.go index b0965c3038..91c58f09a3 100644 --- a/x/wasm/internal/types/codec.go +++ b/x/wasm/internal/types/codec.go @@ -34,7 +34,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { &MsgUpdateAdmin{}, &MsgClearAdmin{}, &MsgIBCCloseChannel{}, - &MsgWasmIBCCall{}, + &MsgIBCSend{}, ) registry.RegisterImplementations( (*govtypes.Content)(nil), diff --git a/x/wasm/internal/types/ibc.go b/x/wasm/internal/types/ibc.go index db9f4f1b3f..4905431ac4 100644 --- a/x/wasm/internal/types/ibc.go +++ b/x/wasm/internal/types/ibc.go @@ -34,3 +34,7 @@ type ConnectionKeeper interface { type PortKeeper interface { BindPort(ctx sdk.Context, portID string) *capabilitytypes.Capability } + +type CapabilityKeeper interface { + GetCapability(ctx sdk.Context, name string) (*capabilitytypes.Capability, bool) +} diff --git a/x/wasm/internal/types/ibc.pb.go b/x/wasm/internal/types/ibc.pb.go index e55aa8733d..fbd60d4117 100644 --- a/x/wasm/internal/types/ibc.pb.go +++ b/x/wasm/internal/types/ibc.pb.go @@ -6,7 +6,6 @@ package types import ( encoding_json "encoding/json" fmt "fmt" - github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" io "io" @@ -25,34 +24,31 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -type MsgWasmIBCCall struct { - // the port on which the packet will be sent - SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty" yaml:"source_port"` +type MsgIBCSend struct { // the channel by which the packet will be sent - SourceChannel string `protobuf:"bytes,2,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty" yaml:"source_channel"` - Sender github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,3,opt,name=sender,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"sender,omitempty"` + Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty" yaml:"source_channel"` // Timeout height relative to the current block height. // The timeout is disabled when set to 0. TimeoutHeight uint64 `protobuf:"varint,4,opt,name=timeout_height,json=timeoutHeight,proto3" json:"timeout_height,omitempty" yaml:"timeout_height"` // Timeout timestamp (in nanoseconds) relative to the current block timestamp. // The timeout is disabled when set to 0. TimeoutTimestamp uint64 `protobuf:"varint,5,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty" yaml:"timeout_timestamp"` - // Msg is the message to the contract - Msg encoding_json.RawMessage `protobuf:"bytes,6,opt,name=msg,proto3,casttype=encoding/json.RawMessage" json:"msg,omitempty"` + // data is the payload to transfer + Data encoding_json.RawMessage `protobuf:"bytes,6,opt,name=data,proto3,casttype=encoding/json.RawMessage" json:"data,omitempty"` } -func (m *MsgWasmIBCCall) Reset() { *m = MsgWasmIBCCall{} } -func (m *MsgWasmIBCCall) String() string { return proto.CompactTextString(m) } -func (*MsgWasmIBCCall) ProtoMessage() {} -func (*MsgWasmIBCCall) Descriptor() ([]byte, []int) { +func (m *MsgIBCSend) Reset() { *m = MsgIBCSend{} } +func (m *MsgIBCSend) String() string { return proto.CompactTextString(m) } +func (*MsgIBCSend) ProtoMessage() {} +func (*MsgIBCSend) Descriptor() ([]byte, []int) { return fileDescriptor_9e387a38c39d89d0, []int{0} } -func (m *MsgWasmIBCCall) XXX_Unmarshal(b []byte) error { +func (m *MsgIBCSend) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgWasmIBCCall) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgIBCSend) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgWasmIBCCall.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgIBCSend.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -62,23 +58,21 @@ func (m *MsgWasmIBCCall) XXX_Marshal(b []byte, deterministic bool) ([]byte, erro return b[:n], nil } } -func (m *MsgWasmIBCCall) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgWasmIBCCall.Merge(m, src) +func (m *MsgIBCSend) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgIBCSend.Merge(m, src) } -func (m *MsgWasmIBCCall) XXX_Size() int { +func (m *MsgIBCSend) XXX_Size() int { return m.Size() } -func (m *MsgWasmIBCCall) XXX_DiscardUnknown() { - xxx_messageInfo_MsgWasmIBCCall.DiscardUnknown(m) +func (m *MsgIBCSend) XXX_DiscardUnknown() { + xxx_messageInfo_MsgIBCSend.DiscardUnknown(m) } -var xxx_messageInfo_MsgWasmIBCCall proto.InternalMessageInfo +var xxx_messageInfo_MsgIBCSend proto.InternalMessageInfo // MsgIBCCloseChannel port and channel need to be owned by the contract type MsgIBCCloseChannel struct { - Port string `protobuf:"bytes,1,opt,name=port,proto3" json:"port,omitempty" yaml:"dest_port"` - Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty" yaml:"deset_channel"` - Sender github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,3,opt,name=sender,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"sender,omitempty"` + Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty" yaml:"source_channel"` } func (m *MsgIBCCloseChannel) Reset() { *m = MsgIBCCloseChannel{} } @@ -115,46 +109,39 @@ func (m *MsgIBCCloseChannel) XXX_DiscardUnknown() { var xxx_messageInfo_MsgIBCCloseChannel proto.InternalMessageInfo func init() { - proto.RegisterType((*MsgWasmIBCCall)(nil), "wasmd.x.wasmd.v1beta1.MsgWasmIBCCall") + proto.RegisterType((*MsgIBCSend)(nil), "wasmd.x.wasmd.v1beta1.MsgIBCSend") proto.RegisterType((*MsgIBCCloseChannel)(nil), "wasmd.x.wasmd.v1beta1.MsgIBCCloseChannel") } func init() { proto.RegisterFile("x/wasm/internal/types/ibc.proto", fileDescriptor_9e387a38c39d89d0) } var fileDescriptor_9e387a38c39d89d0 = []byte{ - // 452 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x92, 0x31, 0x8f, 0xd3, 0x30, - 0x14, 0xc7, 0x1b, 0x5a, 0x8a, 0x30, 0xdc, 0xe9, 0xb0, 0x7a, 0xc8, 0xa0, 0x53, 0x52, 0x65, 0xea, - 0x72, 0x09, 0x85, 0x01, 0x89, 0x89, 0x6b, 0x17, 0x3a, 0x14, 0xa1, 0x08, 0x09, 0x89, 0xe5, 0xe4, - 0x26, 0x96, 0x1b, 0x88, 0xed, 0x2a, 0xcf, 0xe5, 0x7a, 0xdf, 0x02, 0x89, 0xcf, 0xc3, 0x7e, 0xe3, - 0x8d, 0x4c, 0x11, 0xb4, 0xdf, 0x20, 0xe3, 0x4d, 0x28, 0x8e, 0x5b, 0x92, 0x9d, 0xe9, 0xf9, 0xbd, - 0xff, 0xcf, 0x4f, 0xcf, 0xfe, 0x3f, 0xe4, 0x6d, 0xc2, 0x2b, 0x0a, 0x22, 0x4c, 0xa5, 0x66, 0xb9, - 0xa4, 0x59, 0xa8, 0xaf, 0x57, 0x0c, 0xc2, 0x74, 0x11, 0x07, 0xab, 0x5c, 0x69, 0x85, 0x4f, 0x2b, - 0x39, 0x09, 0x36, 0x41, 0x1d, 0xbf, 0x8d, 0x17, 0x4c, 0xd3, 0xf1, 0xf3, 0x01, 0x57, 0x5c, 0x19, - 0x22, 0xac, 0x4e, 0x35, 0xec, 0xff, 0xe8, 0xa2, 0xe3, 0x39, 0xf0, 0x4f, 0x14, 0xc4, 0x6c, 0x32, - 0x9d, 0xd2, 0x2c, 0xc3, 0xaf, 0xd1, 0x23, 0x50, 0xeb, 0x3c, 0x66, 0x97, 0x2b, 0x95, 0x6b, 0xe2, - 0x0c, 0x9d, 0xd1, 0xc3, 0xc9, 0xd3, 0xb2, 0xf0, 0xf0, 0x35, 0x15, 0xd9, 0x1b, 0xbf, 0x21, 0xfa, - 0x11, 0xaa, 0xb3, 0x0f, 0x2a, 0xd7, 0xf8, 0x2d, 0x3a, 0xb6, 0x5a, 0xbc, 0xa4, 0x52, 0xb2, 0x8c, - 0xdc, 0x33, 0x77, 0x9f, 0x95, 0x85, 0x77, 0xda, 0xba, 0x6b, 0x75, 0x3f, 0x3a, 0xaa, 0x0b, 0xd3, - 0x3a, 0xc7, 0x33, 0xd4, 0x07, 0x26, 0x13, 0x96, 0x93, 0xee, 0xd0, 0x19, 0x3d, 0x9e, 0x8c, 0xef, - 0x0a, 0xef, 0x9c, 0xa7, 0x7a, 0xb9, 0x5e, 0x04, 0xb1, 0x12, 0x61, 0xac, 0x40, 0x28, 0xb0, 0xe1, - 0x1c, 0x92, 0xaf, 0xf5, 0xe3, 0x83, 0x8b, 0x38, 0xbe, 0x48, 0x92, 0x9c, 0x01, 0x44, 0xb6, 0x41, - 0x35, 0x8c, 0x4e, 0x05, 0x53, 0x6b, 0x7d, 0xb9, 0x64, 0x29, 0x5f, 0x6a, 0xd2, 0x1b, 0x3a, 0xa3, - 0x5e, 0x73, 0x98, 0xb6, 0xee, 0x47, 0x47, 0xb6, 0xf0, 0xce, 0xe4, 0x78, 0x86, 0x9e, 0xec, 0x89, - 0x2a, 0x82, 0xa6, 0x62, 0x45, 0xee, 0x9b, 0x26, 0x67, 0x65, 0xe1, 0x91, 0x76, 0x93, 0x03, 0xe2, - 0x47, 0x27, 0xb6, 0xf6, 0x71, 0x5f, 0xc2, 0x01, 0xea, 0x0a, 0xe0, 0xa4, 0x6f, 0x1e, 0x75, 0x76, - 0x57, 0x78, 0x84, 0xc9, 0x58, 0x25, 0xa9, 0xe4, 0xe1, 0x17, 0x50, 0x32, 0x88, 0xe8, 0xd5, 0x9c, - 0x01, 0x50, 0xce, 0xa2, 0x0a, 0xf4, 0x7f, 0x3a, 0x08, 0xcf, 0x81, 0x57, 0x8e, 0x64, 0x0a, 0x0e, - 0xdf, 0x33, 0x42, 0xbd, 0x86, 0x25, 0x83, 0xb2, 0xf0, 0x4e, 0xea, 0x21, 0x12, 0x06, 0xda, 0x1a, - 0x62, 0x08, 0xfc, 0x12, 0x3d, 0x68, 0x7b, 0x40, 0xca, 0xc2, 0x1b, 0x1c, 0x60, 0xa6, 0xff, 0x59, - 0xb0, 0x07, 0xff, 0xe3, 0xe7, 0x4f, 0xde, 0xdf, 0xfc, 0x71, 0x3b, 0x37, 0x5b, 0xd7, 0xb9, 0xdd, - 0xba, 0xce, 0xef, 0xad, 0xeb, 0x7c, 0xdf, 0xb9, 0x9d, 0xdb, 0x9d, 0xdb, 0xf9, 0xb5, 0x73, 0x3b, - 0x9f, 0x5f, 0x34, 0x9a, 0x4e, 0x15, 0x88, 0x6a, 0xf9, 0xcc, 0x4e, 0x27, 0xe1, 0xc6, 0xc6, 0xf6, - 0x72, 0x2f, 0xfa, 0x66, 0x59, 0x5f, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x73, 0x7d, 0x0e, 0xa8, - 0xfc, 0x02, 0x00, 0x00, + // 340 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x91, 0x31, 0x4f, 0xfa, 0x40, + 0x18, 0xc6, 0x5b, 0xc2, 0x9f, 0x7f, 0xbc, 0xa8, 0xd1, 0x46, 0x92, 0x6a, 0xc8, 0x95, 0x74, 0x62, + 0xea, 0x41, 0xd8, 0x9c, 0x4c, 0xbb, 0xc8, 0x80, 0x43, 0x35, 0x31, 0x71, 0x21, 0xd7, 0xf6, 0xcd, + 0xb5, 0xa6, 0xbd, 0x23, 0xdc, 0x21, 0xb0, 0xf9, 0x11, 0xfc, 0x58, 0x8c, 0x8c, 0x4e, 0x44, 0xe1, + 0x1b, 0x30, 0x3a, 0x99, 0x96, 0x62, 0xec, 0xea, 0xf4, 0xdc, 0x3d, 0xcf, 0xef, 0xde, 0xe4, 0xde, + 0x07, 0x59, 0x73, 0x32, 0xa3, 0x32, 0x23, 0x09, 0x57, 0x30, 0xe1, 0x34, 0x25, 0x6a, 0x31, 0x06, + 0x49, 0x92, 0x20, 0x74, 0xc6, 0x13, 0xa1, 0x84, 0xd1, 0xcc, 0xe3, 0xc8, 0x99, 0x3b, 0x7b, 0x7d, + 0xe9, 0x05, 0xa0, 0x68, 0xef, 0xea, 0x82, 0x09, 0x26, 0x0a, 0x82, 0xe4, 0xa7, 0x3d, 0x6c, 0xbf, + 0xd6, 0x10, 0x1a, 0x4a, 0x36, 0x70, 0xbd, 0x7b, 0xe0, 0x91, 0xd1, 0x47, 0xff, 0xc3, 0x98, 0x72, + 0x0e, 0xa9, 0x59, 0x6b, 0xeb, 0x9d, 0x23, 0xf7, 0x72, 0xb7, 0xb6, 0x9a, 0x0b, 0x9a, 0xa5, 0xd7, + 0xb6, 0x14, 0xd3, 0x49, 0x08, 0xa3, 0x32, 0xb7, 0xfd, 0x03, 0x69, 0xdc, 0xa0, 0x53, 0x95, 0x64, + 0x20, 0xa6, 0x6a, 0x14, 0x43, 0xc2, 0x62, 0x65, 0xd6, 0xdb, 0x7a, 0xa7, 0xfe, 0xfb, 0x6d, 0x35, + 0xb7, 0xfd, 0x93, 0xd2, 0xb8, 0x2d, 0xee, 0xc6, 0x00, 0x9d, 0x1f, 0x88, 0x5c, 0xa5, 0xa2, 0xd9, + 0xd8, 0xfc, 0x57, 0x0c, 0x69, 0xed, 0xd6, 0x96, 0x59, 0x1d, 0xf2, 0x83, 0xd8, 0xfe, 0x59, 0xe9, + 0x3d, 0x1c, 0x2c, 0xa3, 0x8b, 0xea, 0x11, 0x55, 0xd4, 0x6c, 0xb4, 0xf5, 0xce, 0xb1, 0xdb, 0xfa, + 0x5a, 0x5b, 0x26, 0xf0, 0x50, 0x44, 0x09, 0x67, 0xe4, 0x59, 0x0a, 0xee, 0xf8, 0x74, 0x36, 0x04, + 0x29, 0x29, 0x03, 0xbf, 0x20, 0xed, 0x01, 0x32, 0xf6, 0x1b, 0xf0, 0x52, 0x21, 0xc1, 0x2b, 0x3f, + 0xf5, 0x97, 0x4d, 0xb8, 0x77, 0xcb, 0x4f, 0xac, 0x2d, 0x37, 0x58, 0x5f, 0x6d, 0xb0, 0xfe, 0xb1, + 0xc1, 0xfa, 0xdb, 0x16, 0x6b, 0xab, 0x2d, 0xd6, 0xde, 0xb7, 0x58, 0x7b, 0xea, 0xb2, 0x44, 0xc5, + 0xd3, 0xc0, 0x09, 0x45, 0x46, 0x3c, 0x21, 0xb3, 0xc7, 0xbc, 0xc6, 0xa2, 0x24, 0x32, 0x2f, 0xb5, + 0x5a, 0x6a, 0xd0, 0x28, 0x4a, 0xea, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x80, 0xeb, 0x87, 0xea, + 0xf4, 0x01, 0x00, 0x00, } -func (m *MsgWasmIBCCall) Marshal() (dAtA []byte, err error) { +func (m *MsgIBCSend) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -164,20 +151,20 @@ func (m *MsgWasmIBCCall) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgWasmIBCCall) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgIBCSend) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgWasmIBCCall) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgIBCSend) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if len(m.Msg) > 0 { - i -= len(m.Msg) - copy(dAtA[i:], m.Msg) - i = encodeVarintIbc(dAtA, i, uint64(len(m.Msg))) + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintIbc(dAtA, i, uint64(len(m.Data))) i-- dAtA[i] = 0x32 } @@ -191,27 +178,13 @@ func (m *MsgWasmIBCCall) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x20 } - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintIbc(dAtA, i, uint64(len(m.Sender))) - i-- - dAtA[i] = 0x1a - } - if len(m.SourceChannel) > 0 { - i -= len(m.SourceChannel) - copy(dAtA[i:], m.SourceChannel) - i = encodeVarintIbc(dAtA, i, uint64(len(m.SourceChannel))) + if len(m.Channel) > 0 { + i -= len(m.Channel) + copy(dAtA[i:], m.Channel) + i = encodeVarintIbc(dAtA, i, uint64(len(m.Channel))) i-- dAtA[i] = 0x12 } - if len(m.SourcePort) > 0 { - i -= len(m.SourcePort) - copy(dAtA[i:], m.SourcePort) - i = encodeVarintIbc(dAtA, i, uint64(len(m.SourcePort))) - i-- - dAtA[i] = 0xa - } return len(dAtA) - i, nil } @@ -235,13 +208,6 @@ func (m *MsgIBCCloseChannel) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintIbc(dAtA, i, uint64(len(m.Sender))) - i-- - dAtA[i] = 0x1a - } if len(m.Channel) > 0 { i -= len(m.Channel) copy(dAtA[i:], m.Channel) @@ -249,13 +215,6 @@ func (m *MsgIBCCloseChannel) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.Port) > 0 { - i -= len(m.Port) - copy(dAtA[i:], m.Port) - i = encodeVarintIbc(dAtA, i, uint64(len(m.Port))) - i-- - dAtA[i] = 0xa - } return len(dAtA) - i, nil } @@ -270,21 +229,13 @@ func encodeVarintIbc(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *MsgWasmIBCCall) Size() (n int) { +func (m *MsgIBCSend) Size() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.SourcePort) - if l > 0 { - n += 1 + l + sovIbc(uint64(l)) - } - l = len(m.SourceChannel) - if l > 0 { - n += 1 + l + sovIbc(uint64(l)) - } - l = len(m.Sender) + l = len(m.Channel) if l > 0 { n += 1 + l + sovIbc(uint64(l)) } @@ -294,7 +245,7 @@ func (m *MsgWasmIBCCall) Size() (n int) { if m.TimeoutTimestamp != 0 { n += 1 + sovIbc(uint64(m.TimeoutTimestamp)) } - l = len(m.Msg) + l = len(m.Data) if l > 0 { n += 1 + l + sovIbc(uint64(l)) } @@ -307,18 +258,10 @@ func (m *MsgIBCCloseChannel) Size() (n int) { } var l int _ = l - l = len(m.Port) - if l > 0 { - n += 1 + l + sovIbc(uint64(l)) - } l = len(m.Channel) if l > 0 { n += 1 + l + sovIbc(uint64(l)) } - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovIbc(uint64(l)) - } return n } @@ -328,7 +271,7 @@ func sovIbc(x uint64) (n int) { func sozIbc(x uint64) (n int) { return sovIbc(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *MsgWasmIBCCall) Unmarshal(dAtA []byte) error { +func (m *MsgIBCSend) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -351,47 +294,15 @@ func (m *MsgWasmIBCCall) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgWasmIBCCall: wiretype end group for non-group") + return fmt.Errorf("proto: MsgIBCSend: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgWasmIBCCall: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgIBCSend: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SourcePort", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowIbc - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthIbc - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthIbc - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.SourcePort = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SourceChannel", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -419,41 +330,7 @@ func (m *MsgWasmIBCCall) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.SourceChannel = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowIbc - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthIbc - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthIbc - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Sender = append(m.Sender[:0], dAtA[iNdEx:postIndex]...) - if m.Sender == nil { - m.Sender = []byte{} - } + m.Channel = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 0 { @@ -495,7 +372,7 @@ func (m *MsgWasmIBCCall) Unmarshal(dAtA []byte) error { } case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Msg", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -522,9 +399,9 @@ func (m *MsgWasmIBCCall) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Msg = append(m.Msg[:0], dAtA[iNdEx:postIndex]...) - if m.Msg == nil { - m.Msg = []byte{} + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} } iNdEx = postIndex default: @@ -580,38 +457,6 @@ func (m *MsgIBCCloseChannel) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: MsgIBCCloseChannel: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowIbc - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthIbc - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthIbc - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Port = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) @@ -644,40 +489,6 @@ func (m *MsgIBCCloseChannel) Unmarshal(dAtA []byte) error { } m.Channel = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowIbc - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthIbc - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthIbc - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Sender = append(m.Sender[:0], dAtA[iNdEx:postIndex]...) - if m.Sender == nil { - m.Sender = []byte{} - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipIbc(dAtA[iNdEx:]) diff --git a/x/wasm/internal/types/ibc.proto b/x/wasm/internal/types/ibc.proto index d63f1c5238..96afc470b0 100644 --- a/x/wasm/internal/types/ibc.proto +++ b/x/wasm/internal/types/ibc.proto @@ -6,13 +6,9 @@ import "gogoproto/gogo.proto"; option go_package = "github.com/CosmWasm/wasmd/x/wasmd/internal/types"; option (gogoproto.goproto_getters_all) = false; -message MsgWasmIBCCall { - // the port on which the packet will be sent - string source_port = 1 [(gogoproto.moretags) = "yaml:\"source_port\""]; +message MsgIBCSend { // the channel by which the packet will be sent - string source_channel = 2 [(gogoproto.moretags) = "yaml:\"source_channel\""]; - - bytes sender = 3 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + string channel = 2 [(gogoproto.moretags) = "yaml:\"source_channel\""]; // Timeout height relative to the current block height. // The timeout is disabled when set to 0. @@ -21,14 +17,11 @@ message MsgWasmIBCCall { // The timeout is disabled when set to 0. uint64 timeout_timestamp = 5 [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""]; - // Msg is the message to the contract - bytes msg = 6 [(gogoproto.casttype) = "encoding/json.RawMessage"]; + // data is the payload to transfer + bytes data = 6 [(gogoproto.casttype) = "encoding/json.RawMessage"]; } // MsgIBCCloseChannel port and channel need to be owned by the contract message MsgIBCCloseChannel { - string port = 1 [(gogoproto.moretags) = "yaml:\"dest_port\""]; - string channel = 2 [(gogoproto.moretags) = "yaml:\"deset_channel\""]; - - bytes sender = 3 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + string channel = 2 [(gogoproto.moretags) = "yaml:\"source_channel\""]; } diff --git a/x/wasm/internal/types/msg.go b/x/wasm/internal/types/msg.go index e7d4c206e1..33995d7bd4 100644 --- a/x/wasm/internal/types/msg.go +++ b/x/wasm/internal/types/msg.go @@ -216,27 +216,23 @@ func (msg MsgClearAdmin) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} } -func (msg MsgWasmIBCCall) Route() string { +func (msg MsgIBCSend) Route() string { return RouterKey } -func (msg MsgWasmIBCCall) Type() string { - return "wasm-ibc-call" +func (msg MsgIBCSend) Type() string { + return "wasm-ibc-send" } -func (msg MsgWasmIBCCall) ValidateBasic() error { - if err := sdk.VerifyAddressFormat(msg.Sender); err != nil { - return sdkerrors.Wrap(err, "sender") - } - +func (msg MsgIBCSend) ValidateBasic() error { return nil } -func (msg MsgWasmIBCCall) GetSignBytes() []byte { +func (msg MsgIBCSend) GetSignBytes() []byte { return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } -func (msg MsgWasmIBCCall) GetSigners() []sdk.AccAddress { +func (msg MsgIBCSend) GetSigners() []sdk.AccAddress { return nil } @@ -249,10 +245,6 @@ func (msg MsgIBCCloseChannel) Type() string { } func (msg MsgIBCCloseChannel) ValidateBasic() error { - if err := sdk.VerifyAddressFormat(msg.Sender); err != nil { - return sdkerrors.Wrap(err, "sender") - } - return nil } diff --git a/x/wasm/relay_pingpong_test.go b/x/wasm/relay_pingpong_test.go new file mode 100644 index 0000000000..b3830643dc --- /dev/null +++ b/x/wasm/relay_pingpong_test.go @@ -0,0 +1,381 @@ +package wasm_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/CosmWasm/go-cosmwasm" + wasmTypes "github.com/CosmWasm/go-cosmwasm/types" + "github.com/CosmWasm/wasmd/x/wasm" + "github.com/CosmWasm/wasmd/x/wasm/ibc_testing" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/internal/keeper" + cosmwasmv2 "github.com/CosmWasm/wasmd/x/wasm/internal/keeper/cosmwasm" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported" + channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" + host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + ping = "ping" + pong = "pong" +) +const doNotTimeout uint64 = 110000 + +func TestPinPong(t *testing.T) { + var ( + coordinator = ibc_testing.NewCoordinator(t, 2) + chainA = coordinator.GetChain(ibc_testing.GetChainID(0)) + chainB = coordinator.GetChain(ibc_testing.GetChainID(1)) + ) + _ = chainB.NewRandomContractInstance() // skip 1 id + var ( + pingContractAddr = chainA.NewRandomContractInstance() + pongContractAddr = chainB.NewRandomContractInstance() + ) + require.NotEqual(t, pingContractAddr, pongContractAddr) + + pingContract := &player{t: t, actor: ping, chain: chainA, contractAddr: pingContractAddr} + pongContract := &player{t: t, actor: pong, chain: chainB, contractAddr: pongContractAddr} + + wasmkeeper.MockContracts[pingContractAddr.String()] = pingContract + wasmkeeper.MockContracts[pongContractAddr.String()] = pongContract + + var ( + sourcePortID = wasmkeeper.PortIDForContract(pingContractAddr) + counterpartyPortID = wasmkeeper.PortIDForContract(pongContractAddr) + ) + clientA, clientB, connA, connB := coordinator.SetupClientConnections(chainA, chainB, clientexported.Tendermint) + connA.NextChannelVersion = ping + connB.NextChannelVersion = pong + + channelA, channelB := coordinator.CreateChannel(chainA, chainB, connA, connB, sourcePortID, counterpartyPortID, channeltypes.UNORDERED) + var err error + + const startValue uint64 = 100 + const rounds = 3 + s := startGame{ + ChannelID: channelA.ID, + Value: startValue, + } + startMsg := &wasm.MsgExecuteContract{ + Sender: chainA.SenderAccount.GetAddress(), + Contract: pingContractAddr, + Msg: s.GetBytes(), + } + // send from chainA to chainB + err = coordinator.SendMsgs(chainA, chainB, clientB, startMsg) + require.NoError(t, err) + + t.Log("Duplicate messages are due to check/deliver tx calls") + + var ( + activePlayer = ping + pingBallValue = startValue + ) + for i := 1; i <= rounds; i++ { + t.Logf("++ round: %d\n", i) + ball := NewHit(activePlayer, pingBallValue) + + seq := uint64(i) + pkg := channeltypes.NewPacket(ball.GetBytes(), seq, channelA.PortID, channelA.ID, channelB.PortID, channelB.ID, doNotTimeout, 0) + ack := ball.BuildAck() + + err = coordinator.RelayPacket(chainA, chainB, clientA, clientB, pkg, ack.GetBytes()) + require.NoError(t, err) + //coordinator.CommitBlock(chainA, chainB) + err = coordinator.UpdateClient(chainA, chainB, clientA, clientexported.Tendermint) + require.NoError(t, err) + + // switch side + activePlayer = counterParty(activePlayer) + ball = NewHit(activePlayer, uint64(i)) + pkg = channeltypes.NewPacket(ball.GetBytes(), seq, channelB.PortID, channelB.ID, channelA.PortID, channelA.ID, doNotTimeout, 0) + ack = ball.BuildAck() + + err = coordinator.RelayPacket(chainB, chainA, clientB, clientA, pkg, ack.GetBytes()) + require.NoError(t, err) + err = coordinator.UpdateClient(chainB, chainA, clientB, clientexported.Tendermint) + require.NoError(t, err) + + // switch side for next round + activePlayer = counterParty(activePlayer) + pingBallValue++ + } + assert.Equal(t, startValue+rounds, pingContract.QueryState(lastBallSentKey)) + assert.Equal(t, uint64(rounds), pingContract.QueryState(lastBallReceivedKey)) + assert.Equal(t, uint64(rounds+1), pingContract.QueryState(sentBallsCountKey)) + assert.Equal(t, uint64(rounds), pingContract.QueryState(receivedBallsCountKey)) + assert.Equal(t, uint64(rounds), pingContract.QueryState(confirmedBallsCountKey)) + + assert.Equal(t, uint64(rounds), pongContract.QueryState(lastBallSentKey)) + assert.Equal(t, startValue+rounds-1, pongContract.QueryState(lastBallReceivedKey)) + assert.Equal(t, uint64(rounds), pongContract.QueryState(sentBallsCountKey)) + assert.Equal(t, uint64(rounds), pongContract.QueryState(receivedBallsCountKey)) + assert.Equal(t, uint64(rounds), pongContract.QueryState(confirmedBallsCountKey)) + +} + +// hit is ibc packet payload +type hit map[string]uint64 + +func NewHit(player string, count uint64) hit { + return map[string]uint64{ + player: count, + } +} +func (h hit) GetBytes() []byte { + b, err := json.Marshal(h) + if err != nil { + panic(err) + } + return b +} +func (h hit) String() string { + return fmt.Sprintf("Ball %s", string(h.GetBytes())) +} + +func (h hit) BuildAck() hitAcknowledgement { + return hitAcknowledgement{Success: &h} +} + +func (h hit) BuildError(errMsg string) hitAcknowledgement { + return hitAcknowledgement{Error: errMsg} +} + +// hitAcknowledgement is ibc acknowledgment payload +type hitAcknowledgement struct { + Error string `json:"error,omitempty"` + Success *hit `json:"success,omitempty"` +} + +func (a hitAcknowledgement) GetBytes() []byte { + b, err := json.Marshal(a) + if err != nil { + panic(err) + } + return b +} + +// startGame is an execute message payload +type startGame struct { + ChannelID string + Value uint64 + // limit above the game is aborted + MaxValue uint64 `json:"max_value,omitempty"` +} + +func (g startGame) GetBytes() json.RawMessage { + b, err := json.Marshal(g) + if err != nil { + panic(err) + } + return b +} + +// player is a (mock) contract that sends and receives ibc packages +type player struct { + t *testing.T + chain *ibc_testing.TestChain + contractAddr sdk.AccAddress + actor string // either ping or pong + execCalls int // number of calls to Execute method (checkTx + deliverTx) +} + +// Execute starts the ping pong game +func (p *player) Execute(hash []byte, params wasmTypes.Env, data []byte, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.HandleResponse, uint64, error) { + p.execCalls++ + if p.execCalls%2 == 1 { // skip checkTx step because of no rollback with `chain.GetContext()` + return &cosmwasmv2.HandleResponse{}, 0, nil + } + // start game + var start startGame + if err := json.Unmarshal(data, &start); err != nil { + return nil, 0, err + } + + if start.MaxValue != 0 { + store.Set(maxValueKey, sdk.Uint64ToBigEndian(start.MaxValue)) + } + endpoints := p.loadEndpoints(store, start.ChannelID) + ctx := p.chain.GetContext() + channelCap, ok := p.chain.App.WasmKeeper.ScopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(endpoints.Our.Port, endpoints.Our.Channel)) + if !ok { + return nil, 0, sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") + } + + service := NewHit(p.actor, start.Value) + p.t.Logf("[%s] starting game with: %d: %v\n", p.actor, start.Value, service) + + var seq uint64 = 1 + packet := channeltypes.NewPacket(service.GetBytes(), seq, endpoints.Our.Port, endpoints.Our.Channel, endpoints.Their.Port, endpoints.Their.Channel, doNotTimeout, 0) + err := p.chain.App.WasmKeeper.ChannelKeeper.SendPacket(ctx, channelCap, packet) + if err != nil { + return nil, 0, err + } + + p.incrementCounter(sentBallsCountKey, store) + store.Set(lastBallSentKey, sdk.Uint64ToBigEndian(start.Value)) + return &cosmwasmv2.HandleResponse{}, 0, nil +} + +// OnIBCChannelOpen ensures to accept only configured version +func (p player) OnIBCChannelOpen(hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelOpenResponse, uint64, error) { + if channel.Version != p.actor { + return &cosmwasmv2.IBCChannelOpenResponse{Success: false, Reason: fmt.Sprintf("expected %q but got %q", p.actor, channel.Version)}, 0, nil + } + return &cosmwasmv2.IBCChannelOpenResponse{Success: true}, 0, nil +} + +// OnIBCChannelConnect persists connection endpoints +func (p player) OnIBCChannelConnect(hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelConnectResponse, uint64, error) { + p.storeEndpoint(store, channel) + return &cosmwasmv2.IBCChannelConnectResponse{}, 0, nil +} + +// connectedChannelsModel is a simple persistence model to store endpoint addresses within the contract's store +type connectedChannelsModel struct { + Our cosmwasmv2.IBCEndpoint + Their cosmwasmv2.IBCEndpoint +} + +var ( // store keys + ibcEndpointsKey = []byte("ibc-endpoints") + maxValueKey = []byte("max-value") +) + +func (p player) loadEndpoints(store prefix.Store, channelID string) *connectedChannelsModel { + var counterparties []connectedChannelsModel + if bz := store.Get(ibcEndpointsKey); bz != nil { + require.NoError(p.t, json.Unmarshal(bz, &counterparties)) + } + for _, v := range counterparties { + if v.Our.Channel == channelID { + return &v + } + } + p.t.Fatalf("no counterparty found for channel %q", channelID) + return nil +} + +func (p player) storeEndpoint(store prefix.Store, channel cosmwasmv2.IBCChannel) { + var counterparties []connectedChannelsModel + if b := store.Get(ibcEndpointsKey); b != nil { + require.NoError(p.t, json.Unmarshal(b, &counterparties)) + } + counterparties = append(counterparties, connectedChannelsModel{Our: channel.Endpoint, Their: channel.CounterpartyEndpoint}) + bz, err := json.Marshal(&counterparties) + require.NoError(p.t, err) + store.Set(ibcEndpointsKey, bz) +} + +func (p player) OnIBCChannelClose(ctx sdk.Context, hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelCloseResponse, uint64, error) { + panic("implement me") +} + +var ( // store keys + lastBallSentKey = []byte("lastBallSent") + lastBallReceivedKey = []byte("lastBallReceived") + sentBallsCountKey = []byte("sentBalls") + receivedBallsCountKey = []byte("recvBalls") + confirmedBallsCountKey = []byte("confBalls") +) + +// OnIBCPacketReceive receives the hit and serves a response hit via `cosmwasmv2.IBCMsg` +func (p player) OnIBCPacketReceive(hash []byte, params cosmwasmv2.Env, packet cosmwasmv2.IBCPacket, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketReceiveResponse, uint64, error) { + // parse received data and store + var receivedBall hit + if err := json.Unmarshal(packet.Data, &receivedBall); err != nil { + return &cosmwasmv2.IBCPacketReceiveResponse{ + Acknowledgement: hitAcknowledgement{Error: err.Error()}.GetBytes(), + // no hit msg, we stop the game + }, 0, nil + } + p.incrementCounter(receivedBallsCountKey, store) + + otherCount := receivedBall[counterParty(p.actor)] + store.Set(lastBallReceivedKey, sdk.Uint64ToBigEndian(otherCount)) + + if maxVal := store.Get(maxValueKey); maxVal != nil && otherCount > sdk.BigEndianToUint64(maxVal) { + errMsg := fmt.Sprintf("max value exceeded: %d got %d", sdk.BigEndianToUint64(maxVal), otherCount) + return &cosmwasmv2.IBCPacketReceiveResponse{ + Acknowledgement: receivedBall.BuildError(errMsg).GetBytes(), + }, 0, nil + } + + nextValue := p.incrementCounter(lastBallSentKey, store) + newHit := NewHit(p.actor, nextValue) + respHit := &cosmwasmv2.IBCMsg{SendPacket: &cosmwasmv2.IBCSendMsg{ + ChannelID: packet.Source.Channel, + Data: newHit.GetBytes(), + TimeoutHeight: doNotTimeout, + }} + p.incrementCounter(sentBallsCountKey, store) + p.t.Logf("[%s] received %d, returning %d: %v\n", p.actor, otherCount, nextValue, newHit) + + return &cosmwasmv2.IBCPacketReceiveResponse{ + Acknowledgement: receivedBall.BuildAck().GetBytes(), + Messages: []cosmwasmv2.CosmosMsg{{IBC: respHit}}, + }, 0, nil +} + +// OnIBCPacketAcknowledgement handles the packet acknowledgment frame. Stops the game on an any error +func (p player) OnIBCPacketAcknowledgement(hash []byte, params cosmwasmv2.Env, packetAck cosmwasmv2.IBCAcknowledgement, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketAcknowledgementResponse, uint64, error) { + // parse received data and store + var sentBall hit + if err := json.Unmarshal(packetAck.OriginalPacket.Data, &sentBall); err != nil { + return nil, 0, err + } + + var ack hitAcknowledgement + if err := json.Unmarshal(packetAck.Acknowledgement, &ack); err != nil { + return nil, 0, err + } + if ack.Success != nil { + confirmedCount := sentBall[p.actor] + p.t.Logf("[%s] acknowledged %d: %v\n", p.actor, confirmedCount, sentBall) + } else { + p.t.Logf("[%s] received app layer error: %s\n", p.actor, ack.Error) + + } + + p.incrementCounter(confirmedBallsCountKey, store) + return &cosmwasmv2.IBCPacketAcknowledgementResponse{}, 0, nil +} + +func (p player) OnIBCPacketTimeout(hash []byte, params cosmwasmv2.Env, packet cosmwasmv2.IBCPacket, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketTimeoutResponse, uint64, error) { + panic("implement me") +} + +func (p player) incrementCounter(key []byte, store prefix.Store) uint64 { + var count uint64 + bz := store.Get(key) + if bz != nil { + count = sdk.BigEndianToUint64(bz) + } + count++ + store.Set(key, sdk.Uint64ToBigEndian(count)) + return count +} + +func (p player) QueryState(key []byte) uint64 { + models := p.chain.App.WasmKeeper.QueryRaw(p.chain.GetContext(), p.contractAddr, key) + require.Len(p.t, models, 1) + return sdk.BigEndianToUint64(models[0].Value) +} + +func counterParty(s string) string { + switch s { + case ping: + return pong + case pong: + return ping + default: + panic(fmt.Sprintf("unsupported: %q", s)) + } +} diff --git a/x/wasm/relay_test.go b/x/wasm/relay_test.go index d3a275e4a0..0e2f07f2e9 100644 --- a/x/wasm/relay_test.go +++ b/x/wasm/relay_test.go @@ -3,8 +3,8 @@ package wasm_test import ( "testing" - cosmwasmv1 "github.com/CosmWasm/go-cosmwasm" - wasmTypes "github.com/CosmWasm/go-cosmwasm/types" + "github.com/CosmWasm/go-cosmwasm" + cosmwasmv1 "github.com/CosmWasm/go-cosmwasm/types" "github.com/CosmWasm/wasmd/x/wasm/ibc_testing" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/internal/keeper" cosmwasmv2 "github.com/CosmWasm/wasmd/x/wasm/internal/keeper/cosmwasm" @@ -53,11 +53,10 @@ func TestFromIBCTransferToContract(t *testing.T) { ack := ibctransfertypes.FungibleTokenPacketAcknowledgement{Success: true}.GetBytes() err = coordinator.AcknowledgePacket(chainA, chainB, clientB, packet, ack) // sent to chainA - //err = coordinator.RelayPacket(chainA, chainB, clientA, clientB, packet, ack) require.NoError(t, err) newBalance := chainA.App.BankKeeper.GetBalance(chainA.GetContext(), chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) assert.Equal(t, originalBalance.Sub(coinToSendToB), newBalance) - const ibcVoucherTicker = "ibc/310F9D708E5AA2F54CA83BC04C2E56F1EA62DB6FBDA321B337867CF5BEECF531" + const ibcVoucherTicker = "ibc/1AAD10C9C252ACF464C7167E328C866BBDA0BDED3D89EFAB7B7C30BF01DE4657" chainBBalance := chainB.App.BankKeeper.GetBalance(chainB.GetContext(), chainB.SenderAccount.GetAddress(), ibcVoucherTicker) // note: the contract is called during check and deliverTX but the context used in the contract does not rollback // so that we got twice the amount @@ -82,59 +81,84 @@ func TestContractCanInitiateIBCTransfer(t *testing.T) { var ( sourcePortID = contractAPortID counterpartPortID = "transfer" + ibcVoucherTicker = "ibc/8D5B148875A26426899137B476C646A94652D73BAEEE3CD30B9C261EB7BC0E1B" ) clientA, clientB, connA, connB := coordinator.SetupClientConnections(chainA, chainB, clientexported.Tendermint) - // a channel for transfer to transfer - transChanA, transChanB := coordinator.CreateTransferChannels(chainA, chainB, connA, connB, channeltypes.UNORDERED) - myContract.transferChannelID = transChanA.ID + channelA, channelB := coordinator.CreateChannel(chainA, chainB, connA, connB, sourcePortID, counterpartPortID, channeltypes.UNORDERED) - originalBalance := chainA.App.BankKeeper.GetBalance(chainA.GetContext(), myContractAddr, sdk.DefaultBondDenom) - require.Equal(t, ibc_testing.TestCoin, originalBalance, "exp %q but got %q", ibc_testing.TestCoin, originalBalance) + // send to chain B + err := coordinator.UpdateClient(chainB, chainA, clientB, clientexported.Tendermint) + require.NoError(t, err) - // a channel for contranct to transfer - channelA, channelB := coordinator.CreateChannel(chainA, chainB, connA, connB, sourcePortID, counterpartPortID, channeltypes.UNORDERED) + packet := channeltypes.NewPacket(myContract.packetSent.GetBytes(), 1, channelA.PortID, channelA.ID, channelB.PortID, channelB.ID, 110, 0) + err = coordinator.RecvPacket(chainA, chainB, clientA, packet) //sent to chainB + require.NoError(t, err) + + // send Ack to chain A + ack := ibctransfertypes.FungibleTokenPacketAcknowledgement{Success: true}.GetBytes() + err = coordinator.AcknowledgePacket(chainA, chainB, clientB, packet, ack) // sent to chainA + require.NoError(t, err) - _ = transChanB - _, _, _ = clientA, channelB, originalBalance - _ = clientB - _ = channelA - newBalance := chainA.App.BankKeeper.GetBalance(chainA.GetContext(), myContractAddr, sdk.DefaultBondDenom) - assert.Equal(t, originalBalance.Sub(coinToSendToB).String(), newBalance.String()) + newBalance := chainB.App.BankKeeper.GetBalance(chainB.GetContext(), chainB.SenderAccount.GetAddress(), ibcVoucherTicker) + assert.Equal(t, sdk.NewCoin(ibcVoucherTicker, coinToSendToB.Amount).String(), newBalance.String(), chainB.App.BankKeeper.GetAllBalances(chainB.GetContext(), chainB.SenderAccount.GetAddress())) } type senderContract struct { - t *testing.T - contractAddr sdk.AccAddress - chain *ibc_testing.TestChain - receiverAddr sdk.AccAddress - coinsToSend sdk.Coin - transferChannelID string + t *testing.T + contractAddr sdk.AccAddress + chain *ibc_testing.TestChain + receiverAddr sdk.AccAddress + coinsToSend sdk.Coin + packetSent *ibctransfertypes.FungibleTokenPacketData } -func (s *senderContract) OnIBCChannelOpen(hash []byte, params cosmwasmv2.Env, order channeltypes.Order, version string, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelOpenResponse, uint64, error) { - return &cosmwasmv2.IBCChannelOpenResponse{Result: true}, 0, nil +func (s *senderContract) OnIBCChannelOpen(hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelOpenResponse, uint64, error) { + return &cosmwasmv2.IBCChannelOpenResponse{Success: true}, 0, nil } -func (s *senderContract) OnIBCChannelConnect(hash []byte, params cosmwasmv2.Env, counterpartyPortID string, counterpartyChannelID string, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelConnectResponse, uint64, error) { +func (s *senderContract) OnIBCChannelConnect(hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelConnectResponse, uint64, error) { // abusing onConnect event to send the message. can be any execute event which is not mocked though - //todo: better demo would be to use querier to find the transfer port - msg := ibctransfertypes.NewMsgTransfer("transfer", s.transferChannelID, s.coinsToSend, s.contractAddr, s.receiverAddr.String(), 110, 0) - return &cosmwasmv2.IBCChannelConnectResponse{Messages: []sdk.Msg{msg}}, 0, nil + escrowAddress := ibctransfertypes.GetEscrowAddress(channel.Endpoint.Port, channel.Endpoint.Channel) + sendToEscrowMsg := &cosmwasmv1.BankMsg{ + Send: &cosmwasmv1.SendMsg{ + FromAddress: s.contractAddr.String(), + ToAddress: escrowAddress.String(), + Amount: cosmwasmv1.Coins{cosmwasmv1.NewCoin(s.coinsToSend.Amount.Uint64(), s.coinsToSend.Denom)}, + }} + + dataPacket := ibctransfertypes.NewFungibleTokenPacketData( + s.coinsToSend.Denom, s.coinsToSend.Amount.Uint64(), s.contractAddr.String(), s.receiverAddr.String(), + ) + s.packetSent = &dataPacket + ibcPacket := &cosmwasmv2.IBCMsg{ + SendPacket: &cosmwasmv2.IBCSendMsg{ + ChannelID: channel.Endpoint.Channel, + Data: dataPacket.GetBytes(), + TimeoutHeight: 110, + TimeoutTimestamp: 0, + }, + } + return &cosmwasmv2.IBCChannelConnectResponse{Messages: []cosmwasmv2.CosmosMsg{{Bank: sendToEscrowMsg}, {IBC: ibcPacket}}}, 0, nil } -func (s *senderContract) OnIBCPacketReceive(hash []byte, params cosmwasmv2.Env, msg []byte, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketReceiveResponse, uint64, error) { +func (s *senderContract) OnIBCChannelClose(ctx sdk.Context, hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelCloseResponse, uint64, error) { panic("implement me") } -func (s *senderContract) OnIBCPacketAcknowledgement(hash []byte, params cosmwasmv2.Env, originalData []byte, acknowledgement []byte, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketAcknowledgementResponse, uint64, error) { +func (s *senderContract) OnIBCPacketReceive(hash []byte, params cosmwasmv2.Env, packet cosmwasmv2.IBCPacket, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketReceiveResponse, uint64, error) { panic("implement me") } -func (s *senderContract) OnIBCPacketTimeout(hash []byte, params cosmwasmv2.Env, msg []byte, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketTimeoutResponse, uint64, error) { +func (s *senderContract) OnIBCPacketAcknowledgement(hash []byte, params cosmwasmv2.Env, packetAck cosmwasmv2.IBCAcknowledgement, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketAcknowledgementResponse, uint64, error) { + return &cosmwasmv2.IBCPacketAcknowledgementResponse{}, 0, nil +} + +func (s *senderContract) OnIBCPacketTimeout(hash []byte, params cosmwasmv2.Env, packet cosmwasmv2.IBCPacket, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketTimeoutResponse, uint64, error) { + // return from escrow panic("implement me") } -func (s *senderContract) OnIBCChannelClose(ctx sdk.Context, hash []byte, params cosmwasmv2.Env, counterpartyPortID, counterpartyChannelID string, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelCloseResponse, uint64, error) { +func (s *senderContract) Execute(hash []byte, params cosmwasmv1.Env, msg []byte, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.HandleResponse, uint64, error) { panic("implement me") } @@ -144,47 +168,58 @@ type receiverContract struct { chain *ibc_testing.TestChain } -func (c *receiverContract) OnIBCChannelOpen(hash []byte, params cosmwasmv2.Env, order channeltypes.Order, version string, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelOpenResponse, uint64, error) { +func (c *receiverContract) OnIBCChannelOpen(hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelOpenResponse, uint64, error) { //if order != channeltypes.ORDERED { // todo: ordered channels fail with `k.GetNextSequenceAck` as there is no value for destPort/ DestChannel stored // return &cosmwasmv2.IBCChannelOpenResponse{ // Result: false, // Reason: "channel type must be ordered", // }, 0, nil //} - return &cosmwasmv2.IBCChannelOpenResponse{Result: true}, 0, nil + return &cosmwasmv2.IBCChannelOpenResponse{Success: true}, 0, nil +} + +func (c *receiverContract) OnIBCChannelConnect(hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelConnectResponse, uint64, error) { + return &cosmwasmv2.IBCChannelConnectResponse{}, 0, nil +} + +func (c *receiverContract) OnIBCChannelClose(ctx sdk.Context, hash []byte, params cosmwasmv2.Env, channel cosmwasmv2.IBCChannel, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelCloseResponse, uint64, error) { + return &cosmwasmv2.IBCChannelCloseResponse{}, 0, nil } -func (c *receiverContract) OnIBCPacketReceive(hash []byte, params cosmwasmv2.Env, msg []byte, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketReceiveResponse, uint64, error) { + +func (c *receiverContract) OnIBCPacketReceive(hash []byte, params cosmwasmv2.Env, packet cosmwasmv2.IBCPacket, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketReceiveResponse, uint64, error) { var src ibctransfertypes.FungibleTokenPacketData - if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(msg, &src); err != nil { + if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(packet.Data, &src); err != nil { return nil, 0, err } // call original ibctransfer keeper to not copy all code into this - packet := params.IBC.AsPacket(msg) + ibcPacket := toIBCPacket(packet) ctx := c.chain.GetContext() // HACK: please note that this is not reverted after checkTX - err := c.chain.App.TransferKeeper.OnRecvPacket(ctx, packet, src) + err := c.chain.App.TransferKeeper.OnRecvPacket(ctx, ibcPacket, src) if err != nil { return nil, 0, sdkerrors.Wrap(err, "within our smart contract") } - log := []wasmTypes.LogAttribute{} // note: all events are under `wasm` event type + log := []cosmwasmv1.LogAttribute{} // note: all events are under `wasm` event type myAck := ibctransfertypes.FungibleTokenPacketAcknowledgement{Success: true}.GetBytes() return &cosmwasmv2.IBCPacketReceiveResponse{Acknowledgement: myAck, Log: log}, 0, nil } -func (c *receiverContract) OnIBCPacketAcknowledgement(hash []byte, params cosmwasmv2.Env, originalData []byte, acknowledgement []byte, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketAcknowledgementResponse, uint64, error) { + +func (c *receiverContract) OnIBCPacketAcknowledgement(hash []byte, params cosmwasmv2.Env, packetAck cosmwasmv2.IBCAcknowledgement, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketAcknowledgementResponse, uint64, error) { var src ibctransfertypes.FungibleTokenPacketData - if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(originalData, &src); err != nil { + if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(packetAck.OriginalPacket.Data, &src); err != nil { return nil, 0, err } // call original ibctransfer keeper to not copy all code into this - packet := params.IBC.AsPacket(originalData) var ack ibctransfertypes.FungibleTokenPacketAcknowledgement - if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(acknowledgement, &src); err != nil { + if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(packetAck.Acknowledgement, &ack); err != nil { return nil, 0, err } + // call original ibctransfer keeper to not copy all code into this ctx := c.chain.GetContext() // HACK: please note that this is not reverted after checkTX - err := c.chain.App.TransferKeeper.OnAcknowledgementPacket(ctx, packet, src, ack) + ibcPacket := toIBCPacket(packetAck.OriginalPacket) + err := c.chain.App.TransferKeeper.OnAcknowledgementPacket(ctx, ibcPacket, src, ack) if err != nil { return nil, 0, sdkerrors.Wrap(err, "within our smart contract") } @@ -192,26 +227,37 @@ func (c *receiverContract) OnIBCPacketAcknowledgement(hash []byte, params cosmwa return &cosmwasmv2.IBCPacketAcknowledgementResponse{}, 0, nil } -func (c *receiverContract) OnIBCPacketTimeout(hash []byte, params cosmwasmv2.Env, originalData []byte, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketTimeoutResponse, uint64, error) { +func (c *receiverContract) OnIBCPacketTimeout(hash []byte, params cosmwasmv2.Env, packet cosmwasmv2.IBCPacket, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCPacketTimeoutResponse, uint64, error) { var src ibctransfertypes.FungibleTokenPacketData - if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(originalData, &src); err != nil { + if err := ibctransfertypes.ModuleCdc.UnmarshalJSON(packet.Data, &src); err != nil { return nil, 0, err } // call original ibctransfer keeper to not copy all code into this - packet := params.IBC.AsPacket(originalData) + ibcPacket := toIBCPacket(packet) // call original ibctransfer keeper to not copy all code into this ctx := c.chain.GetContext() // HACK: please note that this is not reverted after checkTX - err := c.chain.App.TransferKeeper.OnTimeoutPacket(ctx, packet, src) + err := c.chain.App.TransferKeeper.OnTimeoutPacket(ctx, ibcPacket, src) if err != nil { return nil, 0, sdkerrors.Wrap(err, "within our smart contract") } return &cosmwasmv2.IBCPacketTimeoutResponse{}, 0, nil } -func (s *receiverContract) OnIBCChannelConnect(hash []byte, params cosmwasmv2.Env, counterpartyPortID string, counterpartyChannelID string, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelConnectResponse, uint64, error) { - return &cosmwasmv2.IBCChannelConnectResponse{}, 0, nil -} -func (s *receiverContract) OnIBCChannelClose(ctx sdk.Context, hash []byte, params cosmwasmv2.Env, counterpartyPortID, counterpartyChannelID string, store prefix.Store, api cosmwasmv1.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.IBCChannelCloseResponse, uint64, error) { + +func (c *receiverContract) Execute(hash []byte, params cosmwasmv1.Env, msg []byte, store prefix.Store, api cosmwasm.GoAPI, querier wasmkeeper.QueryHandler, meter sdk.GasMeter, gas uint64) (*cosmwasmv2.HandleResponse, uint64, error) { panic("implement me") } + +func toIBCPacket(p cosmwasmv2.IBCPacket) channeltypes.Packet { + return channeltypes.Packet{ + Sequence: p.Sequence, + SourcePort: p.Source.Port, + SourceChannel: p.Source.Channel, + DestinationPort: p.Destination.Port, + DestinationChannel: p.Destination.Channel, + Data: p.Data, + TimeoutHeight: p.TimeoutHeight, + TimeoutTimestamp: p.TimeoutTimestamp, + } +}