Skip to content

Commit 0348c76

Browse files
committed
feat: add memo to IBC transfers of ICS rewards
1 parent 33dbf16 commit 0348c76

File tree

4 files changed

+89
-9
lines changed

4 files changed

+89
-9
lines changed

x/ccv/consumer/keeper/distribution.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error {
121121

122122
sentCoins := sdk.NewCoins()
123123
var allBalances sdk.Coins
124+
rewardMemo, err := ccv.CreateTransferMemo(ctx.ChainID())
125+
if err != nil {
126+
return err
127+
}
128+
124129
// iterate over all whitelisted reward denoms
125130
for _, denom := range k.AllowedRewardDenoms(ctx) {
126131
// get the balance of the denom in the toSendToProviderTokens address
@@ -137,7 +142,7 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error {
137142
Receiver: providerAddr, // provider fee pool address to send to
138143
TimeoutHeight: timeoutHeight, // timeout height disabled
139144
TimeoutTimestamp: timeoutTimestamp,
140-
Memo: "consumer chain rewards distribution",
145+
Memo: rewardMemo,
141146
}
142147

143148
// validate MsgTransfer before calling Transfer()

x/ccv/provider/ibc_middleware.go

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/cosmos/interchain-security/v5/x/ccv/provider/keeper"
1515
"github.com/cosmos/interchain-security/v5/x/ccv/provider/types"
16+
ccvtypes "github.com/cosmos/interchain-security/v5/x/ccv/types"
1617
)
1718

1819
var _ porttypes.Middleware = &IBCMiddleware{}
@@ -111,6 +112,7 @@ func (im IBCMiddleware) OnRecvPacket(
111112
packet channeltypes.Packet,
112113
relayer sdk.AccAddress,
113114
) exported.Acknowledgement {
115+
logger := im.keeper.Logger(ctx)
114116
// IBC v10: Added channelID parameter
115117
ack := im.app.OnRecvPacket(ctx, channelID, packet, relayer)
116118

@@ -119,12 +121,6 @@ func (im IBCMiddleware) OnRecvPacket(
119121
// that the packet data is valid and can be safely
120122
// deserialized without checking errors.
121123
if ack.Success() {
122-
// execute the middleware logic only if the sender is a consumer chain
123-
consumerID, err := im.keeper.IdentifyConsumerChainIDFromIBCPacket(ctx, packet)
124-
if err != nil {
125-
return ack
126-
}
127-
128124
// extract the coin info received from the packet data
129125
var data ibctransfertypes.FungibleTokenPacketData
130126
_ = types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data)
@@ -135,20 +131,44 @@ func (im IBCMiddleware) OnRecvPacket(
135131
return ack
136132
}
137133

134+
chainID := ""
135+
// check if the transfer has the reward memo
136+
if rewardMemo, err := ccvtypes.GetRewardMemoFromTransferMemo(data.Memo); err != nil {
137+
// check if the transfer is on a channel with the same underlying
138+
// client as the CCV channel
139+
chainID, err = im.keeper.IdentifyConsumerChainIDFromIBCPacket(ctx, packet)
140+
if err != nil {
141+
if data.Memo == "consumer chain rewards distribution" {
142+
// log error message
143+
logger.Error(
144+
"received token transfer with ICS reward from unknown consumer",
145+
"packet", packet.String(),
146+
"fungibleTokenPacketData", data.String(),
147+
"error", err.Error(),
148+
)
149+
}
150+
151+
return ack
152+
}
153+
} else {
154+
logger.Info("transfer memo:%#+v", rewardMemo)
155+
chainID = rewardMemo.ChainID
156+
}
157+
138158
coinAmt, _ := math.NewIntFromString(data.Amount)
139159
coinDenom := GetProviderDenom(data.Denom, packet)
140160

141161
// verify that the coin's denom is a whitelisted consumer denom,
142162
// and if so, adds it to the consumer chain rewards allocation,
143163
// otherwise the prohibited coin just stays in the pool forever.
144164
if im.keeper.ConsumerRewardDenomExists(ctx, coinDenom) {
145-
alloc := im.keeper.GetConsumerRewardsAllocation(ctx, consumerID)
165+
alloc := im.keeper.GetConsumerRewardsAllocation(ctx, chainID)
146166
alloc.Rewards = alloc.Rewards.Add(
147167
sdk.NewDecCoinsFromCoins(sdk.Coin{
148168
Denom: coinDenom,
149169
Amount: coinAmt,
150170
})...)
151-
im.keeper.SetConsumerRewardsAllocation(ctx, consumerID, alloc)
171+
im.keeper.SetConsumerRewardsAllocation(ctx, chainID, alloc)
152172
}
153173
}
154174

x/ccv/types/wire.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package types
22

33
import (
4+
"encoding/json"
45
"fmt"
56

67
errorsmod "cosmossdk.io/errors"
@@ -194,3 +195,45 @@ func NewConsumerPacketData(cpdType ConsumerPacketDataType, data isConsumerPacket
194195
Data: data,
195196
}
196197
}
198+
199+
type RewardMemo struct {
200+
ChainID string `json:"chainId"`
201+
Memo string `json:"memo"`
202+
}
203+
204+
// CreateTransferMemo creates a memo for the IBC transfer of ICS rewards.
205+
// Note that the memo follows the Fungible Token Transfer v2 standard
206+
// https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md#using-the-memo-field
207+
func CreateTransferMemo(chainID string) (string, error) {
208+
memo := RewardMemo{ChainID: chainID, Memo: "ICS rewards"}
209+
memoBytes, err := json.Marshal(memo)
210+
if err != nil {
211+
return "", err
212+
}
213+
return fmt.Sprintf(`{
214+
"provider": %s
215+
}`,
216+
string(memoBytes),
217+
), nil
218+
}
219+
220+
func GetRewardMemoFromTransferMemo(memo string) (RewardMemo, error) {
221+
memoData := map[string]json.RawMessage{}
222+
err := json.Unmarshal([]byte(memo), &memoData)
223+
if err != nil {
224+
return RewardMemo{}, err
225+
}
226+
227+
providerMemo, ok := memoData["provider"]
228+
if !ok {
229+
return RewardMemo{}, err
230+
}
231+
232+
rewardMemo := RewardMemo{}
233+
err = json.Unmarshal([]byte(providerMemo), &rewardMemo)
234+
if err != nil {
235+
return RewardMemo{}, err
236+
}
237+
238+
return rewardMemo, nil
239+
}

x/ccv/types/wire_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,15 @@ func TestVSCMaturedPacketDataWireBytes(t *testing.T) {
223223

224224
require.Equal(t, expectedStr, str)
225225
}
226+
227+
func TestCreateTransferMemo(t *testing.T) {
228+
chainID := "chain-13"
229+
230+
transferMemo, err := types.CreateTransferMemo(chainID)
231+
require.NoError(t, err)
232+
233+
rewardMemo, err := types.GetRewardMemoFromTransferMemo(transferMemo)
234+
require.NoError(t, err)
235+
require.Equal(t, chainID, rewardMemo.ChainID)
236+
require.Equal(t, "ICS rewards", rewardMemo.Memo)
237+
}

0 commit comments

Comments
 (0)