Skip to content
New issue

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

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

Already on GitHub? # 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 3 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

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
45 changes: 32 additions & 13 deletions x/uibc/uics20/ibc_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,32 +67,51 @@ func (im ICS20Module) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet,
// 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}
fallbackReceiver, events, err := mh.onRecvPacketPrepare(&ctx, packet, ftData)
if err != nil && err != memoValidationErr {
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 fallbackReceiver != nil {
if err == memoValidationErr {
ftData.Receiver = 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)
}
} else {
// 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()
}
}
execCtx, execCtxFlush := transferCtx.CacheContext()

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

if err := mh.dispatchMemoMsgs(&ctx, msgs); err != nil {
if err = mh.execute(&execCtx); err != nil {
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 ctxFlush != nil {
ctxFlush = nil // discard the context
ack = im.IBCModule.OnRecvPacket(ctx, packet, relayer)
}
} else {
execCtxFlush()
}

end:
if ctxFlush != nil {
ctxFlush()
}
im.emitEvents(ctx.EventManager(), recvPacketLogger(&ctx), "ics20-memo-hook", events)

return ack
}

Expand Down
107 changes: 68 additions & 39 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 memoValidationErr = 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) {
) (sdk.AccAddress, []string, error) {
var events []string
mh.memo = ftData.Memo
amount, ok := sdk.NewIntFromString(ftData.Amount)
if !ok { // must not happen
return nil, nil, fmt.Errorf("can't parse transfer amount: %s", ftData.Amount)
}
ibcDenom := uibc.ExtractDenomFromPacketOnRecv(packet, ftData.Denom)
mh.received = sdk.NewCoin(ibcDenom, amount)
mh.receiver, err = sdk.AccAddressFromBech32(ftData.Receiver)

Check failure on line 47 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed darwin-amd64

undefined: err

Check failure on line 47 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed linux-amd64

undefined: err

Check failure on line 47 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed linux-arm64

undefined: err

Check failure on line 47 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed darwin-arm64

undefined: err

Check failure on line 47 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / Run govulncheck

undefined: err

Check failure on line 47 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / test-app-non-determinism

undefined: err

Check failure on line 47 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / test-unit-cover

undefined: err

Check failure on line 47 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / Analyze

undefined: err
if err != nil { // must not happen

Check failure on line 48 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed darwin-amd64

undefined: err

Check failure on line 48 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed linux-amd64

undefined: err

Check failure on line 48 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed linux-arm64

undefined: err

Check failure on line 48 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed darwin-arm64

undefined: err

Check failure on line 48 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / Run govulncheck

undefined: err

Check failure on line 48 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / test-app-non-determinism

undefined: err

Check failure on line 48 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / test-unit-cover

undefined: err

Check failure on line 48 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / Analyze

undefined: err
return nil, nil, sdkerrors.Wrap(err, "can't parse ftData.Receiver bech32 address")

Check failure on line 49 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed darwin-amd64

undefined: err

Check failure on line 49 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed linux-amd64

undefined: err

Check failure on line 49 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed linux-arm64

undefined: err

Check failure on line 49 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / umeed darwin-arm64

undefined: err

Check failure on line 49 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / Run govulncheck

undefined: err

Check failure on line 49 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / test-app-non-determinism

undefined: err

Check failure on line 49 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / test-unit-cover

undefined: err

Check failure on line 49 in x/uibc/uics20/memo_handler.go

View workflow job for this annotation

GitHub Actions / Analyze

undefined: err
}

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

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, nil
}
var msgs []sdk.Msg
var fallbackReceiver sdk.AccAddress
if memo.FallbackAddr != "" {
if fallbackReceiver, err = sdk.AccAddressFromBech32(memo.FallbackAddr); err != nil {
return nil, nil, nil,
return nil, nil,
sdkerrors.Wrap(err, "ICS20 memo fallback_addr defined, but not formatted correctly")
}
if fallbackReceiver.Equals(mh.receiver) {
fallbackReceiver = nil
}
}

msgs, err = memo.GetMsgs()
mh.msgs, err = memo.GetMsgs()
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 fallbackReceiver, events, nil
}

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 {
events = append(events, "memo.messages are not valid, err: "+err.Error())
return nil, fallbackReceiver, events, nil
return fallbackReceiver, events, memoValidationErr
}

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

// 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)
}

if len(mh.msgs) == 0 {
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 {
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
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