Skip to content

Commit

Permalink
Merge pull request #183 from quasar-finance/feature/ibc-transfer-modu…
Browse files Browse the repository at this point in the history
…le_refactor

IBC transfer module refactor
  • Loading branch information
Ehsan-saradar authored Dec 14, 2022
2 parents 373359e + 83160b9 commit 68dc8b7
Show file tree
Hide file tree
Showing 52 changed files with 2,770 additions and 288 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,8 @@ See [this document](LOCALNET.md) to run a Quasar testnet locally.
- [Ignite docs](https://docs.ignite.com)
- [Cosmos SDK docs](https://docs.cosmos.network)
- [Developer Chat](https://discord.gg/H6wGTY8sxw)


## Attributions

* Neutron: IBC Transfer module wrapper to enable re-entry into smart contracts after ibc transfer (`x/transfer`)
20 changes: 13 additions & 7 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

appParams "github.com/quasarlabs/quasarnode/app/params"
"github.com/quasarlabs/quasarnode/app/upgrades"
"github.com/quasarlabs/quasarnode/decorators"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
Expand Down Expand Up @@ -451,8 +452,6 @@ func New(
govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.wasmKeeper, enabledProposals))
}

// TODO AUDIT Above lines

// IBC Modules & Keepers

app.TransferKeeper = ibctransferkeeper.NewKeeper(
Expand Down Expand Up @@ -493,10 +492,7 @@ func New(
icaControllerIBCModule := icacontroller.NewIBCModule(app.ICAControllerKeeper, intergammIBCModule)
icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper)

decoratedTransferIBCModule := intergammmodule.NewIBCTransferModuleDecorator(
&transferIbcModule,
app.IntergammKeeper,
)
// TODO AUDIT Above lines

// Create evidence Keeper for to register the IBC light client misbehaviour evidence route
evidenceKeeper := evidencekeeper.NewKeeper(
Expand Down Expand Up @@ -612,6 +608,16 @@ func New(
wasmOpts...,
)

var decoratedTransferIBCModule ibcporttypes.IBCModule
decoratedTransferIBCModule = decorators.NewIBCTransferIntergammDecorator(
app.IntergammKeeper,
transferIbcModule,
)
decoratedTransferIBCModule = decorators.NewIBCTransferWasmDecorator(
&app.wasmKeeper,
decoratedTransferIBCModule,
)

// Set Intergamm hooks

// IBC
Expand Down Expand Up @@ -715,7 +721,7 @@ func New(

// NOTE: we may consider parsing `appOpts` inside module constructors. For the moment
// we prefer to be more strict in what arguments the modules expect.
var skipGenesisInvariants = cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))
skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))

// NOTE: Any module instantiated in the module manager that is later modified
// must be passed by reference here.
Expand Down
128 changes: 128 additions & 0 deletions decorators/ibc_transfer_intergamm_decorator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package decorators

import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
ibctransfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types"
intergammkeeper "github.com/quasarlabs/quasarnode/x/intergamm/keeper"
intergammtypes "github.com/quasarlabs/quasarnode/x/intergamm/types"
"github.com/tendermint/tendermint/libs/log"
)

var _ porttypes.IBCModule = IBCTransferIntergammDecorator{}

// IBCModule implements the ICS26 interface for interchain accounts controller chains
type IBCTransferIntergammDecorator struct {
k *intergammkeeper.Keeper
porttypes.IBCModule
}

// NewIBCModule creates a new IBCModule given the intergamm keeper
func NewIBCTransferIntergammDecorator(k *intergammkeeper.Keeper, m porttypes.IBCModule) IBCTransferIntergammDecorator {
return IBCTransferIntergammDecorator{
k: k,
IBCModule: m,
}
}

