Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ibc): memo fallback address #2442

Merged
merged 8 commits into from
Mar 1, 2024
3 changes: 3 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,9 @@ func New(
packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, // forward timeout
packetforwardkeeper.DefaultRefundTransferPacketTimeoutTimestamp, // refund timeout
)
// NOTE: uics20 module must be the last middleware. We need to be sure there is no other code
// that will manipulate packet between the UICS20 middleware (which executes transfer hooks)
// and the transfer app.
transferStack = uics20.NewICS20Module(transferStack, appCodec,
app.UIbcQuotaKeeperB,
leveragekeeper.NewMsgServerImpl(app.LeverageKeeper))
Expand Down
8 changes: 8 additions & 0 deletions proto/umee/uibc/v1/uibc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ option (gogoproto.messagename_all) = true;
message ICS20Memo {
// messages is a list of `sdk.Msg`s that will be executed when handling ICS20 transfer.
repeated google.protobuf.Any messages = 1;
// fallback_addr [optional] is a bech23 account address used to overwrite the original ICS20
// recipient when:
// 1. it is defined
// 2. and memo is can be properly deserialized into this structure
// 3. and `messages` processes failed.
// When memo can't be properly deserialized, then the ICS20 processing will continue to other
// middlewares.
string fallback_addr = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
}

// DecCoinSymbol extends the Cosmos SDK DecCoin type and adds symbol name.
Expand Down
6 changes: 6 additions & 0 deletions x/uibc/ics20.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx"
ics20types "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
Expand All @@ -12,6 +13,11 @@
return tx.UnpackInterfaces(unpacker, m.Messages)
}

// GetMsgs unpacks messages into []sdk.Msg
func (m ICS20Memo) GetMsgs() ([]sdk.Msg, error) {
return tx.GetMsgs(m.Messages, "memo messages")

Check warning on line 18 in x/uibc/ics20.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/ics20.go#L17-L18

Added lines #L17 - L18 were not covered by tests
}

// ExtractDenomFromPacketOnRecv takes a packet with a valid ICS20 token data in the Data field
// and returns the denom as represented in the local chain.
func ExtractDenomFromPacketOnRecv(packet ibcexported.PacketI, denom string) string {
Expand Down
97 changes: 75 additions & 22 deletions x/uibc/uibc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 55 additions & 19 deletions x/uibc/uics20/ibc_module.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package uics20

import (
"strings"
"encoding/json"

sdkerrors "cosmossdk.io/errors"
"github.com/cometbft/cometbft/libs/log"
Expand All @@ -13,7 +13,6 @@
"github.com/cosmos/ibc-go/v7/modules/core/exported"

ltypes "github.com/umee-network/umee/v6/x/leverage/types"
"github.com/umee-network/umee/v6/x/uibc/gmp"
"github.com/umee-network/umee/v6/x/uibc/quota"
)

Expand Down Expand Up @@ -42,38 +41,58 @@
}

