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!: add memo to IBC transfers of ICS rewards #2290

Merged
merged 12 commits into from
Sep 27, 2024
4 changes: 4 additions & 0 deletions proto/interchain_security/ccv/v1/shared_consumer.proto
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ message ConsumerParams {
// The period after which a consumer can retry sending a throttled packet.
google.protobuf.Duration retry_delay_period = 13
[ (gogoproto.nullable) = false, (gogoproto.stdduration) = true ];

// The consumer ID of this consumer chain. Used by the consumer module to send
// ICS rewards.
string consumer_id = 14;
}

// ConsumerGenesisState defines shared genesis information between provider and
Expand Down
1 change: 1 addition & 0 deletions tests/mbt/driver/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ func createConsumerGenesis(modelParams ModelParams, providerChain *ibctesting.Te
[]string{},
[]string{},
ccvtypes.DefaultRetryDelayPeriod,
"",
)

return consumertypes.NewInitialGenesisState(consumerClientState, providerConsState, valUpdates, params)
Expand Down
6 changes: 5 additions & 1 deletion x/ccv/consumer/keeper/distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error {

sentCoins := sdk.NewCoins()
var allBalances sdk.Coins
rewardMemo, err := ccv.CreateTransferMemo(k.GetConsumerId(ctx), ctx.ChainID())
if err != nil {
return err
}
// iterate over all whitelisted reward denoms
for _, denom := range k.AllowedRewardDenoms(ctx) {
// get the balance of the denom in the toSendToProviderTokens address
Expand All @@ -137,7 +141,7 @@ func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error {
Receiver: providerAddr, // provider fee pool address to send to
TimeoutHeight: timeoutHeight, // timeout height disabled
TimeoutTimestamp: timeoutTimestamp,
Memo: "consumer chain rewards distribution",
Memo: rewardMemo,
}

// validate MsgTransfer before calling Transfer()
Expand Down
5 changes: 5 additions & 0 deletions x/ccv/consumer/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,8 @@ func (k Keeper) GetRetryDelayPeriod(ctx sdk.Context) time.Duration {
params := k.GetConsumerParams(ctx)
return params.RetryDelayPeriod
}

func (k Keeper) GetConsumerId(ctx sdk.Context) string {
params := k.GetConsumerParams(ctx)
return params.ConsumerId
}
3 changes: 2 additions & 1 deletion x/ccv/consumer/keeper/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ func TestParams(t *testing.T) {
rewardDenoms,
provideRewardDenoms,
ccv.DefaultRetryDelayPeriod,
"0",
) // these are the default params, IBC suite independently sets enabled=true

params := consumerKeeper.GetConsumerParams(ctx)
require.Equal(t, expParams, params)

newParams := ccv.NewParams(false, 1000,
"channel-2", "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm",
7*24*time.Hour, 25*time.Hour, "0.5", 500, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour)
7*24*time.Hour, 25*time.Hour, "0.5", 500, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, "1")
consumerKeeper.SetParams(ctx, newParams)
params = consumerKeeper.GetConsumerParams(ctx)
require.Equal(t, newParams, params)
Expand Down
1 change: 1 addition & 0 deletions x/ccv/consumer/migrations/v3/legacy_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func GetConsumerParamsLegacy(ctx sdk.Context, paramSpace ccvtypes.LegacyParamSub
getRewardDenoms(ctx, paramSpace),
getProviderRewardDenoms(ctx, paramSpace),
getRetryDelayPeriod(ctx, paramSpace),
"0",
)
}