// OnChanOpenAck implements the IBCModule.OnChanOpenAck
func (im IBCTransferIntergammDecorator) OnChanOpenAck(
ctx sdk.Context,
portID,
channelID string,
counterpartyChannelID string,
counterpartyVersion string,
) error {
connectionID, _, err := im.k.GetChannelKeeper(ctx).GetChannelConnection(ctx, portID, channelID)
if err != nil {
return err
}
destinationChain, _ := im.k.GetChainID(ctx, connectionID)

_, found := im.k.GetPortDetail(ctx, destinationChain, portID)
// Don't update the im.k.SetPortDetail. As updating the new channel id will cause denom changes
// to ibc token transfer. This make sure we use constant value of channel id for a given connection id/chain id
if !found {
pi := intergammtypes.PortInfo{
PortID: portID,
ChannelID: channelID,
CounterpartyChannelID: counterpartyChannelID,
ConnectionID: connectionID,
}
im.k.SetPortDetail(ctx, pi)
im.logger(ctx).Info(
"created new port detail",
"port_detail", pi,
)
}

return im.IBCModule.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion)
}

// OnAcknowledgementPacket implements the IBCModule.OnAcknowledgementPacket
func (im IBCTransferIntergammDecorator) OnAcknowledgementPacket(
ctx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
) error {
err := im.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer)
if err != nil {
return err
}

transferPacket, err := unmarshalTransferPacket(packet)
if err != nil {
return err
}
ack, err := unmarshalAcknowledgement(acknowledgement)
if err != nil {
return err
}

return im.k.HandleIbcTransferAcknowledgement(ctx, packet.GetSequence(), transferPacket, ack)
}

// OnTimeoutPacket implements the IBCModule interface.
func (im IBCTransferIntergammDecorator) OnTimeoutPacket(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
) error {
err := im.IBCModule.OnTimeoutPacket(ctx, packet, relayer)
if err != nil {
return err
}

transferPacket, err := unmarshalTransferPacket(packet)
if err != nil {
return err
}

return im.k.HandleIbcTransferTimeout(ctx, packet.GetSequence(), transferPacket)
}

func (im IBCTransferIntergammDecorator) logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("decorator", "IBCTransferIntergammDecorator")
}

func unmarshalTransferPacket(packet channeltypes.Packet) (ibctransfertypes.FungibleTokenPacketData, error) {
var transferPacket ibctransfertypes.FungibleTokenPacketData
err := intergammtypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &transferPacket)
if err != nil {
return transferPacket, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}

return transferPacket, nil
}

func unmarshalAcknowledgement(acknowledgement []byte) (channeltypes.Acknowledgement, error) {
var ack channeltypes.Acknowledgement
err := intergammtypes.ModuleCdc.UnmarshalJSON(acknowledgement, &ack)
if err != nil {
return ack, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err)
}
return ack, nil
}
156 changes: 156 additions & 0 deletions decorators/ibc_transfer_wasm_decorator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package decorators

import (
"github.com/CosmWasm/wasmd/x/wasm"
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
porttypes "github.com/cosmos/ibc-go/v3/modules/core/05-port/types"
"github.com/tendermint/tendermint/libs/log"
)

var _ porttypes.IBCModule = IBCTransferWasmDecorator{}

// IBCTransferWasmDecorator is a decorator for ibc transfer module that will
// pass ack and timeout callbacks of wasm contracts that were the sender of packet to them.
// Note that the contracts should implement the IBC interface to receive the callbacks
// otherwise they won't receive any callbacks from this decorator and will be treated like
// a regular account.
// NOTICE: Potential Security Issue
type IBCTransferWasmDecorator struct {
k *wasm.Keeper
porttypes.IBCModule
}

// NewIBCTransferWasmDecorator returns a new IBCTransferWasmDecorator with the given wasm keeper and transfer ibc module.
func NewIBCTransferWasmDecorator(k *wasm.Keeper, m porttypes.IBCModule) IBCTransferWasmDecorator {
return IBCTransferWasmDecorator{
k: k,
IBCModule: m,
}
}