// OnRecvPacket is called when a receiver chain receives a packet from SendPacket.
// 1. record IBC quota
// 2. Try to unpack and prepare memo. If memo has a correct structure, and fallback addr is
// defined but malformed, we cancel the transfer (otherwise would not be able to use it
// correctly).
// 3. If memo has a correct structure, but memo.messages can't be unpack or don't pass
// validation, then we continue with the transfer and overwrite the original receiver to
// fallback_addr if it's defined.
// 4. Execute the downstream middleware and the transfer app.
// 5. Execute hooks. If hook execution fails, we don't use the the fallback_addr nor ignore the
// transfer. This is because there could be other middlewares that are already executed.
func (im ICS20Module) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, relayer sdk.AccAddress,
) exported.Acknowledgement {
ftData, err := deserializeFTData(im.cdc, packet)
if err != nil {
return channeltypes.NewErrorAcknowledgement(err)
}
qk := im.kb.Keeper(&ctx)
if ackResp := qk.IBCOnRecvPacket(ftData, packet); ackResp != nil && !ackResp.Success() {
quotaKeeper := im.kb.Keeper(&ctx)
if ackResp := quotaKeeper.IBCOnRecvPacket(ftData, packet); ackResp != nil && !ackResp.Success() {

Check warning on line 61 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L60-L61

Added lines #L60 - L61 were not covered by tests
return ackResp
}

// NOTE: IBC hooks must be the last middleware - just the transfer app.
// MemoHandler may update amoount in the message, because the received token amount may be
// smaller than the amount originally sent (various fees). We need to be sure that there is
// no other middleware that can change packet data or amounts.

mh := MemoHandler{im.cdc, im.leverage}
msgs, overwriteReceiver, events, err := mh.onRecvPacketPre(&ctx, packet, ftData)
if err != nil {
return channeltypes.NewErrorAcknowledgement(err)

Check warning on line 73 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L70-L73

Added lines #L70 - L73 were not covered by tests
}
if overwriteReceiver != nil {
ftData.Receiver = overwriteReceiver.String()
msgs = nil // we don't want to execute hooks when we set fallback address.
events = append(events, "overwrite receiver to fallback_addr="+ftData.Receiver)
if packet.Data, err = json.Marshal(ftData); err != nil {
return channeltypes.NewErrorAcknowledgement(err)

Check warning on line 80 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L75-L80

Added lines #L75 - L80 were not covered by tests
}
}

// call transfer module app
ack := im.IBCModule.OnRecvPacket(ctx, packet, relayer)
if !ack.Success() {
return ack
}

if ftData.Memo != "" {
logger := recvPacketLogger(&ctx)
if strings.EqualFold(ftData.Sender, gmp.DefaultGMPAddress) {
logger.Info("handle the memo with gmp")
gh := gmp.NewHandler()
if err := gh.OnRecvPacket(ctx, packet, ftData); err != nil {
logger.Error("can't handle ICS20 memo on GMP", "err", err)
}
} else {
mh := MemoHandler{im.cdc, im.leverage}
if err := mh.onRecvPacket(&ctx, packet, ftData); err != nil {
logger.Error("can't handle ICS20 memo", "err", err)
}
}
if err := mh.dispatchMemoMsgs(&ctx, msgs); err != nil {
events = append(events, "can't handle ICS20 memo err = "+err.Error())

Check warning on line 91 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L90-L91

Added lines #L90 - L91 were not covered by tests
}

im.emitEvents(ctx.EventManager(), recvPacketLogger(&ctx), "ics20-memo-hook", events)

Check warning on line 94 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L94

Added line #L94 was not covered by tests

return ack
}

Expand Down Expand Up @@ -104,12 +123,29 @@
ftData, err := deserializeFTData(im.cdc, packet)
if err != nil {
// we only log error, because we want to propagate the ack to other layers.
ctx.Logger().Error("can't revert quota update", "err", err)
ctx.Logger().With("scope", "ics20-OnAckErr").Error("can't revert quota update", "err", err)

Check warning on line 126 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L126

Added line #L126 was not covered by tests
}
qk := im.kb.Keeper(ctx)
qk.IBCRevertQuotaUpdate(ftData.Amount, ftData.Denom)
}

func (im ICS20Module) emitEvents(em *sdk.EventManager, logger log.Logger, topic string, events []string) {
attributes := make([]sdk.Attribute, len(events))
key := topic + "-context"
for i, s := range events {

Check warning on line 135 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L132-L135

Added lines #L132 - L135 were not covered by tests
// it's ok that all events have the same key. This is how ibc-apps are dealing with events.
attributes[i] = sdk.NewAttribute(key, s)

Check warning on line 137 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L137

Added line #L137 was not covered by tests
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
}
logger.Debug("Handle ICS20 memo", "events", events)

Check warning on line 139 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L139

Added line #L139 was not covered by tests

em.EmitEvents(sdk.Events{
sdk.NewEvent(
topic,
attributes...,
),
})

Check warning on line 146 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L141-L146

Added lines #L141 - L146 were not covered by tests
}

func deserializeFTData(cdc codec.JSONCodec, packet channeltypes.Packet,
) (d ics20types.FungibleTokenPacketData, err error) {
if err = cdc.UnmarshalJSON(packet.GetData(), &d); err != nil {
Expand Down
Loading
Loading