diff --git a/app/app.go b/app/app.go index 393332c4d1..ebe7ff3536 100644 --- a/app/app.go +++ b/app/app.go @@ -581,7 +581,9 @@ func New( var transferStack ibcporttypes.IBCModule transferStack = ibctransfer.NewIBCModule(app.IBCTransferKeeper) // transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) - transferStack = uics20.NewICS20Module(transferStack, app.UIbcQuotaKeeperB, appCodec) + transferStack = uics20.NewICS20Module(transferStack, appCodec, + app.UIbcQuotaKeeperB, + leveragekeeper.NewMsgServerImpl(app.LeverageKeeper)) // Create Interchain Accounts Controller Stack // SendPacket, since it is originating from the application to core IBC: diff --git a/proto/umee/uibc/v1/uibc.proto b/proto/umee/uibc/v1/uibc.proto new file mode 100644 index 0000000000..b567195d04 --- /dev/null +++ b/proto/umee/uibc/v1/uibc.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package umee.uibc.v1; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/umee-network/umee/v6/x/uibc"; + +option (gogoproto.goproto_getters_all) = false; +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; +} diff --git a/tests/tsdk/codec.go b/tests/tsdk/codec.go index 6ced34ca40..75eab15991 100644 --- a/tests/tsdk/codec.go +++ b/tests/tsdk/codec.go @@ -11,7 +11,7 @@ import ( // typically a RegisterInterfaces function in Cosmos SDK modules) func NewCodec(registrars ...func(types.InterfaceRegistry)) codec.Codec { interfaceRegistry := types.NewInterfaceRegistry() - std.RegisterInterfaces(interfaceRegistry) + std.RegisterInterfaces(interfaceRegistry) // register SDK interfaces for _, f := range registrars { f(interfaceRegistry) } diff --git a/x/uibc/ics20.go b/x/uibc/ics20.go new file mode 100644 index 0000000000..221e8bc7e0 --- /dev/null +++ b/x/uibc/ics20.go @@ -0,0 +1,11 @@ +package uibc + +import ( + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/tx" +) + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (m ICS20Memo) UnpackInterfaces(unpacker types.AnyUnpacker) error { + return tx.UnpackInterfaces(unpacker, m.Messages) +} diff --git a/x/uibc/quota/ibc_middleware.go b/x/uibc/quota/ibc_middleware.go index 57e2bb1ef5..50c66595d0 100644 --- a/x/uibc/quota/ibc_middleware.go +++ b/x/uibc/quota/ibc_middleware.go @@ -15,7 +15,6 @@ import ( func (k Keeper) IBCOnSendPacket(packet []byte) error { params := k.GetParams() - if !params.IbcStatus.IBCTransferEnabled() { return ics20types.ErrSendDisabled } @@ -34,21 +33,15 @@ func (k Keeper) IBCOnSendPacket(packet []byte) error { return nil } -func (k Keeper) IBCOnRecvPacket(packet channeltypes.Packet) exported.Acknowledgement { +func (k Keeper) IBCOnRecvPacket(ft ics20types.FungibleTokenPacketData, packet channeltypes.Packet, +) exported.Acknowledgement { params := k.GetParams() if !params.IbcStatus.IBCTransferEnabled() { return channeltypes.NewErrorAcknowledgement(ics20types.ErrReceiveDisabled) } if params.IbcStatus.OutflowQuotaEnabled() { - var data ics20types.FungibleTokenPacketData - if err := ics20types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - ackErr := sdkerrors.ErrInvalidType.Wrap("cannot unmarshal ICS-20 transfer packet data") - return channeltypes.NewErrorAcknowledgement(ackErr) - } - - isSourceChain := ics20types.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) - ackErr := k.RecordIBCInflow(packet, data.Denom, data.Amount, isSourceChain) + ackErr := k.RecordIBCInflow(packet, ft.Denom, ft.Amount) if ackErr != nil { return ackErr } diff --git a/x/uibc/quota/quota.go b/x/uibc/quota/quota.go index 520b224faa..008ceaee0f 100644 --- a/x/uibc/quota/quota.go +++ b/x/uibc/quota/quota.go @@ -6,7 +6,7 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/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/cosmos/ibc-go/v7/modules/core/exported" @@ -229,24 +229,25 @@ func (k Keeper) UndoUpdateQuota(denom string, amount sdkmath.Int) error { } // RecordIBCInflow will save the inflow amount if token is registered otherwise it will skip -func (k Keeper) RecordIBCInflow( - packet channeltypes.Packet, dataDenom, dataAmount string, isSourceChain bool, +func (k Keeper) RecordIBCInflow(packet channeltypes.Packet, denom, amount string, ) exported.Acknowledgement { + // if chain is recevier and sender chain is source then we need create ibc_denom (ibc/hash(channel,denom)) to // check ibc_denom is exists in leverage token registry - if isSourceChain { + if !ics20types.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), denom) { // since SendPacket did not prefix the denomination, we must prefix denomination here - sourcePrefix := transfertypes.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) + sourcePrefix := ics20types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) // NOTE: sourcePrefix contains the trailing "/" - prefixedDenom := sourcePrefix + dataDenom + prefixedDenom := sourcePrefix + denom // construct the denomination trace from the full raw denomination and get the ibc_denom - ibcDenom := transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() + ibcDenom := ics20types.ParseDenomTrace(prefixedDenom).IBCDenom() ts, err := k.leverage.GetTokenSettings(*k.ctx, ibcDenom) if err != nil { - // skip if token is not a registered token on leverage if ltypes.ErrNotRegisteredToken.Is(err) { - return nil + return nil // skip recording inflow if the token is not registered } + k.ctx.Logger().Error("can't get x/leverage token settings", "error", err) + return channeltypes.NewErrorAcknowledgement(err) } // get the exchange price (eg: UMEE) in USD from oracle using SYMBOL Denom eg: `UMEE` @@ -256,7 +257,7 @@ func (k Keeper) RecordIBCInflow( } // calculate total exchange rate powerReduction := ten.Power(uint64(ts.Exponent)) - inflowInUSD := sdk.MustNewDecFromStr(dataAmount).Quo(powerReduction).Mul(exchangeRate) + inflowInUSD := sdk.MustNewDecFromStr(amount).Quo(powerReduction).Mul(exchangeRate) tokenInflow := sdk.NewDecCoinFromDec(ibcDenom, inflowInUSD) k.SetTokenInflow(tokenInflow) diff --git a/x/uibc/uibc.pb.go b/x/uibc/uibc.pb.go new file mode 100644 index 0000000000..7cc9fef99b --- /dev/null +++ b/x/uibc/uibc.pb.go @@ -0,0 +1,329 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: umee/uibc/v1/uibc.proto + +package uibc + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type ICS20Memo struct { + // messages is a list of `sdk.Msg`s that will be executed when handling ICS20 transfer. + Messages []*types.Any `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"` +} + +func (m *ICS20Memo) Reset() { *m = ICS20Memo{} } +func (m *ICS20Memo) String() string { return proto.CompactTextString(m) } +func (*ICS20Memo) ProtoMessage() {} +func (*ICS20Memo) Descriptor() ([]byte, []int) { + return fileDescriptor_963b2b690b6cd9dd, []int{0} +} +func (m *ICS20Memo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ICS20Memo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ICS20Memo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ICS20Memo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ICS20Memo.Merge(m, src) +} +func (m *ICS20Memo) XXX_Size() int { + return m.Size() +} +func (m *ICS20Memo) XXX_DiscardUnknown() { + xxx_messageInfo_ICS20Memo.DiscardUnknown(m) +} + +var xxx_messageInfo_ICS20Memo proto.InternalMessageInfo + +func (*ICS20Memo) XXX_MessageName() string { + return "umee.uibc.v1.ICS20Memo" +} +func init() { + proto.RegisterType((*ICS20Memo)(nil), "umee.uibc.v1.ICS20Memo") +} + +func init() { proto.RegisterFile("umee/uibc/v1/uibc.proto", fileDescriptor_963b2b690b6cd9dd) } + +var fileDescriptor_963b2b690b6cd9dd = []byte{ + // 219 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2f, 0xcd, 0x4d, 0x4d, + 0xd5, 0x2f, 0xcd, 0x4c, 0x4a, 0xd6, 0x2f, 0x33, 0x04, 0xd3, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, + 0x42, 0x3c, 0x20, 0x09, 0x3d, 0xb0, 0x40, 0x99, 0xa1, 0x94, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x58, + 0x42, 0x1f, 0xc4, 0x82, 0xa8, 0x91, 0x92, 0x4c, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0xf3, + 0x92, 0x4a, 0xd3, 0xf4, 0x13, 0xf3, 0x2a, 0x61, 0x52, 0xc9, 0xf9, 0xc5, 0xb9, 0xf9, 0xc5, 0xf1, + 0x10, 0x3d, 0x10, 0x0e, 0x44, 0x4a, 0xc9, 0x96, 0x8b, 0xd3, 0xd3, 0x39, 0xd8, 0xc8, 0xc0, 0x37, + 0x35, 0x37, 0x5f, 0xc8, 0x80, 0x8b, 0x23, 0x37, 0xb5, 0xb8, 0x38, 0x31, 0x3d, 0xb5, 0x58, 0x82, + 0x51, 0x81, 0x59, 0x83, 0xdb, 0x48, 0x44, 0x0f, 0x62, 0xaa, 0x1e, 0xcc, 0x54, 0x3d, 0xc7, 0xbc, + 0xca, 0x20, 0xb8, 0x2a, 0x27, 0x8f, 0x13, 0x0f, 0xe5, 0x18, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, + 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x4e, 0x3c, 0x96, 0x63, 0xbc, + 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xb5, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, + 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x90, 0x2f, 0x74, 0xf3, 0x52, 0x4b, 0xca, 0xf3, 0x8b, 0xb2, 0xc1, + 0x1c, 0xfd, 0x32, 0x33, 0xfd, 0x0a, 0xb0, 0x47, 0x93, 0xd8, 0xc0, 0x36, 0x18, 0x03, 0x02, 0x00, + 0x00, 0xff, 0xff, 0xb9, 0x78, 0x7f, 0x06, 0x04, 0x01, 0x00, 0x00, +} + +func (m *ICS20Memo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ICS20Memo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ICS20Memo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Messages) > 0 { + for iNdEx := len(m.Messages) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Messages[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintUibc(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintUibc(dAtA []byte, offset int, v uint64) int { + offset -= sovUibc(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ICS20Memo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Messages) > 0 { + for _, e := range m.Messages { + l = e.Size() + n += 1 + l + sovUibc(uint64(l)) + } + } + return n +} + +func sovUibc(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozUibc(x uint64) (n int) { + return sovUibc(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ICS20Memo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUibc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ICS20Memo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ICS20Memo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Messages", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUibc + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthUibc + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthUibc + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Messages = append(m.Messages, &types.Any{}) + if err := m.Messages[len(m.Messages)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipUibc(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthUibc + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipUibc(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowUibc + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowUibc + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowUibc + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthUibc + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupUibc + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthUibc + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthUibc = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowUibc = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupUibc = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/uibc/uics20/ibc_module.go b/x/uibc/uics20/ibc_module.go index 7ef20c8ee1..3578cb30fa 100644 --- a/x/uibc/uics20/ibc_module.go +++ b/x/uibc/uics20/ibc_module.go @@ -2,14 +2,17 @@ package uics20 import ( "cosmossdk.io/errors" - transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx" + ics20types "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" "github.com/cosmos/ibc-go/v7/modules/core/exported" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - + 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/quota" ) @@ -20,16 +23,19 @@ var _ porttypes.IBCModule = ICS20Module{} // quota update on acknowledgement error or timeout. type ICS20Module struct { porttypes.IBCModule - kb quota.KeeperBuilder - cdc codec.JSONCodec + kb quota.KeeperBuilder + leverage ltypes.MsgServer + cdc codec.JSONCodec } // NewICS20Module is an IBCMiddlware constructor. // `app` must be an ICS20 app. -func NewICS20Module(app porttypes.IBCModule, k quota.KeeperBuilder, cdc codec.JSONCodec) ICS20Module { +func NewICS20Module(app porttypes.IBCModule, cdc codec.JSONCodec, k quota.KeeperBuilder, l ltypes.MsgServer, +) ICS20Module { return ICS20Module{ IBCModule: app, kb: k, + leverage: l, cdc: cdc, } } @@ -37,11 +43,27 @@ func NewICS20Module(app porttypes.IBCModule, k quota.KeeperBuilder, cdc codec.JS // OnRecvPacket is called when a receiver chain receives a packet from SendPacket. 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(packet); ackResp != nil && !ackResp.Success() { + if ackResp := qk.IBCOnRecvPacket(ftData, packet); ackResp != nil && !ackResp.Success() { return ackResp } + if ftData.Memo != "" { + msgs, err := DeserializeMemoMsgs(im.cdc, []byte(ftData.Memo)) + if err != nil { + // TODO: need to verify if we want to stop the handle the error or revert the ibc transerf + // -> same logic in dispatchMemoMsgs + ctx.Logger().Error("can't JSON deserialize ftData Memo, expecting list of Msg", "err", err) + } else { + // TODO: need to handle fees! + im.dispatchMemoMsgs(&ctx, msgs) + } + } + return im.IBCModule.OnRecvPacket(ctx, packet, relayer) } @@ -69,7 +91,7 @@ func (im ICS20Module) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packe } func (im ICS20Module) onAckErr(ctx *sdk.Context, packet channeltypes.Packet) { - ftData, err := im.deserializeFTData(packet) + 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) @@ -78,12 +100,58 @@ func (im ICS20Module) onAckErr(ctx *sdk.Context, packet channeltypes.Packet) { qk.IBCRevertQuotaUpdate(ftData.Amount, ftData.Denom) } -func (im ICS20Module) deserializeFTData( - packet channeltypes.Packet, -) (d transfertypes.FungibleTokenPacketData, err error) { - if err = im.cdc.UnmarshalJSON(packet.GetData(), &d); err != nil { +// runs messages encoded in the ICS20 memo. +// NOTE: storage is forked, and only committed (flushed) if all messages pass and if all +// messages are supported. Otherwise the fork storage is discarded. +func (im ICS20Module) dispatchMemoMsgs(ctx *sdk.Context, msgs []sdk.Msg) { + + if len(msgs) > 2 { + ctx.Logger().Error("ics20 memo with more than 2 messages are not supported") + return + } + + // Caching context so that we don't update the store in case of failure. + cacheCtx, flush := ctx.CacheContext() + logger := ctx.Logger().With("scope", "ics20-OnRecvPacket") + for _, m := range msgs { + if err := im.handleMemoMsg(&cacheCtx, m); err != nil { + // ignore changes in cacheCtx and return + logger.Error("error dispatching", "msg: %v\t\t err: %v", m, err) + return + } + logger.Debug("dispatching", "msg", m) + } + flush() +} + +func (im ICS20Module) handleMemoMsg(ctx *sdk.Context, msg sdk.Msg) (err error) { + switch msg := msg.(type) { + case *ltypes.MsgSupply: + _, err = im.leverage.Supply(*ctx, msg) + case *ltypes.MsgSupplyCollateral: + _, err = im.leverage.SupplyCollateral(*ctx, msg) + case *ltypes.MsgBorrow: + _, err = im.leverage.Borrow(*ctx, msg) + default: + err = sdkerrors.ErrInvalidRequest.Wrapf("unsupported type in the ICS20 memo: %T", msg) + } + return err +} + +func deserializeFTData(cdc codec.JSONCodec, packet channeltypes.Packet, +) (d ics20types.FungibleTokenPacketData, err error) { + + if err = cdc.UnmarshalJSON(packet.GetData(), &d); err != nil { err = errors.Wrap(err, "cannot unmarshal ICS-20 transfer packet data") } return } + +func DeserializeMemoMsgs(cdc codec.JSONCodec, data []byte) ([]sdk.Msg, error) { + var m uibc.ICS20Memo + if err := cdc.UnmarshalJSON(data, &m); err != nil { + return nil, err + } + return tx.GetMsgs(m.Messages, "memo messages") +} diff --git a/x/uibc/uics20/ics4_module_int_test.go b/x/uibc/uics20/ics4_module_int_test.go new file mode 100644 index 0000000000..a55e298632 --- /dev/null +++ b/x/uibc/uics20/ics4_module_int_test.go @@ -0,0 +1,37 @@ +package uics20_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/stretchr/testify/assert" + + "github.com/umee-network/umee/v6/tests/accs" + "github.com/umee-network/umee/v6/tests/tsdk" + 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/uics20" +) + +func TestMsgMarshalling(t *testing.T) { + assert := assert.New(t) + cdc := tsdk.NewCodec(uibc.RegisterInterfaces, ltypes.RegisterInterfaces) + msgs := []sdk.Msg{ + &uibc.MsgGovSetIBCStatus{Authority: "auth1", Description: "d1", + IbcStatus: uibc.IBCTransferStatus_IBC_TRANSFER_STATUS_QUOTA_OUT_DISABLED}, + ltypes.NewMsgCollateralize(accs.Alice, sdk.NewCoin("ATOM", sdk.NewInt(1020))), + } + anyMsg, err := tx.SetMsgs(msgs) + assert.NoError(err) + var memo = uibc.ICS20Memo{Messages: anyMsg} + + bz, err := cdc.MarshalJSON(&memo) + assert.NoError(err) + + msgs2, err := uics20.DeserializeMemoMsgs(cdc, bz) + assert.NoError(err) + for i := range msgs2 { + assert.Equal(msgs[i], msgs2[i], "idx=%d", i) + } +} diff --git a/x/uibc/uics20/ics4_wrapper_test.go b/x/uibc/uics20/ics4_wrapper_test.go index 82c5195901..ffdc194289 100644 --- a/x/uibc/uics20/ics4_wrapper_test.go +++ b/x/uibc/uics20/ics4_wrapper_test.go @@ -3,8 +3,6 @@ package uics20_test import ( "testing" - "gotest.tools/v3/assert" - "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,6 +12,7 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" "github.com/golang/mock/gomock" + "gotest.tools/v3/assert" "github.com/umee-network/umee/v6/tests/tsdk" lfixtures "github.com/umee-network/umee/v6/x/leverage/fixtures"