From 224ae35c3ee550c9ba49a52244a71a0d163d2f79 Mon Sep 17 00:00:00 2001 From: beer-1 <147697694+beer-1@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:17:26 +0900 Subject: [PATCH 1/2] apply latest ibc-hook support --- app/app.go | 66 +++++-- app/{ibc-middleware => ibc-hooks}/README.md | 103 ++++++++-- app/ibc-hooks/ack.go | 122 ++++++++++++ app/ibc-hooks/hooks.go | 74 +++++++ app/ibc-hooks/message.go | 21 ++ app/ibc-hooks/receive.go | 138 +++++++++++++ app/ibc-hooks/timeout.go | 95 +++++++++ app/{ibc-middleware => ibc-hooks}/util.go | 56 ++++-- app/ibc-hooks/util_test.go | 117 +++++++++++ app/ibc-middleware/ibc_middleware.go | 206 -------------------- app/ibc-middleware/util_test.go | 44 ----- app/upgrade.go | 46 +---- go.mod | 7 +- go.sum | 14 +- 14 files changed, 766 insertions(+), 343 deletions(-) rename app/{ibc-middleware => ibc-hooks}/README.md (59%) create mode 100644 app/ibc-hooks/ack.go create mode 100644 app/ibc-hooks/hooks.go create mode 100644 app/ibc-hooks/message.go create mode 100644 app/ibc-hooks/receive.go create mode 100644 app/ibc-hooks/timeout.go rename app/{ibc-middleware => ibc-hooks}/util.go (59%) create mode 100644 app/ibc-hooks/util_test.go delete mode 100644 app/ibc-middleware/ibc_middleware.go delete mode 100644 app/ibc-middleware/util_test.go diff --git a/app/app.go b/app/app.go index 4e0e6fd..2ad833a 100644 --- a/app/app.go +++ b/app/app.go @@ -104,6 +104,9 @@ import ( initiaapplanes "github.com/initia-labs/initia/app/lanes" initiaappparams "github.com/initia-labs/initia/app/params" + ibchooks "github.com/initia-labs/initia/x/ibc-hooks" + ibchookskeeper "github.com/initia-labs/initia/x/ibc-hooks/keeper" + ibchookstypes "github.com/initia-labs/initia/x/ibc-hooks/types" "github.com/initia-labs/initia/x/ibc/fetchprice" fetchpricekeeper "github.com/initia-labs/initia/x/ibc/fetchprice/keeper" fetchpricetypes "github.com/initia-labs/initia/x/ibc/fetchprice/types" @@ -139,10 +142,9 @@ import ( // local imports appante "github.com/initia-labs/miniwasm/app/ante" apphook "github.com/initia-labs/miniwasm/app/hook" - wasmibcmiddleware "github.com/initia-labs/miniwasm/app/ibc-middleware" + ibcwasmhooks "github.com/initia-labs/miniwasm/app/ibc-hooks" appkeepers "github.com/initia-labs/miniwasm/app/keepers" applanes "github.com/initia-labs/miniwasm/app/lanes" - "github.com/initia-labs/miniwasm/x/bank" bankkeeper "github.com/initia-labs/miniwasm/x/bank/keeper" "github.com/initia-labs/miniwasm/x/tokenfactory" @@ -229,6 +231,7 @@ type MinitiaApp struct { OracleKeeper *oraclekeeper.Keeper // x/oracle keeper used for the slinky oracle FetchPriceKeeper *fetchpricekeeper.Keeper TokenFactoryKeeper *tokenfactorykeeper.Keeper + IBCHooksKeeper *ibchookskeeper.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -284,6 +287,7 @@ func NewMinitiaApp( ibcfeetypes.StoreKey, wasmtypes.StoreKey, opchildtypes.StoreKey, auctiontypes.StoreKey, packetforwardtypes.StoreKey, icqtypes.StoreKey, oracletypes.StoreKey, fetchpricetypes.StoreKey, tokenfactorytypes.StoreKey, + ibchookstypes.StoreKey, ) tkeys := storetypes.NewTransientStoreKeys() memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) @@ -444,6 +448,13 @@ func NewMinitiaApp( ) app.IBCFeeKeeper = &ibcFeeKeeper + app.IBCHooksKeeper = ibchookskeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[ibchookstypes.StoreKey]), + authorityAddr, + ac, + ) + //////////////////////////// // Transfer configuration // //////////////////////////// @@ -453,7 +464,6 @@ func NewMinitiaApp( var transferStack porttypes.IBCModule { packetForwardKeeper := &packetforwardkeeper.Keeper{} - wasmMiddleware := &wasmibcmiddleware.IBCMiddleware{} // Create Transfer Keepers transferKeeper := ibctransferkeeper.NewKeeper( @@ -480,8 +490,8 @@ func NewMinitiaApp( app.IBCKeeper.ChannelKeeper, communityPoolKeeper, app.BankKeeper, - // ics4wrapper: transfer -> packet forward -> wasm - wasmMiddleware, + // ics4wrapper: transfer -> packet forward -> fee + app.IBCFeeKeeper, authorityAddr, ) app.PacketForwardKeeper = packetForwardKeeper @@ -494,19 +504,21 @@ func NewMinitiaApp( packetforwardkeeper.DefaultRefundTransferPacketTimeoutTimestamp, ) - // create move middleware for transfer - *wasmMiddleware = wasmibcmiddleware.NewIBCMiddleware( + // create wasm middleware for transfer + hookMiddleware := ibchooks.NewIBCMiddleware( // receive: wasm -> packet forward -> transfer packetForwardMiddleware, - // ics4wrapper: transfer -> packet forward -> wasm -> fee - app.IBCFeeKeeper, - app.WasmKeeper, + ibchooks.NewICS4Middleware( + nil, /* ics4wrapper: not used */ + ibcwasmhooks.NewWasmHooks(app.WasmKeeper, ac), + ), + app.IBCHooksKeeper, ) // create ibcfee middleware for transfer transferStack = ibcfee.NewIBCMiddleware( // receive: fee -> wasm -> packet forward -> transfer - wasmMiddleware, + hookMiddleware, // ics4wrapper: transfer -> packet forward -> wasm -> fee -> channel *app.IBCFeeKeeper, ) @@ -563,8 +575,32 @@ func NewMinitiaApp( // Wasm IBC Configuration // ////////////////////////////// - wasmIBCModule := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCFeeKeeper) - wasmIBCStack := ibcfee.NewIBCMiddleware(wasmIBCModule, *app.IBCFeeKeeper) + var wasmIBCStack porttypes.IBCModule + { + wasmIBCModule := wasm.NewIBCHandler( + app.WasmKeeper, + app.IBCKeeper.ChannelKeeper, + // ics4wrapper: wasm -> fee + app.IBCFeeKeeper, + ) + + // create wasm middleware for wasm IBC stack + hookMiddleware := ibchooks.NewIBCMiddleware( + // receive: hook -> wasm + wasmIBCModule, + ibchooks.NewICS4Middleware( + nil, /* ics4wrapper: not used */ + ibcwasmhooks.NewWasmHooks(app.WasmKeeper, ac), + ), + app.IBCHooksKeeper, + ) + + wasmIBCStack = ibcfee.NewIBCMiddleware( + // receive: fee -> hook -> wasm + hookMiddleware, + *app.IBCFeeKeeper, + ) + } /////////////////////// // ICQ configuration // @@ -718,6 +754,7 @@ func NewMinitiaApp( consensus.NewAppModule(appCodec, *app.ConsensusParamsKeeper), wasm.NewAppModule(appCodec, app.WasmKeeper, nil /* unused */, app.AccountKeeper, app.BankKeeper, app.MsgServiceRouter(), nil), auction.NewAppModule(app.appCodec, *app.AuctionKeeper), + tokenfactory.NewAppModule(appCodec, *app.TokenFactoryKeeper, *app.AccountKeeper, *app.BankKeeper), // ibc modules ibc.NewAppModule(app.IBCKeeper), ibctransfer.NewAppModule(*app.TransferKeeper), @@ -729,9 +766,9 @@ func NewMinitiaApp( packetforward.NewAppModule(app.PacketForwardKeeper, nil), icq.NewAppModule(*app.ICQKeeper, nil), fetchprice.NewAppModule(appCodec, *app.FetchPriceKeeper), + ibchooks.NewAppModule(appCodec, *app.IBCHooksKeeper), // slinky modules oracle.NewAppModule(appCodec, *app.OracleKeeper), - tokenfactory.NewAppModule(appCodec, *app.TokenFactoryKeeper, *app.AccountKeeper, *app.BankKeeper), ) // BasicModuleManager defines the module BasicManager is in charge of setting up basic, @@ -782,6 +819,7 @@ func NewMinitiaApp( ibcfeetypes.ModuleName, auctiontypes.ModuleName, wasmtypes.ModuleName, oracletypes.ModuleName, packetforwardtypes.ModuleName, icqtypes.ModuleName, fetchpricetypes.ModuleName, tokenfactorytypes.ModuleName, + ibchookstypes.ModuleName, } app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) diff --git a/app/ibc-middleware/README.md b/app/ibc-hooks/README.md similarity index 59% rename from app/ibc-middleware/README.md rename to app/ibc-hooks/README.md index 41784ce..7984ee5 100644 --- a/app/ibc-middleware/README.md +++ b/app/ibc-hooks/README.md @@ -7,7 +7,7 @@ This allows cross-chain contract calls, that involve token movement. This is useful for a variety of usecases. One of primary importance is cross-chain swaps, which is an extremely powerful primitive. -The mechanism enabling this is a `memo` field on every ICS20 transfer packet as of [IBC v3.4.0](https://medium.com/the-interchain-foundation/moving-beyond-simple-token-transfers-d42b2b1dc29b). +The mechanism enabling this is a `memo` field on every ICS20 or ICS721 transfer packet as of [IBC v3.4.0](https://medium.com/the-interchain-foundation/moving-beyond-simple-token-transfers-d42b2b1dc29b). Wasm hooks is an IBC middleware that parses an ICS20 transfer, and if the `memo` field is of a particular form, executes a wasm contract call. We now detail the `memo` format for `wasm` contract calls, and the execution guarantees provided. ### Cosmwasm Contract Execution Format @@ -17,6 +17,17 @@ The cosmwasm `MsgExecuteContract` is defined [here](https://github.com/CosmWasm/ ) as the following type: ```go +// HookData defines a wrapper for wasm execute message +// and async callback. +type HookData struct { + // Message is a wasm execute message which will be executed + // at `OnRecvPacket` of receiver chain. + Message *wasmtypes.MsgExecuteContract `json:"message,omitempty"` + + // AsyncCallback is a contract address + AsyncCallback string `json:"async_callback,omitempty"` +} + type MsgExecuteContract struct { // Sender is the actor that committed the message in the sender chain Sender string @@ -46,13 +57,14 @@ So our constructed cosmwasm message that we execute will look like: ```go msg := MsgExecuteContract{ // Sender is the that actor that signed the messages - Sender: "osmo1-hash-of-channel-and-sender", + Sender: "init1-hash-of-channel-and-sender", // Contract is the address of the smart contract - Contract: packet.data.memo["wasm"]["ContractAddress"], + Contract: packet.data.memo["wasm"]["contract"], // Msg json encoded message to be passed to the contract - Msg: packet.data.memo["wasm"]["Msg"], + Msg: packet.data.memo["wasm"]["msg"], // Funds coins that are transferred to the contract on execution Funds: sdk.NewCoin{Denom: ibc.ConvertSenderDenomToLocalDenom(packet.data.Denom), Amount: packet.data.Amount} +} ``` ### ICS20 packet structure @@ -68,12 +80,15 @@ ICS20 is JSON native, so we use JSON for the memo format. "amount": "1000", "sender": "addr on counterparty chain", // will be transformed "receiver": "contract addr or blank", - "memo": { + "memo": { "wasm": { - "contract": "osmo1contractAddr", + "contract": "init1contractAddr", "msg": { "raw_message_fields": "raw_message_data", - } + }, + "funds": [ + {"denom": "ibc/denom", "amount": "100"} + ] } } } @@ -85,8 +100,8 @@ An ICS20 packet is formatted correctly for wasmhooks iff the following all hold: * `memo` is not blank * `memo` is valid JSON * `memo` has at least one key, with value `"wasm"` -* `memo["wasm"]` has exactly two entries, `"contract"` and `"msg"` -* `memo["wasm"]["msg"]` is a valid JSON object +* `memo["wasm"]["message"]` has exactly two entries, `"contract"`, `"msg"` and `"fund"` +* `memo["wasm"]["message"]["msg"]` is a valid JSON object * `receiver == "" || receiver == memo["wasm"]["contract"]` We consider an ICS20 packet as directed towards wasmhooks iff all of the following hold: @@ -117,6 +132,72 @@ In wasm hooks, post packet execution: * if wasm message has error, return ErrAck * otherwise continue through middleware -## Testing strategy +## Ack callbacks + +A contract that sends an IBC transfer, may need to listen for the ACK from that packet. To allow +contracts to listen on the ack of specific packets, we provide Ack callbacks. + +### Design + +The sender of an IBC transfer packet may specify a callback for when the ack of that packet is received in the memo +field of the transfer packet. + +Crucially, _only_ the IBC packet sender can set the callback. + +### Use case + +The crosschain swaps implementation sends an IBC transfer. If the transfer were to fail, we want to allow the sender +to be able to retrieve their funds (which would otherwise be stuck in the contract). To do this, we allow users to +retrieve the funds after the timeout has passed, but without the ack information, we cannot guarantee that the send +hasn't failed (i.e.: returned an error ack notifying that the receiving change didn't accept it) + +### Implementation + +#### Callback information in memo -See go tests. +For the callback to be processed, the transfer packet's memo should contain the following in its JSON: + +```json +{ + "wasm": { + "async_callback": "init1contractAddr" + } +} +``` + +When an ack is received, it will notify the specified contract via a sudo message. + +#### Interface for receiving the Acks and Timeouts + +The contract that awaits the callback should implement the following interface for a sudo message: + +```rust +#[cw_serde] +pub enum IBCLifecycleComplete { + #[serde(rename = "ibc_ack")] + IBCAck { + /// The source channel (miniwasm side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + /// String encoded version of the ack as seen by OnAcknowledgementPacket(..) + ack: String, + /// Weather an ack is a success of failure according to the transfer spec + success: bool, + }, + #[serde(rename = "ibc_timeout")] + IBCTimeout { + /// The source channel (miniwasm side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + }, +} + +/// Message type for `sudo` entry_point +#[cw_serde] +pub enum SudoMsg { + #[serde(rename = "ibc_lifecycle_complete")] + IBCLifecycleComplete(IBCLifecycleComplete), +} +``` diff --git a/app/ibc-hooks/ack.go b/app/ibc-hooks/ack.go new file mode 100644 index 0000000..8c6292f --- /dev/null +++ b/app/ibc-hooks/ack.go @@ -0,0 +1,122 @@ +package wasm_hooks + +import ( + "encoding/json" + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + + ibchooks "github.com/initia-labs/initia/x/ibc-hooks" + nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types" +) + +func (h WasmHooks) onAckIcs20Packet( + ctx sdk.Context, + im ibchooks.IBCMiddleware, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + data transfertypes.FungibleTokenPacketData, +) error { + if err := im.App.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer); err != nil { + return err + } + + isWasmRouted, hookData, err := validateAndParseMemo(data.GetMemo()) + if !isWasmRouted || hookData.AsyncCallback == "" { + return nil + } else if err != nil { + return err + } + + callback := hookData.AsyncCallback + if allowed, err := h.checkACL(im, ctx, callback); err != nil { + return err + } else if !allowed { + return nil + } + + contractAddr, err := h.ac.StringToBytes(callback) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") + } + + success := "false" + if !isAckError(acknowledgement) { + success = "true" + } + + // Notify the sender that the ack has been received + ackAsJson, err := json.Marshal(acknowledgement) + if err != nil { + // If the ack is not a json object, error + return err + } + + sudoMsg := []byte(fmt.Sprintf( + `{"ibc_lifecycle_complete": {"ibc_ack": {"channel": "%s", "sequence": %d, "ack": %s, "success": %s}}}`, + packet.SourceChannel, packet.Sequence, ackAsJson, success)) + _, err = h.wasmKeeper.Sudo(ctx, contractAddr, sudoMsg) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") + } + + return nil +} + +func (h WasmHooks) onAckIcs721Packet( + ctx sdk.Context, + im ibchooks.IBCMiddleware, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + data nfttransfertypes.NonFungibleTokenPacketData, +) error { + if err := im.App.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer); err != nil { + return err + } + + isWasmRouted, hookData, err := validateAndParseMemo(data.GetMemo()) + if !isWasmRouted || hookData.AsyncCallback == "" { + return nil + } else if err != nil { + return err + } + + callback := hookData.AsyncCallback + if allowed, err := h.checkACL(im, ctx, callback); err != nil { + return err + } else if !allowed { + return nil + } + + contractAddr, err := h.ac.StringToBytes(callback) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") + } + + success := "false" + if !isAckError(acknowledgement) { + success = "true" + } + + // Notify the sender that the ack has been received + ackAsJson, err := json.Marshal(acknowledgement) + if err != nil { + // If the ack is not a json object, error + return err + } + + sudoMsg := []byte(fmt.Sprintf( + `{"ibc_lifecycle_complete": {"ibc_ack": {"channel": "%s", "sequence": %d, "ack": %s, "success": %s}}}`, + packet.SourceChannel, packet.Sequence, ackAsJson, success)) + _, err = h.wasmKeeper.Sudo(ctx, contractAddr, sudoMsg) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") + } + + return nil +} diff --git a/app/ibc-hooks/hooks.go b/app/ibc-hooks/hooks.go new file mode 100644 index 0000000..301ac9e --- /dev/null +++ b/app/ibc-hooks/hooks.go @@ -0,0 +1,74 @@ +package wasm_hooks + +import ( + "cosmossdk.io/core/address" + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + ibchooks "github.com/initia-labs/initia/x/ibc-hooks" +) + +var ( + _ ibchooks.OnRecvPacketOverrideHooks = WasmHooks{} + _ ibchooks.OnAcknowledgementPacketOverrideHooks = WasmHooks{} + _ ibchooks.OnTimeoutPacketOverrideHooks = WasmHooks{} +) + +type WasmHooks struct { + wasmKeeper *wasmkeeper.Keeper + ac address.Codec +} + +func NewWasmHooks(wasmKeeper *wasmkeeper.Keeper, ac address.Codec) *WasmHooks { + return &WasmHooks{ + wasmKeeper: wasmKeeper, + ac: ac, + } +} + +func (h WasmHooks) OnRecvPacketOverride(im ibchooks.IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) ibcexported.Acknowledgement { + if isIcs20, ics20Data := isIcs20Packet(packet.GetData()); isIcs20 { + return h.onRecvIcs20Packet(ctx, im, packet, relayer, ics20Data) + } + + if isIcs721, ics721Data := isIcs721Packet(packet.GetData(), packet.SourcePort); isIcs721 { + return h.onRecvIcs721Packet(ctx, im, packet, relayer, ics721Data) + } + + return im.App.OnRecvPacket(ctx, packet, relayer) +} + +func (h WasmHooks) OnAcknowledgementPacketOverride(im ibchooks.IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { + if isIcs20, ics20Data := isIcs20Packet(packet.GetData()); isIcs20 { + return h.onAckIcs20Packet(ctx, im, packet, acknowledgement, relayer, ics20Data) + } + + if isIcs721, ics721Data := isIcs721Packet(packet.GetData(), packet.DestinationPort); isIcs721 { + return h.onAckIcs721Packet(ctx, im, packet, acknowledgement, relayer, ics721Data) + } + + return im.App.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) +} + +func (h WasmHooks) OnTimeoutPacketOverride(im ibchooks.IBCMiddleware, ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress) error { + if isIcs20, ics20Data := isIcs20Packet(packet.GetData()); isIcs20 { + return h.onTimeoutIcs20Packet(ctx, im, packet, relayer, ics20Data) + } + + if isIcs721, ics721Data := isIcs721Packet(packet.GetData(), packet.DestinationPort); isIcs721 { + return h.onTimeoutIcs721Packet(ctx, im, packet, relayer, ics721Data) + } + + return im.App.OnTimeoutPacket(ctx, packet, relayer) +} + +func (h WasmHooks) checkACL(im ibchooks.IBCMiddleware, ctx sdk.Context, addrStr string) (bool, error) { + addr, err := h.ac.StringToBytes(addrStr) + if err != nil { + return false, err + } + + return im.HooksKeeper.GetAllowed(ctx, addr) +} diff --git a/app/ibc-hooks/message.go b/app/ibc-hooks/message.go new file mode 100644 index 0000000..8ed5208 --- /dev/null +++ b/app/ibc-hooks/message.go @@ -0,0 +1,21 @@ +package wasm_hooks + +import ( + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" +) + +const ( + // The memo key is used to parse ics-20 or ics-712 memo fields. + wasmHookMemoKey = "wasm" +) + +// HookData defines a wrapper for wasm execute message +// and async callback. +type HookData struct { + // Message is a wasm execute message which will be executed + // at `OnRecvPacket` of receiver chain. + Message *wasmtypes.MsgExecuteContract `json:"message,omitempty"` + + // AsyncCallback is a contract address + AsyncCallback string `json:"async_callback,omitempty"` +} diff --git a/app/ibc-hooks/receive.go b/app/ibc-hooks/receive.go new file mode 100644 index 0000000..1fcebd8 --- /dev/null +++ b/app/ibc-hooks/receive.go @@ -0,0 +1,138 @@ +package wasm_hooks + +import ( + "encoding/json" + + sdk "github.com/cosmos/cosmos-sdk/types" + + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" + + ibchooks "github.com/initia-labs/initia/x/ibc-hooks" + nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" +) + +func (h WasmHooks) onRecvIcs20Packet( + ctx sdk.Context, + im ibchooks.IBCMiddleware, + packet channeltypes.Packet, + relayer sdk.AccAddress, + data transfertypes.FungibleTokenPacketData, +) ibcexported.Acknowledgement { + isWasmRouted, hookData, err := validateAndParseMemo(data.GetMemo()) + if !isWasmRouted || hookData.Message == nil { + return im.App.OnRecvPacket(ctx, packet, relayer) + } else if err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } + + msg := hookData.Message + if allowed, err := h.checkACL(im, ctx, msg.Contract); err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } else if !allowed { + return im.App.OnRecvPacket(ctx, packet, relayer) + } + + // Validate whether the receiver is correctly specified or not. + if err := validateReceiver(msg, data.Receiver); err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } + + // Calculate the receiver / contract caller based on the packet's channel and sender + intermediateSender := deriveIntermediateSender(packet.GetDestChannel(), data.GetSender()) + + // The funds sent on this packet need to be transferred to the intermediary account for the sender. + // For this, we override the ICS20 packet's Receiver (essentially hijacking the funds to this new address) + // and execute the underlying OnRecvPacket() call (which should eventually land on the transfer app's + // relay.go and send the funds to the intermediary account. + // + // If that succeeds, we make the contract call + data.Receiver = intermediateSender + bz, err := json.Marshal(data) + if err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } + packet.Data = bz + + ack := im.App.OnRecvPacket(ctx, packet, relayer) + if !ack.Success() { + return ack + } + + msg.Sender = intermediateSender + _, err = h.execMsg(ctx, msg) + if err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } + + return ack +} + +func (h WasmHooks) onRecvIcs721Packet( + ctx sdk.Context, + im ibchooks.IBCMiddleware, + packet channeltypes.Packet, + relayer sdk.AccAddress, + data nfttransfertypes.NonFungibleTokenPacketData, +) ibcexported.Acknowledgement { + isWasmRouted, hookData, err := validateAndParseMemo(data.GetMemo()) + if !isWasmRouted || hookData.Message == nil { + return im.App.OnRecvPacket(ctx, packet, relayer) + } else if err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } + + msg := hookData.Message + if allowed, err := h.checkACL(im, ctx, msg.Contract); err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } else if !allowed { + return im.App.OnRecvPacket(ctx, packet, relayer) + } + + // Validate whether the receiver is correctly specified or not. + if err := validateReceiver(msg, data.Receiver); err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } + + // Calculate the receiver / contract caller based on the packet's channel and sender + intermediateSender := deriveIntermediateSender(packet.GetDestChannel(), data.GetSender()) + + // The funds sent on this packet need to be transferred to the intermediary account for the sender. + // For this, we override the ICS721 packet's Receiver (essentially hijacking the funds to this new address) + // and execute the underlying OnRecvPacket() call (which should eventually land on the transfer app's + // relay.go and send the funds to the intermediary account. + // + // If that succeeds, we make the contract call + data.Receiver = intermediateSender + bz, err := json.Marshal(data) + if err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } + packet.Data = bz + + ack := im.App.OnRecvPacket(ctx, packet, relayer) + if !ack.Success() { + return ack + } + + msg.Sender = intermediateSender + _, err = h.execMsg(ctx, msg) + if err != nil { + return newEmitErrorAcknowledgement(ctx, err) + } + + return ack +} + +func (im WasmHooks) execMsg(ctx sdk.Context, msg *wasmtypes.MsgExecuteContract) (*wasmtypes.MsgExecuteContractResponse, error) { + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + wasmMsgServer := wasmkeeper.NewMsgServerImpl(im.wasmKeeper) + return wasmMsgServer.ExecuteContract(ctx, msg) +} diff --git a/app/ibc-hooks/timeout.go b/app/ibc-hooks/timeout.go new file mode 100644 index 0000000..4d5a2bf --- /dev/null +++ b/app/ibc-hooks/timeout.go @@ -0,0 +1,95 @@ +package wasm_hooks + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + + ibchooks "github.com/initia-labs/initia/x/ibc-hooks" + nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types" +) + +func (h WasmHooks) onTimeoutIcs20Packet( + ctx sdk.Context, + im ibchooks.IBCMiddleware, + packet channeltypes.Packet, + relayer sdk.AccAddress, + data transfertypes.FungibleTokenPacketData, +) error { + if err := im.App.OnTimeoutPacket(ctx, packet, relayer); err != nil { + return err + } + + isWasmRouted, hookData, err := validateAndParseMemo(data.GetMemo()) + if !isWasmRouted || hookData.AsyncCallback == "" { + return nil + } else if err != nil { + return err + } + + callback := hookData.AsyncCallback + if allowed, err := h.checkACL(im, ctx, callback); err != nil { + return err + } else if !allowed { + return nil + } + + contractAddr, err := h.ac.StringToBytes(callback) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") + } + + sudoMsg := []byte(fmt.Sprintf( + `{"ibc_lifecycle_complete": {"ibc_timeout": {"channel": "%s", "sequence": %d}}}`, + packet.SourceChannel, packet.Sequence)) + _, err = h.wasmKeeper.Sudo(ctx, contractAddr, sudoMsg) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") + } + + return nil +} + +func (h WasmHooks) onTimeoutIcs721Packet( + ctx sdk.Context, + im ibchooks.IBCMiddleware, + packet channeltypes.Packet, + relayer sdk.AccAddress, + data nfttransfertypes.NonFungibleTokenPacketData, +) error { + if err := im.App.OnTimeoutPacket(ctx, packet, relayer); err != nil { + return err + } + + isWasmRouted, hookData, err := validateAndParseMemo(data.GetMemo()) + if !isWasmRouted || hookData.AsyncCallback == "" { + return nil + } else if err != nil { + return err + } + + callback := hookData.AsyncCallback + if allowed, err := h.checkACL(im, ctx, callback); err != nil { + return err + } else if !allowed { + return nil + } + + contractAddr, err := h.ac.StringToBytes(callback) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") + } + + sudoMsg := []byte(fmt.Sprintf( + `{"ibc_lifecycle_complete": {"ibc_timeout": {"channel": "%s", "sequence": %d}}}`, + packet.SourceChannel, packet.Sequence)) + _, err = h.wasmKeeper.Sudo(ctx, contractAddr, sudoMsg) + if err != nil { + return errorsmod.Wrap(err, "Ack callback error") + } + + return nil +} diff --git a/app/ibc-middleware/util.go b/app/ibc-hooks/util.go similarity index 59% rename from app/ibc-middleware/util.go rename to app/ibc-hooks/util.go index f9480ad..4815bcd 100644 --- a/app/ibc-middleware/util.go +++ b/app/ibc-hooks/util.go @@ -1,8 +1,9 @@ -package ibc_middleware +package wasm_hooks import ( "encoding/json" "fmt" + "strings" "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,10 +12,12 @@ import ( transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" ) -const senderPrefix = "ibc-move-wasm-intermediary" +const senderPrefix = "ibc-wasm-hook-intermediary" // deriveIntermediateSender compute intermediate sender address // Bech32(Hash(Hash("ibc-hook-intermediary") + channelID/sender)) @@ -24,39 +27,58 @@ func deriveIntermediateSender(channel, originalSender string) string { return senderAddr.String() } -func isIcs20Packet(packet channeltypes.Packet) (isIcs20 bool, ics20data transfertypes.FungibleTokenPacketData) { +func isIcs20Packet(packetData []byte) (isIcs20 bool, ics20data transfertypes.FungibleTokenPacketData) { var data transfertypes.FungibleTokenPacketData - if err := json.Unmarshal(packet.GetData(), &data); err != nil { + decoder := json.NewDecoder(strings.NewReader(string(packetData))) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&data); err != nil { return false, data } return true, data } -func validateAndParseMemo(memo, receiver string) (isWasmRouted bool, msg wasmtypes.MsgExecuteContract, err error) { - isWasmRouted, metadata := jsonStringHasKey(memo, "wasm") +func isIcs721Packet(packetData []byte, counterPartyPort string) (isIcs721 bool, ics721data nfttransfertypes.NonFungibleTokenPacketData) { + if data, err := nfttransfertypes.DecodePacketData(packetData, counterPartyPort); err != nil { + return false, data + } else { + return true, data + } +} + +func validateAndParseMemo(memo string) ( + isWasmRouted bool, + hookData HookData, + err error, +) { + isWasmRouted, metadata := jsonStringHasKey(memo, wasmHookMemoKey) if !isWasmRouted { return } - wasmRaw := metadata["wasm"] - bz, err := json.Marshal(wasmRaw) + wasmHookRaw := metadata[wasmHookMemoKey] + + // parse wasm raw bytes to execute message + bz, err := json.Marshal(wasmHookRaw) if err != nil { err = errors.Wrap(channeltypes.ErrInvalidPacket, err.Error()) return } - err = json.Unmarshal(bz, &msg) + err = json.Unmarshal(bz, &hookData) if err != nil { err = errors.Wrap(channeltypes.ErrInvalidPacket, err.Error()) return } + return +} + +func validateReceiver(msg *wasmtypes.MsgExecuteContract, receiver string) error { if receiver != msg.Contract { - err = errors.Wrap(channeltypes.ErrInvalidPacket, "receiver is not properly set") - return + return errors.Wrap(channeltypes.ErrInvalidPacket, "receiver is not properly set") } - return + return nil } // jsonStringHasKey parses the memo as a json object and checks if it contains the key. @@ -94,3 +116,13 @@ func newEmitErrorAcknowledgement(ctx sdk.Context, err error) channeltypes.Acknow }, } } + +// isAckError checks an IBC acknowledgement to see if it's an error. +// This is a replacement for ack.Success() which is currently not working on some circumstances +func isAckError(acknowledgement []byte) bool { + var ackErr channeltypes.Acknowledgement_Error + if err := json.Unmarshal(acknowledgement, &ackErr); err == nil && len(ackErr.Error) > 0 { + return true + } + return false +} diff --git a/app/ibc-hooks/util_test.go b/app/ibc-hooks/util_test.go new file mode 100644 index 0000000..b2a275f --- /dev/null +++ b/app/ibc-hooks/util_test.go @@ -0,0 +1,117 @@ +package wasm_hooks + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + + nfttransfertypes "github.com/initia-labs/initia/x/ibc/nft-transfer/types" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" +) + +func Test_isIcs20Packet(t *testing.T) { + transferMsg := transfertypes.NewFungibleTokenPacketData("denom", "1000000", "0x1", "0x2", "memo") + bz, err := json.Marshal(transferMsg) + require.NoError(t, err) + + ok, _transferMsg := isIcs20Packet(bz) + require.True(t, ok) + require.Equal(t, transferMsg, _transferMsg) + + nftTransferMsg := nfttransfertypes.NewNonFungibleTokenPacketData("class_id", "uri", "data", []string{"1", "2", "3"}, []string{"uri1", "uri2", "uri3"}, []string{"data1", "data2", "data3"}, "sender", "receiver", "memo") + bz, err = json.Marshal(nftTransferMsg) + require.NoError(t, err) + + ok, _ = isIcs20Packet(bz) + require.False(t, ok) +} + +func Test_isIcs721Packet(t *testing.T) { + nftTransferMsg := nfttransfertypes.NewNonFungibleTokenPacketData("class_id", "uri", "data", []string{"1", "2", "3"}, []string{"uri1", "uri2", "uri3"}, []string{"data1", "data2", "data3"}, "sender", "receiver", "memo") + + ok, _nftTransferMsg := isIcs721Packet(nftTransferMsg.GetBytes("ics721"), "ics721") + require.True(t, ok) + require.Equal(t, nftTransferMsg, _nftTransferMsg) + + // isWasmPacket + ok, _nftTransferMsg = isIcs721Packet(nftTransferMsg.GetBytes("wasm.contract"), "wasm.contract") + require.True(t, ok) + require.Equal(t, nftTransferMsg, _nftTransferMsg) + + transferMsg := transfertypes.NewFungibleTokenPacketData("denom", "1000000", "0x1", "0x2", "memo") + ok, _ = isIcs721Packet(transferMsg.GetBytes(), "ics721") + require.False(t, ok) +} + +func Test_validateAndParseMemo_without_callback(t *testing.T) { + memo := `{ + "wasm" : { + "message": { + "sender": "init_addr", + "contract": "contract_addr", + "msg": {}, + "funds": [{"denom":"foo","amount":"100"}] + } + } + }` + isWasmRouted, hookData, err := validateAndParseMemo(memo) + require.True(t, isWasmRouted) + require.NoError(t, err) + require.Equal(t, HookData{ + Message: &wasmtypes.MsgExecuteContract{ + Sender: "init_addr", + Contract: "contract_addr", + Msg: []byte("{}"), + Funds: sdk.Coins{{ + Denom: "foo", + Amount: math.NewInt(100), + }}, + }, + AsyncCallback: "", + }, hookData) + require.NoError(t, validateReceiver(hookData.Message, "contract_addr")) + + // invalid receiver + require.NoError(t, err) + require.Error(t, validateReceiver(hookData.Message, "invalid_addr")) + + isWasmRouted, _, err = validateAndParseMemo("hihi") + require.False(t, isWasmRouted) + require.NoError(t, err) +} + +func Test_validateAndParseMemo_with_callback(t *testing.T) { + memo := `{ + "wasm" : { + "message": { + "sender": "init_addr", + "contract": "contract_addr", + "msg": {}, + "funds": [{"denom":"foo","amount":"100"}] + }, + "async_callback": "callback_addr" + } + }` + isWasmRouted, hookData, err := validateAndParseMemo(memo) + require.True(t, isWasmRouted) + require.NoError(t, err) + require.Equal(t, HookData{ + Message: &wasmtypes.MsgExecuteContract{ + Sender: "init_addr", + Contract: "contract_addr", + Msg: []byte("{}"), + Funds: sdk.Coins{{ + Denom: "foo", + Amount: math.NewInt(100), + }}, + }, + AsyncCallback: "callback_addr", + }, hookData) + require.NoError(t, validateReceiver(hookData.Message, "contract_addr")) +} diff --git a/app/ibc-middleware/ibc_middleware.go b/app/ibc-middleware/ibc_middleware.go deleted file mode 100644 index ec61d8d..0000000 --- a/app/ibc-middleware/ibc_middleware.go +++ /dev/null @@ -1,206 +0,0 @@ -package ibc_middleware - -import ( - "encoding/json" - - sdk "github.com/cosmos/cosmos-sdk/types" - capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" - - clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" - channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" - porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" - ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" - - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" -) - -var _ porttypes.Middleware = &IBCMiddleware{} - -type IBCMiddleware struct { - app porttypes.IBCModule - ics4Wrapper porttypes.ICS4Wrapper - wasmKeeper *wasmkeeper.Keeper -} - -func NewIBCMiddleware( - app porttypes.IBCModule, - ics4Wrapper porttypes.ICS4Wrapper, - wasmKeeper *wasmkeeper.Keeper, -) IBCMiddleware { - return IBCMiddleware{ - app: app, - ics4Wrapper: ics4Wrapper, - wasmKeeper: wasmKeeper, - } -} - -// OnChanOpenInit implements the IBCMiddleware interface -func (im IBCMiddleware) OnChanOpenInit( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID string, - channelID string, - channelCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - version string, -) (string, error) { - return im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, version) -} - -// OnChanOpenTry implements the IBCMiddleware interface -func (im IBCMiddleware) OnChanOpenTry( - ctx sdk.Context, - order channeltypes.Order, - connectionHops []string, - portID, - channelID string, - channelCap *capabilitytypes.Capability, - counterparty channeltypes.Counterparty, - counterpartyVersion string, -) (string, error) { - return im.app.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, channelCap, counterparty, counterpartyVersion) -} - -// OnChanOpenAck implements the IBCMiddleware interface -func (im IBCMiddleware) OnChanOpenAck( - ctx sdk.Context, - portID, - channelID string, - counterpartyChannelID string, - counterpartyVersion string, -) error { - return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) -} - -// OnChanOpenConfirm implements the IBCMiddleware interface -func (im IBCMiddleware) OnChanOpenConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return im.app.OnChanOpenConfirm(ctx, portID, channelID) -} - -// OnChanCloseInit implements the IBCMiddleware interface -func (im IBCMiddleware) OnChanCloseInit( - ctx sdk.Context, - portID, - channelID string, -) error { - return im.app.OnChanCloseInit(ctx, portID, channelID) -} - -// OnChanCloseConfirm implements the IBCMiddleware interface -func (im IBCMiddleware) OnChanCloseConfirm( - ctx sdk.Context, - portID, - channelID string, -) error { - return im.app.OnChanCloseConfirm(ctx, portID, channelID) -} - -// OnAcknowledgementPacket implements the IBCMiddleware interface -func (im IBCMiddleware) OnAcknowledgementPacket( - ctx sdk.Context, - packet channeltypes.Packet, - acknowledgement []byte, - relayer sdk.AccAddress, -) error { - return im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) -} - -// OnTimeoutPacket implements the IBCMiddleware interface -func (im IBCMiddleware) OnTimeoutPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) error { - return im.app.OnTimeoutPacket(ctx, packet, relayer) -} - -// SendPacket implements the ICS4 Wrapper interface -func (im IBCMiddleware) SendPacket( - ctx sdk.Context, - chanCap *capabilitytypes.Capability, - sourcePort string, - sourceChannel string, - timeoutHeight clienttypes.Height, - timeoutTimestamp uint64, - data []byte, -) (sequence uint64, err error) { - return im.ics4Wrapper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) -} - -// WriteAcknowledgement implements the ICS4 Wrapper interface -func (im IBCMiddleware) WriteAcknowledgement( - ctx sdk.Context, - chanCap *capabilitytypes.Capability, - packet ibcexported.PacketI, - ack ibcexported.Acknowledgement, -) error { - return im.ics4Wrapper.WriteAcknowledgement(ctx, chanCap, packet, ack) -} - -func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { - return im.ics4Wrapper.GetAppVersion(ctx, portID, channelID) -} - -// OnRecvPacket implements the IBCMiddleware interface -func (im IBCMiddleware) OnRecvPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) ibcexported.Acknowledgement { - isIcs20, data := isIcs20Packet(packet) - if !isIcs20 { - return im.app.OnRecvPacket(ctx, packet, relayer) - } - - // Validate the memo - isWasmRouted, msg, err := validateAndParseMemo(data.GetMemo(), data.Receiver) - if !isWasmRouted { - return im.app.OnRecvPacket(ctx, packet, relayer) - } else if err != nil { - return newEmitErrorAcknowledgement(ctx, err) - } - - // Calculate the receiver / contract caller based on the packet's channel and sender - intermediateSender := deriveIntermediateSender(packet.GetDestChannel(), data.GetSender()) - - // The funds sent on this packet need to be transferred to the intermediary account for the sender. - // For this, we override the ICS20 packet's Receiver (essentially hijacking the funds to this new address) - // and execute the underlying OnRecvPacket() call (which should eventually land on the transfer app's - // relay.go and send the funds to the intermediary account. - // - // If that succeeds, we make the contract call - data.Receiver = intermediateSender - bz, err := json.Marshal(data) - if err != nil { - return newEmitErrorAcknowledgement(ctx, err) - } - packet.Data = bz - - ack := im.app.OnRecvPacket(ctx, packet, relayer) - if !ack.Success() { - return ack - } - - msg.Sender = intermediateSender - _, err = im.execMsg(ctx, &msg) - if err != nil { - return newEmitErrorAcknowledgement(ctx, err) - } - - return ack -} - -func (im IBCMiddleware) execMsg(ctx sdk.Context, msg *wasmtypes.MsgExecuteContract) (*wasmtypes.MsgExecuteContractResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - moveMsgServer := wasmkeeper.NewMsgServerImpl(im.wasmKeeper) - return moveMsgServer.ExecuteContract(sdk.WrapSDKContext(ctx), msg) -} diff --git a/app/ibc-middleware/util_test.go b/app/ibc-middleware/util_test.go deleted file mode 100644 index 71db29a..0000000 --- a/app/ibc-middleware/util_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package ibc_middleware - -import ( - "testing" - - "cosmossdk.io/math" - "github.com/stretchr/testify/require" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func Test_validateAndParseMemo(t *testing.T) { - memo := ` - { - "wasm" : { - "sender": "init_addr", - "contract": "contract_addr", - "msg": {}, - "funds": [{"denom":"foo","amount":"100"}] - } - }` - isWasmRouted, msg, err := validateAndParseMemo(memo, "contract_addr") - require.True(t, isWasmRouted) - require.NoError(t, err) - require.Equal(t, wasmtypes.MsgExecuteContract{ - Sender: "init_addr", - Contract: "contract_addr", - Msg: []byte("{}"), - Funds: sdk.Coins{{ - Denom: "foo", - Amount: math.NewInt(100), - }}, - }, msg) - - // invalid receiver - isWasmRouted, _, err = validateAndParseMemo(memo, "invalid_addr") - require.True(t, isWasmRouted) - require.Error(t, err) - - isWasmRouted, _, err = validateAndParseMemo("hihi", "invalid_addr") - require.False(t, isWasmRouted) - require.NoError(t, err) -} diff --git a/app/upgrade.go b/app/upgrade.go index 2c34b4e..38ebb1d 100644 --- a/app/upgrade.go +++ b/app/upgrade.go @@ -1,52 +1,10 @@ package app import ( - "context" - - storetypes "cosmossdk.io/store/types" - upgradetypes "cosmossdk.io/x/upgrade/types" "github.com/cosmos/cosmos-sdk/types/module" - packetforwardtypes "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8/packetforward/types" - icqtypes "github.com/cosmos/ibc-apps/modules/async-icq/v8/types" - oracletypes "github.com/skip-mev/slinky/x/oracle/types" - - fetchpricetypes "github.com/initia-labs/initia/x/ibc/fetchprice/types" ) -const upgradeName = "0.2.0-beta.3" +// const upgradeName = "0.2.0-beta.3" // RegisterUpgradeHandlers returns upgrade handlers -func (app *MinitiaApp) RegisterUpgradeHandlers(cfg module.Configurator) { - app.UpgradeKeeper.SetUpgradeHandler( - upgradeName, - func(ctx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { - - // remove fetchprice ibc module from version map - delete(vm, fetchpricetypes.ModuleName) - - return app.ModuleManager.RunMigrations(ctx, app.configurator, vm) - }, - ) - - upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() - if err != nil { - panic(err) - } - - if upgradeInfo.Name == upgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { - storeUpgrades := storetypes.StoreUpgrades{ - Added: []string{ - fetchpricetypes.StoreKey, - packetforwardtypes.StoreKey, - icqtypes.StoreKey, - oracletypes.StoreKey, - }, - Deleted: []string{ - "fetchpriceconsumer", - }, - } - - // configure store loader that checks if version == upgradeHeight and applies store upgrades - app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) - } -} +func (app *MinitiaApp) RegisterUpgradeHandlers(cfg module.Configurator) {} diff --git a/go.mod b/go.mod index 1592ac4..981a599 100644 --- a/go.mod +++ b/go.mod @@ -21,10 +21,10 @@ require ( github.com/cometbft/cometbft v0.38.5 github.com/cosmos/cosmos-db v1.0.0 github.com/cosmos/cosmos-proto v1.0.0-beta.4 - github.com/cosmos/cosmos-sdk v0.50.3 + github.com/cosmos/cosmos-sdk v0.50.4 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogoproto v1.4.11 - github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.0.0 + github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.0.1 github.com/cosmos/ibc-apps/modules/async-icq/v8 v8.0.0 github.com/cosmos/ibc-go/modules/capability v1.0.0 github.com/cosmos/ibc-go/v8 v8.0.0 @@ -33,7 +33,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/initia-labs/OPinit v0.2.0-beta.1 - github.com/initia-labs/initia v0.2.0-beta.7.1 + github.com/initia-labs/initia v0.2.0-beta.7.0.20240307103405-3f582cc4f665 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 github.com/rakyll/statik v0.1.7 @@ -217,7 +217,6 @@ replace ( github.com/cosmos/iavl => github.com/initia-labs/iavl v0.0.0-20240208034922-5d81c449d4c0 - github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 => github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.0.1-0.20240124225747-f055ce5b405c github.com/cosmos/ibc-apps/modules/async-icq/v8 => github.com/cosmos/ibc-apps/modules/async-icq/v8 v8.0.1-0.20240124225747-f055ce5b405c // dgrijalva/jwt-go is deprecated and doesn't receive security updates. diff --git a/go.sum b/go.sum index d620d16..62e0a41 100644 --- a/go.sum +++ b/go.sum @@ -372,8 +372,8 @@ github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= -github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.0.1-0.20240124225747-f055ce5b405c h1:ugHoyxZFAUCjkpuUhjNWMF5E4H+8tjibJYRL6RZBZUw= -github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.0.1-0.20240124225747-f055ce5b405c/go.mod h1:82hPO/tRawbuFad2gPwChvpZ0JEIoNi91LwVneAYCeM= +github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.0.1 h1:BHn+JWZILxkUT9IrlP1ctUfo9ENGi+EmiZ9om1XSHIw= +github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 v8.0.1/go.mod h1:82hPO/tRawbuFad2gPwChvpZ0JEIoNi91LwVneAYCeM= github.com/cosmos/ibc-apps/modules/async-icq/v8 v8.0.1-0.20240124225747-f055ce5b405c h1:44QAwcoTqB1jT6p7IogVKZwdKVZykX2fg+iU0Q/LTZ0= github.com/cosmos/ibc-apps/modules/async-icq/v8 v8.0.1-0.20240124225747-f055ce5b405c/go.mod h1:D3Q380FpWRFtmUQWLosPxachi6w24Og2t5u/Tww5wtY= github.com/cosmos/ibc-go/modules/capability v1.0.0 h1:r/l++byFtn7jHYa09zlAdSeevo8ci1mVZNO9+V0xsLE= @@ -727,10 +727,10 @@ github.com/initia-labs/cosmos-sdk v0.0.0-20240123082052-77e8b246064a h1:wD9ynlE7 github.com/initia-labs/cosmos-sdk v0.0.0-20240123082052-77e8b246064a/go.mod h1:tlrkY1sntOt1q0OX/rqF0zRJtmXNoffAS6VFTcky+w8= github.com/initia-labs/iavl v0.0.0-20240208034922-5d81c449d4c0 h1:GQ7/UD5mB6q104roqZK5jxb6ff9sBk0/uwFxgERQIaU= github.com/initia-labs/iavl v0.0.0-20240208034922-5d81c449d4c0/go.mod h1:CmTGqMnRnucjxbjduneZXT+0vPgNElYvdefjX2q9tYc= -github.com/initia-labs/initia v0.2.0-beta.7.1 h1:m72+y/htXXYuV6Wg0IROQtC/ZZyF0Y1BD3DmeWmLhSQ= -github.com/initia-labs/initia v0.2.0-beta.7.1/go.mod h1:sCqG5B+KaauSRp7o9DceNtTcGayyvqSEz30+HuYmBs0= -github.com/initia-labs/initiavm v0.2.0-beta.2.1 h1:L6UHFS5LB/somGfgMO0N2tpkas0v+lTBIMkDvZw+S1M= -github.com/initia-labs/initiavm v0.2.0-beta.2.1/go.mod h1:KTdC3W2wgeOiL0YYIu6MEvK4j8envSpLjH14sMKMD6k= +github.com/initia-labs/initia v0.2.0-beta.7.0.20240307103405-3f582cc4f665 h1:2tukXqPBd0/BUtOfxDJZOeLyLXZea/F6mTAh4U013gc= +github.com/initia-labs/initia v0.2.0-beta.7.0.20240307103405-3f582cc4f665/go.mod h1:lEjADkvlfGu0KOu8TdxJ2uF40lSEFSbXFv+/LLDNFLo= +github.com/initia-labs/movevm v0.0.0-20240228123400-2951cc64c945 h1:aoPuGF00i+yo39QfiJwLv7QMnhaGJJ/jzkW2NNAas9o= +github.com/initia-labs/movevm v0.0.0-20240228123400-2951cc64c945/go.mod h1:1EyJ06+Hn43MfaXZ+30a2gmhS5zjqiFF8oSF5CHai28= github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls= github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -844,8 +844,6 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/novifinancial/serde-reflection/serde-generate/runtime/golang v0.0.0-20220519162058-e5cd3c3b3f3a h1:oMG8C4E7DFkat7WQicw4JNa/dYUaqO7RvLPbkFdADIA= -github.com/novifinancial/serde-reflection/serde-generate/runtime/golang v0.0.0-20220519162058-e5cd3c3b3f3a/go.mod h1:NrRYJCFtaewjIRr4B9V2AyWsAEMW0Zqdjs8Bm+bACbM= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= From 3a9a05935eb25f14f5ce23bc54a38a4b05aeafb3 Mon Sep 17 00:00:00 2001 From: beer-1 <147697694+beer-1@users.noreply.github.com> Date: Thu, 7 Mar 2024 20:39:11 +0900 Subject: [PATCH 2/2] change to ack like normal wasm chain --- app/ibc-hooks/hooks.go | 6 +++--- app/ibc-hooks/util.go | 10 ++++++++-- app/ibc-hooks/util_test.go | 9 ++------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/ibc-hooks/hooks.go b/app/ibc-hooks/hooks.go index 301ac9e..a355029 100644 --- a/app/ibc-hooks/hooks.go +++ b/app/ibc-hooks/hooks.go @@ -33,7 +33,7 @@ func (h WasmHooks) OnRecvPacketOverride(im ibchooks.IBCMiddleware, ctx sdk.Conte return h.onRecvIcs20Packet(ctx, im, packet, relayer, ics20Data) } - if isIcs721, ics721Data := isIcs721Packet(packet.GetData(), packet.SourcePort); isIcs721 { + if isIcs721, ics721Data := isIcs721Packet(packet.GetData()); isIcs721 { return h.onRecvIcs721Packet(ctx, im, packet, relayer, ics721Data) } @@ -45,7 +45,7 @@ func (h WasmHooks) OnAcknowledgementPacketOverride(im ibchooks.IBCMiddleware, ct return h.onAckIcs20Packet(ctx, im, packet, acknowledgement, relayer, ics20Data) } - if isIcs721, ics721Data := isIcs721Packet(packet.GetData(), packet.DestinationPort); isIcs721 { + if isIcs721, ics721Data := isIcs721Packet(packet.GetData()); isIcs721 { return h.onAckIcs721Packet(ctx, im, packet, acknowledgement, relayer, ics721Data) } @@ -57,7 +57,7 @@ func (h WasmHooks) OnTimeoutPacketOverride(im ibchooks.IBCMiddleware, ctx sdk.Co return h.onTimeoutIcs20Packet(ctx, im, packet, relayer, ics20Data) } - if isIcs721, ics721Data := isIcs721Packet(packet.GetData(), packet.DestinationPort); isIcs721 { + if isIcs721, ics721Data := isIcs721Packet(packet.GetData()); isIcs721 { return h.onTimeoutIcs721Packet(ctx, im, packet, relayer, ics721Data) } diff --git a/app/ibc-hooks/util.go b/app/ibc-hooks/util.go index 4815bcd..0c9382a 100644 --- a/app/ibc-hooks/util.go +++ b/app/ibc-hooks/util.go @@ -37,8 +37,14 @@ func isIcs20Packet(packetData []byte) (isIcs20 bool, ics20data transfertypes.Fun return true, data } -func isIcs721Packet(packetData []byte, counterPartyPort string) (isIcs721 bool, ics721data nfttransfertypes.NonFungibleTokenPacketData) { - if data, err := nfttransfertypes.DecodePacketData(packetData, counterPartyPort); err != nil { +const wasmPortPrefix = "wasm." + +func isIcs721Packet(packetData []byte) (isIcs721 bool, ics721data nfttransfertypes.NonFungibleTokenPacketData) { + // Use wasm port prefix to ack like normal wasm chain. + // + // initia l1 is handling encoding and decoding depends on port id, + // so miniwasm should ack like normal wasm chain. + if data, err := nfttransfertypes.DecodePacketData(packetData, wasmPortPrefix); err != nil { return false, data } else { return true, data diff --git a/app/ibc-hooks/util_test.go b/app/ibc-hooks/util_test.go index b2a275f..c8c0fe9 100644 --- a/app/ibc-hooks/util_test.go +++ b/app/ibc-hooks/util_test.go @@ -35,17 +35,12 @@ func Test_isIcs20Packet(t *testing.T) { func Test_isIcs721Packet(t *testing.T) { nftTransferMsg := nfttransfertypes.NewNonFungibleTokenPacketData("class_id", "uri", "data", []string{"1", "2", "3"}, []string{"uri1", "uri2", "uri3"}, []string{"data1", "data2", "data3"}, "sender", "receiver", "memo") - ok, _nftTransferMsg := isIcs721Packet(nftTransferMsg.GetBytes("ics721"), "ics721") - require.True(t, ok) - require.Equal(t, nftTransferMsg, _nftTransferMsg) - - // isWasmPacket - ok, _nftTransferMsg = isIcs721Packet(nftTransferMsg.GetBytes("wasm.contract"), "wasm.contract") + ok, _nftTransferMsg := isIcs721Packet(nftTransferMsg.GetBytes(wasmPortPrefix)) require.True(t, ok) require.Equal(t, nftTransferMsg, _nftTransferMsg) transferMsg := transfertypes.NewFungibleTokenPacketData("denom", "1000000", "0x1", "0x2", "memo") - ok, _ = isIcs721Packet(transferMsg.GetBytes(), "ics721") + ok, _ = isIcs721Packet(transferMsg.GetBytes()) require.False(t, ok) }