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: handle fallback address with memo execution and adjust message coins #2443

Merged
merged 7 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 14 additions & 32 deletions x/uibc/gmp/gmp_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,87 +2,69 @@ package gmp

import (
"encoding/json"
"fmt"

"cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
ics20types "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"

"github.com/umee-network/umee/v6/x/uibc"
)

type Handler struct {
}

var _ GeneralMessageHandler = Handler{}

func NewHandler() *Handler {
return &Handler{}
}

func (h Handler) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data ics20types.FungibleTokenPacketData,
func (h Handler) OnRecvPacket(ctx sdk.Context, coinReceived sdk.Coin, memo string, receiver sdk.AccAddress,
) error {
logger := ctx.Logger().With("handler", "gmp_handler")
var msg Message
var err error

if err = json.Unmarshal([]byte(data.GetMemo()), &msg); err != nil {
logger.With(err).Error("cannot unmarshal memo")
if err = json.Unmarshal([]byte(memo), &msg); err != nil {
logger.Error("cannot unmarshal memo", "err", err)
return err
}

switch msg.Type {
case TypeGeneralMessage:
err := h.HandleGeneralMessage(ctx, msg.SourceAddress, msg.SourceAddress, data.Receiver, msg.Payload)
err := h.HandleGeneralMessage(ctx, msg.SourceAddress, msg.SourceAddress, receiver, msg.Payload)
if err != nil {
logger.Error("err at HandleGeneralMessage", err)
}
case TypeGeneralMessageWithToken:
// parse the transfer amount
amt, ok := sdk.NewIntFromString(data.Amount)
if !ok {
return errors.Wrapf(
ics20types.ErrInvalidAmount,
"unable to parse transfer amount (%s) into sdk.Int",
data.Amount,
)
}
denom := uibc.ExtractDenomFromPacketOnRecv(packet, data.Denom)
err := h.HandleGeneralMessageWithToken(ctx, msg.SourceAddress, msg.SourceAddress, data.Receiver,
msg.Payload, sdk.NewCoin(denom, amt))

err := h.HandleGeneralMessageWithToken(
ctx, msg.SourceAddress, msg.SourceAddress, receiver, msg.Payload, coinReceived)
if err != nil {
logger.Error("err at HandleGeneralMessageWithToken", err)
}
default:
logger.With(fmt.Errorf("unrecognized message type: %d", msg.Type)).Error("unrecognized gmp message")
logger.Error("unrecognized gmp message type: %d", msg.Type)
}

return err
}

func (h Handler) HandleGeneralMessage(ctx sdk.Context, srcChain, srcAddress string, destAddress string,
func (h Handler) HandleGeneralMessage(ctx sdk.Context, srcChain, srcAddress string, receiver sdk.AccAddress,
payload []byte) error {
ctx.Logger().Info("HandleGeneralMessage called",
"srcChain", srcChain,
"srcAddress", srcAddress,
"destAddress", destAddress,
"receiver", receiver,
"payload", payload,
"handler", "gmp-handler",
)
return nil
}

func (h Handler) HandleGeneralMessageWithToken(ctx sdk.Context, srcChain, srcAddress string, destAddress string,
payload []byte, coin sdk.Coin) error {
func (h Handler) HandleGeneralMessageWithToken(ctx sdk.Context, srcChain, srcAddress string,
receiver sdk.AccAddress, payload []byte, coin sdk.Coin) error {

ctx.Logger().Info("HandleGeneralMessageWithToken called",
"srcChain", srcChain,
"srcAddress", srcAddress,
"destAddress", destAddress,
"receiver", receiver,
"payload", payload,
"coin", coin,
"handler", "gmp-handler",
"handler", "gmp-token-handler",
)
return nil
}
8 changes: 0 additions & 8 deletions x/uibc/gmp/types.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
package gmp
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved

import sdk "github.com/cosmos/cosmos-sdk/types"

type GeneralMessageHandler interface {
HandleGeneralMessage(ctx sdk.Context, srcChain, srcAddress string, destAddress string, payload []byte) error
HandleGeneralMessageWithToken(ctx sdk.Context, srcChain, srcAddress string, destAddress string,
payload []byte, coin sdk.Coin) error
}

const (
DefaultGMPAddress = "axelar1dv4u5k73pzqrxlzujxg3qp8kvc3pje7jtdvu72npnt5zhq05ejcsn5qme5"
)
Expand Down
50 changes: 37 additions & 13 deletions x/uibc/uics20/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,32 +67,56 @@
// 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 {
mh := MemoHandler{cdc: im.cdc, leverage: im.leverage}
events, err := mh.onRecvPacketPrepare(&ctx, packet, ftData)
if err != nil && err != errMemoValidation {

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L70 - L72 were not covered by tests
return channeltypes.NewErrorAcknowledgement(err)
}
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)
var transferCtx = ctx
var ctxFlush func()
if mh.fallbackReceiver != nil {
if err == errMemoValidation {
ftData.Receiver = mh.fallbackReceiver.String()
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 82 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L75 - L82 were not covered by tests
}
} else {

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L84

Added line #L84 was not covered by tests
// create a new cache context: we have a fallback receiver and memo is valid.
// we will discard it when the execution fails to move the funds to the fallbackAddress.
transferCtx, ctxFlush = ctx.CacheContext()

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L87

Added line #L87 was not covered by tests
}
}
execCtx, execCtxFlush := transferCtx.CacheContext()

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L90

Added line #L90 was not covered by tests

// call transfer module app
ack := im.IBCModule.OnRecvPacket(ctx, packet, relayer)
ack := im.IBCModule.OnRecvPacket(transferCtx, packet, relayer)

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L93

Added line #L93 was not covered by tests
if !ack.Success() {
return ack
goto end

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L95

Added line #L95 was not covered by tests
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
}

if err := mh.dispatchMemoMsgs(&ctx, msgs); err != nil {
if err = mh.execute(&execCtx); err != nil {

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L98

Added line #L98 was not covered by tests
events = append(events, "can't handle ICS20 memo err = "+err.Error())
// if we created a new cache context, then we can discard it, and repeate the transfer to
// the fallback address
if mh.fallbackReceiver != nil {
ctxFlush = nil // discard the context
ftData.Receiver = mh.fallbackReceiver.String()
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 107 in x/uibc/uics20/ibc_module.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L102-L107

Added lines #L102 - L107 were not covered by tests
}
ack = im.IBCModule.OnRecvPacket(ctx, packet, relayer)

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L109

Added line #L109 was not covered by tests
}
} else {
execCtxFlush()

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L111-L112

Added lines #L111 - L112 were not covered by tests
}

end:
if ctxFlush != nil {
ctxFlush()

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

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/ibc_module.go#L116-L117

Added lines #L116 - L117 were not covered by tests
}
im.emitEvents(ctx.EventManager(), recvPacketLogger(&ctx), "ics20-memo-hook", events)

return ack
}