// OnAcknowledgementPacket implements the IBCModule.OnAcknowledgementPacket
func (im IBCTransferWasmDecorator) OnAcknowledgementPacket(
ctx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
relayer sdk.AccAddress,
) error {
err := im.IBCModule.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer)
if err != nil {
return err
}

transferPacket, err := unmarshalTransferPacket(packet)
if err != nil {
return err
}
ack, err := unmarshalAcknowledgement(acknowledgement)
if err != nil {
return err
}

contractAddr, err := sdk.AccAddressFromBech32(transferPacket.GetSender())
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to decode address from bech32: %v", err)
}
contractInfo := im.k.GetContractInfo(ctx, contractAddr)
// Skip if there's no contract with this address (it's a regular address) or the contract doesn't support IBC
if contractInfo == nil || contractInfo.IBCPortID == "" {
return nil
}

if !ack.Success() {
im.logger(ctx).Debug(
"passing an error acknowledgment to contract",
"contract_address", contractAddr,
"error", ack.GetError(),
)
}
err = im.k.OnAckPacket(ctx, contractAddr, wasmvmtypes.IBCPacketAckMsg{
Acknowledgement: wasmvmtypes.IBCAcknowledgement{Data: acknowledgement},
OriginalPacket: newWasmIBCPacket(packet),
Relayer: relayer.String(),
})
if err != nil {
im.logger(ctx).Error(
"contract returned error for acknowledgment",
"contract_address", contractAddr,
"error", err,
)
return sdkerrors.Wrap(err, "contract returned error for acknowledgment")
}

return nil
}

// OnTimeoutPacket implements the IBCModule.OnTimeoutPacket
func (im IBCTransferWasmDecorator) OnTimeoutPacket(
ctx sdk.Context,
packet channeltypes.Packet,
relayer sdk.AccAddress,
) error {
err := im.IBCModule.OnTimeoutPacket(ctx, packet, relayer)
if err != nil {
return err
}

transferPacket, err := unmarshalTransferPacket(packet)
if err != nil {
return err
}

contractAddr, err := sdk.AccAddressFromBech32(transferPacket.GetSender())
if err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "failed to decode address from bech32: %v", err)
}
contractInfo := im.k.GetContractInfo(ctx, contractAddr)
if contractInfo.IBCPortID == "" {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "contract %s is not a valid IBC contract", contractAddr)
}
// Skip if there's no contract with this address (it's a regular address) or the contract doesn't support IBC
if contractInfo == nil || contractInfo.IBCPortID == "" {
return nil
}

err = im.k.OnTimeoutPacket(ctx, contractAddr, wasmvmtypes.IBCPacketTimeoutMsg{
Packet: newWasmIBCPacket(packet),
Relayer: relayer.String(),
})
if err != nil {
im.logger(ctx).Error(
"contract returned error for timeout",
"contract_address", contractAddr,
"error", err,
)
return sdkerrors.Wrap(err, "contract returned error for timeout")
}

return nil
}

func newWasmIBCPacket(packet channeltypes.Packet) wasmvmtypes.IBCPacket {
timeout := wasmvmtypes.IBCTimeout{
Timestamp: packet.TimeoutTimestamp,
}
if !packet.TimeoutHeight.IsZero() {
timeout.Block = &wasmvmtypes.IBCTimeoutBlock{
Height: packet.TimeoutHeight.RevisionHeight,
Revision: packet.TimeoutHeight.RevisionNumber,
}
}

return wasmvmtypes.IBCPacket{
Data: packet.Data,
Src: wasmvmtypes.IBCEndpoint{ChannelID: packet.SourceChannel, PortID: packet.SourcePort},
Dest: wasmvmtypes.IBCEndpoint{ChannelID: packet.DestinationChannel, PortID: packet.DestinationPort},
Sequence: packet.Sequence,
Timeout: timeout,
}
}

func (im IBCTransferWasmDecorator) logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("decorator", "IBCTransferWasmCallback")
}
Loading

0 comments on commit 68dc8b7

Please sign in to comment.