Expand Down
3 changes: 3 additions & 0 deletions x/ccv/consumer/types/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ func TestValidateInitialGenesisState(t *testing.T) {
[]string{},
[]string{},
ccv.DefaultRetryDelayPeriod,
"1",
)),
true,
},
Expand All @@ -252,6 +253,7 @@ func TestValidateInitialGenesisState(t *testing.T) {
[]string{},
[]string{},
ccv.DefaultRetryDelayPeriod,
"1",
)),
true,
},
Expand Down Expand Up @@ -457,6 +459,7 @@ func TestValidateRestartConsumerGenesisState(t *testing.T) {
[]string{},
[]string{},
ccv.DefaultRetryDelayPeriod,
"1",
)),
true,
},
Expand Down
38 changes: 24 additions & 14 deletions x/ccv/consumer/types/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (

// Tests the validation of consumer params that happens at genesis
func TestValidateParams(t *testing.T) {
consumerId := "13"

testCases := []struct {
name string
params ccvtypes.ConsumerParams
Expand All @@ -19,59 +21,67 @@ func TestValidateParams(t *testing.T) {
{"default params", ccvtypes.DefaultParams(), true},
{
"custom valid params",
ccvtypes.NewParams(true, 5, "", "", 1004, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), true,
ccvtypes.NewParams(true, 5, "", "", 1004, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), true,
},
{
"custom invalid params, block per dist transmission",
ccvtypes.NewParams(true, -5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, -5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, dist transmission channel",
ccvtypes.NewParams(true, 5, "badchannel/", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "badchannel/", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, ccv timeout",
ccvtypes.NewParams(true, 5, "", "", -5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", -5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, transfer timeout",
ccvtypes.NewParams(true, 5, "", "", 1004, -7, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 1004, -7, "0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, consumer redist fraction is negative",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "-0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "-0.5", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, consumer redist fraction is over 1",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "1.2", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "1.2", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, bad consumer redist fraction ",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "notFrac", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "notFrac", 1000, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, negative num historical entries",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", -100, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", -100, 24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, negative unbonding period",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, -24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, -24*21*time.Hour, []string{"untrn"}, []string{"uatom"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, invalid reward denom",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"u"}, []string{}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{"u"}, []string{}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, invalid provider reward denom",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{"a"}, 2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{"a"}, 2*time.Hour, consumerId), false,
},
{
"custom invalid params, retry delay period is negative",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, -2*time.Hour), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, -2*time.Hour, consumerId), false,
},
{
"custom invalid params, retry delay period is zero",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, 0), false,
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, 0, consumerId), false,
},
{
"custom invalid params, consumer ID is blank",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, time.Hour, ""), false,
},
{
"custom invalid params, consumer ID is not a uint64",
ccvtypes.NewParams(true, 5, "", "", 5, 1005, "0.5", 1000, 24*21*time.Hour, []string{}, []string{}, time.Hour, "consumerId"), false,
},
}

Expand Down
32 changes: 26 additions & 6 deletions x/ccv/provider/ibc_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/cosmos/interchain-security/v6/x/ccv/provider/keeper"
"github.com/cosmos/interchain-security/v6/x/ccv/provider/types"
ccvtypes "github.com/cosmos/interchain-security/v6/x/ccv/types"
)