Expand Down
111 changes: 70 additions & 41 deletions x/uibc/uics20/memo_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import (
"errors"
"fmt"
"strings"

sdkerrors "cosmossdk.io/errors"
"github.com/cosmos/cosmos-sdk/codec"
Expand All @@ -12,76 +13,99 @@

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

var errMemoValidation = errors.New("ics20 memo validation error")

type MemoHandler struct {
cdc codec.JSONCodec
leverage ltypes.MsgServer

isGMP bool
msgs []sdk.Msg
memo string
received sdk.Coin

receiver sdk.AccAddress
fallbackReceiver sdk.AccAddress
}

// onRecvPacketPre parses transfer Memo field and prepares the MemoHandler.
// See ICS20Module.OnRecvPacket for the flow
func (mh MemoHandler) onRecvPacketPre(
func (mh *MemoHandler) onRecvPacketPrepare(
ctx *sdk.Context, packet ibcexported.PacketI, ftData ics20types.FungibleTokenPacketData,
) ([]sdk.Msg, sdk.AccAddress, []string, error) {
) ([]string, error) {

Check warning on line 38 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L38

Added line #L38 was not covered by tests
var events []string
var err error
mh.memo = ftData.Memo
amount, ok := sdk.NewIntFromString(ftData.Amount)
if !ok { // must not happen
return nil, fmt.Errorf("can't parse transfer amount: %s", ftData.Amount)

Check warning on line 44 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L40-L44

Added lines #L40 - L44 were not covered by tests
}
ibcDenom := uibc.ExtractDenomFromPacketOnRecv(packet, ftData.Denom)
mh.received = sdk.NewCoin(ibcDenom, amount)
mh.receiver, err = sdk.AccAddressFromBech32(ftData.Receiver)
if err != nil { // must not happen
return nil, sdkerrors.Wrap(err, "can't parse ftData.Receiver bech32 address")

Check warning on line 50 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L46-L50

Added lines #L46 - L50 were not covered by tests
}

if strings.EqualFold(ftData.Sender, gmp.DefaultGMPAddress) {
events = append(events, "axelar GMP transaction")
mh.isGMP = true
return events, nil

Check warning on line 56 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L53-L56

Added lines #L53 - L56 were not covered by tests
}

memo, err := deserializeMemo(mh.cdc, []byte(ftData.Memo))
if err != nil {
recvPacketLogger(ctx).Debug("Not recognized ICS20 memo, ignoring hook execution", "err", err)
return nil, nil, nil, nil
return nil, nil

Check warning on line 62 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L62

Added line #L62 was not covered by tests
}
var msgs []sdk.Msg
var fallbackReceiver sdk.AccAddress
if memo.FallbackAddr != "" {
if fallbackReceiver, err = sdk.AccAddressFromBech32(memo.FallbackAddr); err != nil {
return nil, nil, nil,
if mh.fallbackReceiver, err = sdk.AccAddressFromBech32(memo.FallbackAddr); err != nil {
return nil,

Check warning on line 66 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L65-L66

Added lines #L65 - L66 were not covered by tests
sdkerrors.Wrap(err, "ICS20 memo fallback_addr defined, but not formatted correctly")
}
if mh.fallbackReceiver.Equals(mh.receiver) {
mh.fallbackReceiver = nil

Check warning on line 70 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L69-L70

Added lines #L69 - L70 were not covered by tests
}
}

msgs, err = memo.GetMsgs()
mh.msgs, err = memo.GetMsgs()

Check warning on line 74 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L74

Added line #L74 was not covered by tests
if err != nil {
e := "ICS20 memo recognized, but can't unpack memo.messages: " + err.Error()
events = append(events, e)
return nil, fallbackReceiver, events, nil
return events, nil

Check warning on line 78 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L78

Added line #L78 was not covered by tests
}

receiver, err := sdk.AccAddressFromBech32(ftData.Receiver)
if err != nil { // must not happen
return nil, nil, nil, sdkerrors.Wrap(err, "can't parse ftData.Receiver bech32 address")
}
amount, ok := sdk.NewIntFromString(ftData.Amount)
if !ok { // must not happen
return nil, nil, nil, fmt.Errorf("can't parse transfer amount: %s [%w]", ftData.Amount, err)
}
ibcDenom := uibc.ExtractDenomFromPacketOnRecv(packet, ftData.Denom)
sentCoin := sdk.NewCoin(ibcDenom, amount)
if err := mh.validateMemoMsg(receiver, sentCoin, msgs); err != nil {
if err := mh.validateMemoMsg(); err != nil {

Check warning on line 81 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L81

Added line #L81 was not covered by tests
events = append(events, "memo.messages are not valid, err: "+err.Error())
return nil, fallbackReceiver, events, nil
return events, errMemoValidation

Check warning on line 83 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L83

Added line #L83 was not covered by tests
}

return msgs, fallbackReceiver, events, nil
return events, nil

Check warning on line 86 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L86

Added line #L86 was not covered by tests
}

// runs messages encoded in the ICS20 memo.
// NOTE: we fork the store and only commit if all messages pass. Otherwise the fork store
// is discarded.
func (mh MemoHandler) dispatchMemoMsgs(ctx *sdk.Context, msgs []sdk.Msg) error {
if len(msgs) == 0 {
func (mh MemoHandler) execute(ctx *sdk.Context) error {
logger := recvPacketLogger(ctx)
if mh.isGMP {
gh := gmp.NewHandler()
return gh.OnRecvPacket(*ctx, mh.received, mh.memo, mh.receiver)

Check warning on line 96 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L92-L96

Added lines #L92 - L96 were not covered by tests
}

if len(mh.msgs) == 0 {

Check warning on line 99 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L99

Added line #L99 was not covered by tests
return nil // quick return - we have nothing to handle
}

// Caching context so that we don't update the store in case of failure.
cacheCtx, flush := ctx.CacheContext()
logger := recvPacketLogger(ctx)
for _, m := range msgs {
if err := mh.handleMemoMsg(&cacheCtx, m); err != nil {
// ignore changes in cacheCtx and return
for _, m := range mh.msgs {
if err := mh.handleMemoMsg(ctx, m); err != nil {

Check warning on line 104 in x/uibc/uics20/memo_handler.go

View check run for this annotation

Codecov / codecov/patch

x/uibc/uics20/memo_handler.go#L103-L104

Added lines #L103 - L104 were not covered by tests
return sdkerrors.Wrapf(err, "error dispatching msg: %v", m)
}
logger.Debug("dispatching", "msg", m)
}
flush()
return nil
}

Expand All @@ -98,8 +122,8 @@
// - [MsgLiquidate]
// Signer of each message (account under charged with coins), must be the receiver of the ICS20
// transfer.
func (mh MemoHandler) validateMemoMsg(_receiver sdk.AccAddress, sent sdk.Coin, msgs []sdk.Msg) error {
msgLen := len(msgs)
func (mh MemoHandler) validateMemoMsg() error {
msgLen := len(mh.msgs)
if msgLen == 0 {
return nil
}
Expand All @@ -110,22 +134,22 @@
}

var (
asset sdk.Coin
asset *sdk.Coin
// collateral sdk.Coin
)
switch msg := msgs[0].(type) {
switch msg := mh.msgs[0].(type) {
case *ltypes.MsgSupplyCollateral:
asset = msg.Asset
asset = &msg.Asset
// collateral = asset
case *ltypes.MsgSupply:
asset = msg.Asset
asset = &msg.Asset
case *ltypes.MsgLiquidate:
asset = msg.Repayment
asset = &msg.Repayment
default:
return errMsg0Type
}

return assertSubCoins(sent, asset)
return adjustOperatedCoin(mh.received, asset)

/**
TODO: handlers v2
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -171,10 +195,15 @@
return err
}

func assertSubCoins(sent, operated sdk.Coin) error {
if sent.Denom != operated.Denom || sent.Amount.LT(operated.Amount) {
// adjustOperatedCoin assures that received and operated are of the same denom. Returns error if
// not. Moreover it updates operated amount if it is bigger than the received amount.
func adjustOperatedCoin(received sdk.Coin, operated *sdk.Coin) error {
if received.Denom != operated.Denom {
return errNoSubCoins
}
if received.Amount.LT(operated.Amount) {
operated.Amount = received.Amount
}
return nil
}

Expand Down
Loading
Loading