var _ porttypes.Middleware = &IBCMiddleware{}
Expand Down Expand Up @@ -113,6 +114,8 @@ func (im IBCMiddleware) OnRecvPacket(
packet channeltypes.Packet,
relayer sdk.AccAddress,
) exported.Acknowledgement {
logger := im.keeper.Logger(ctx)

// executes the IBC transfer OnRecv logic
ack := im.app.OnRecvPacket(ctx, packet, relayer)

Expand All @@ -121,12 +124,6 @@ func (im IBCMiddleware) OnRecvPacket(
// that the packet data is valid and can be safely
// deserialized without checking errors.
if ack.Success() {
// execute the middleware logic only if the sender is a consumer chain
consumerId, err := im.keeper.IdentifyConsumerIdFromIBCPacket(ctx, packet)
if err != nil {
return ack
}

// extract the coin info received from the packet data
var data ibctransfertypes.FungibleTokenPacketData
_ = types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data)
Expand All @@ -137,6 +134,29 @@ func (im IBCMiddleware) OnRecvPacket(
return ack
}

consumerId := ""
// check if the transfer has the reward memo
if rewardMemo, err := ccvtypes.GetRewardMemoFromTransferMemo(data.Memo); err != nil {
// check if the transfer is on a channel with the same underlying
// client as the CCV channel
consumerId, err = im.keeper.IdentifyConsumerIdFromIBCPacket(ctx, packet)
if err != nil {
if data.Memo == "consumer chain rewards distribution" {
// log error message
logger.Error(
"received token transfer with ICS reward from unknown consumer",
"packet", packet.String(),
"fungibleTokenPacketData", data.String(),
"error", err.Error(),
)

}
return ack
}
} else {
consumerId = rewardMemo.ConsumerId
}

coinAmt, _ := math.NewIntFromString(data.Amount)
coinDenom := GetProviderDenom(data.Denom, packet)

Expand Down
1 change: 1 addition & 0 deletions x/ccv/provider/keeper/consumer_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ func (k Keeper) MakeConsumerGenesis(
[]string{},
[]string{},
ccv.DefaultRetryDelayPeriod,
consumerId,
)

// create provider client state and consensus state for the consumer to be able
Expand Down
4 changes: 3 additions & 1 deletion x/ccv/provider/keeper/consumer_lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,8 @@ func TestMakeConsumerGenesis(t *testing.T) {
"soft_opt_out_threshold": "0",
"reward_denoms": [],
"provider_reward_denoms": [],
"retry_delay_period": %d
"retry_delay_period": %d,
"consumer_id": "%s"
},
"new_chain": true,
"provider" : {
Expand Down Expand Up @@ -720,6 +721,7 @@ func TestMakeConsumerGenesis(t *testing.T) {
initializationParameters.HistoricalEntries,
consumerUnbondingPeriod.Nanoseconds(),
ccvtypes.DefaultRetryDelayPeriod.Nanoseconds(),
CONSUMER_ID,
providerChainId,
trustingPeriod.Nanoseconds(),
providerUnbondingPeriod.Nanoseconds(),
Expand Down
28 changes: 14 additions & 14 deletions x/ccv/provider/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func (k Keeper) QueryConsumerGenesis(c context.Context, req *types.QueryConsumer
}

consumerId := req.ConsumerId
if err := types.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error())
if err := ccvtypes.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

gen, ok := k.GetConsumerGenesis(ctx, consumerId)
Expand Down Expand Up @@ -141,8 +141,8 @@ func (k Keeper) QueryValidatorConsumerAddr(goCtx context.Context, req *types.Que
ctx := sdk.UnwrapSDKContext(goCtx)

consumerId := req.ConsumerId
if err := types.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error())
if err := ccvtypes.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

providerAddrTmp, err := sdk.ConsAddressFromBech32(req.ProviderAddress)
Expand Down Expand Up @@ -230,8 +230,8 @@ func (k Keeper) QueryAllPairsValConsAddrByConsumer(
}

consumerId := req.ConsumerId
if err := types.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error())
if err := ccvtypes.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

// list of pairs valconsensus addr <providerValConAddrs : consumerValConAddrs>
Expand Down Expand Up @@ -275,8 +275,8 @@ func (k Keeper) QueryConsumerChainOptedInValidators(goCtx context.Context, req *
}

consumerId := req.ConsumerId
if err := types.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error())
if err := ccvtypes.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

optedInVals := []string{}
Expand All @@ -302,8 +302,8 @@ func (k Keeper) QueryConsumerValidators(goCtx context.Context, req *types.QueryC
}

consumerId := req.ConsumerId
if err := types.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error())
if err := ccvtypes.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

ctx := sdk.UnwrapSDKContext(goCtx)
Expand Down Expand Up @@ -507,8 +507,8 @@ func (k Keeper) QueryValidatorConsumerCommissionRate(goCtx context.Context, req
}

consumerId := req.ConsumerId
if err := types.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error())
if err := ccvtypes.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

consAddr, err := sdk.ConsAddressFromBech32(req.ProviderAddress)
Expand Down Expand Up @@ -569,8 +569,8 @@ func (k Keeper) QueryConsumerChain(goCtx context.Context, req *types.QueryConsum
}

consumerId := req.ConsumerId
if err := types.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, errorsmod.Wrap(types.ErrInvalidConsumerId, consumerId).Error())
if err := ccvtypes.ValidateConsumerId(consumerId); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
ctx := sdk.UnwrapSDKContext(goCtx)

Expand Down
1 change: 0 additions & 1 deletion x/ccv/provider/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
var (
ErrUnknownConsumerId = errorsmod.Register(ModuleName, 3, "no consumer chain with this consumer id")
ErrUnknownConsumerChannelId = errorsmod.Register(ModuleName, 4, "no consumer chain with this channel id")
ErrInvalidConsumerId = errorsmod.Register(ModuleName, 6, "invalid consumer id")
ErrConsumerKeyInUse = errorsmod.Register(ModuleName, 10, "consumer key is already in use by a validator")
ErrCannotAssignDefaultKeyAssignment = errorsmod.Register(ModuleName, 11, "cannot re-assign default key assignment")
ErrInvalidConsumerRewardDenom = errorsmod.Register(ModuleName, 14, "invalid consumer reward denom")
Expand Down
Loading
Loading