From 92905f350eaa7e73f730d448b9fb34767b237813 Mon Sep 17 00:00:00 2001 From: Oren Date: Sun, 2 Jun 2024 11:35:46 +0300 Subject: [PATCH 1/2] CNS-965: divide fund when creating a new pending ibc iprpc fund --- x/rewards/keeper/ibc_iprpc.go | 31 +++++++++++++++++++- x/rewards/keeper/ibc_iprpc_test.go | 47 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go index 16b156d6a8..f7cfff59bf 100644 --- a/x/rewards/keeper/ibc_iprpc.go +++ b/x/rewards/keeper/ibc_iprpc.go @@ -128,6 +128,35 @@ func (k Keeper) NewPendingIbcIprpcFund(ctx sdk.Context, creator string, spec str ) } + // divide funds by duration since we use addSpecFunds() when applying the PendingIbcIprpcFund + // which assumes that each month will get the input fund + + monthlyFund := sdk.NewCoin(fund.Denom, fund.Amount.QuoRaw(int64(duration))) + if monthlyFund.IsZero() { + return utils.LavaFormatWarning("fund amount cannot be less than duration", fmt.Errorf("cannot create PendingIbcIprpcFund"), + utils.LogAttr("creator", creator), + utils.LogAttr("spec", spec), + utils.LogAttr("duration", duration), + utils.LogAttr("funds", fund), + ) + } + + // leftovers will be transfered to the community pool + leftovers := sdk.NewCoin(fund.Denom, fund.Amount.Sub(monthlyFund.Amount.MulRaw(int64(duration)))) + if !leftovers.IsZero() { + receiverName, _ := types.IbcIprpcReceiverAddress() + err := k.FundCommunityPoolFromModule(ctx, sdk.NewCoins(leftovers), receiverName) + if err != nil { + return utils.LavaFormatError("cannot transfer monthly fund leftovers to community pool for PendingIbcIprpcFund", err, + utils.LogAttr("creator", creator), + utils.LogAttr("spec", spec), + utils.LogAttr("duration", duration), + utils.LogAttr("funds", fund), + utils.LogAttr("leftovers", leftovers), + ) + } + } + // get index for the new object latestPendingIbcIprpcFund := k.GetLatestPendingIbcIprpcFund(ctx) newIndex := uint64(0) @@ -143,7 +172,7 @@ func (k Keeper) NewPendingIbcIprpcFund(ctx sdk.Context, creator string, spec str Creator: creator, Spec: spec, Duration: duration, - Fund: fund, + Fund: monthlyFund, Expiry: expiry, } diff --git a/x/rewards/keeper/ibc_iprpc_test.go b/x/rewards/keeper/ibc_iprpc_test.go index 045721abc9..2adaddd8c3 100644 --- a/x/rewards/keeper/ibc_iprpc_test.go +++ b/x/rewards/keeper/ibc_iprpc_test.go @@ -341,3 +341,50 @@ func TestCalcPendingIbcIprpcFundExpiration(t *testing.T) { expiry := keeper.CalcPendingIbcIprpcFundExpiration(ctx) require.Equal(t, expectedExpiry, expiry) } + +// TestPendingIbcIprpcFundNewFunds tests that when creating a new PendingIbcIprpcFund the original +// fund gets divided by duration and the division leftovers are transfered to the community pool +func TestPendingIbcIprpcFundNewFunds(t *testing.T) { + template := []struct { + name string + funds math.Int + duration uint64 + expectedFundsInPending math.Int + expectedFundsInCommunity math.Int + success bool + }{ + {"divisiable - 9ulava", math.NewInt(9), 3, math.NewInt(3), math.ZeroInt(), true}, + {"not divisiable - 10ulava", math.NewInt(10), 3, math.NewInt(3), math.OneInt(), true}, + {"less than duration - 1ulava", math.NewInt(1), 3, math.ZeroInt(), math.ZeroInt(), false}, + {"one month duration - 10ulava", math.NewInt(10), 1, math.NewInt(10), math.ZeroInt(), true}, + } + + for _, tt := range template { + t.Run(tt.name, func(t *testing.T) { + ts := newTester(t, false) + keeper, ctx := ts.Keepers.Rewards, ts.Ctx + spec := ts.Spec("mock") + funds := sdk.NewCoin(ts.TokenDenom(), tt.funds) + + // set the IPRPC receiver balance manually since we don't call the IBC middleware + // this is crucial since the leftover funds are taken from it to the community pool + _, iprpcReceiverAddr := types.IbcIprpcReceiverAddress() + ts.Keepers.BankKeeper.SetBalance(ctx, iprpcReceiverAddr, sdk.NewCoins(funds)) + + // create a new PendingIbcIprpcFund + err := keeper.NewPendingIbcIprpcFund(ctx, "creator", spec.Index, tt.duration, funds) + if tt.success { + require.NoError(t, err) + latest := keeper.GetLatestPendingIbcIprpcFund(ts.Ctx) + require.True(t, latest.Fund.Amount.Equal(tt.expectedFundsInPending)) + } else { + require.Error(t, err) + } + + // check community pool balance + communityCoins := ts.Keepers.Distribution.GetFeePoolCommunityCoins(ts.Ctx) + communityBalance := communityCoins.AmountOf(ts.TokenDenom()).TruncateInt() + require.True(t, communityBalance.Equal(tt.expectedFundsInCommunity)) + }) + } +} From 12a19c9a5a0684f67b8968011814fff215f7953a Mon Sep 17 00:00:00 2001 From: Oren Date: Sun, 2 Jun 2024 12:23:18 +0300 Subject: [PATCH 2/2] CNS-965: lint fix --- x/rewards/client/cli/query_generate_ibc_iprpc_tx.go | 3 +-- x/rewards/keeper/ibc_iprpc.go | 11 ++++------- x/rewards/keeper/ibc_iprpc_test.go | 5 ++--- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/x/rewards/client/cli/query_generate_ibc_iprpc_tx.go b/x/rewards/client/cli/query_generate_ibc_iprpc_tx.go index 811394ef5e..a5f0fc3919 100644 --- a/x/rewards/client/cli/query_generate_ibc_iprpc_tx.go +++ b/x/rewards/client/cli/query_generate_ibc_iprpc_tx.go @@ -194,7 +194,6 @@ func getTimeoutHeight(clientQueryCtx client.Context, portId string, channelId st clientHeight, ok := clientState.GetLatestHeight().(clienttypes.Height) if !ok { return clienttypes.ZeroHeight(), fmt.Errorf("invalid height type. expected type: %T, got: %T", clienttypes.Height{}, clientState.GetLatestHeight()) - } defaultTimeoutHeightStr := ibctransfertypes.DefaultRelativePacketTimeoutHeight @@ -210,5 +209,5 @@ func getTimeoutHeight(clientQueryCtx client.Context, portId string, channelId st } func escapeMemo(memo string) string { - return fmt.Sprintf("%q", string(memo)) + return fmt.Sprintf("%q", memo) } diff --git a/x/rewards/keeper/ibc_iprpc.go b/x/rewards/keeper/ibc_iprpc.go index f7cfff59bf..e589f64cf5 100644 --- a/x/rewards/keeper/ibc_iprpc.go +++ b/x/rewards/keeper/ibc_iprpc.go @@ -130,7 +130,6 @@ func (k Keeper) NewPendingIbcIprpcFund(ctx sdk.Context, creator string, spec str // divide funds by duration since we use addSpecFunds() when applying the PendingIbcIprpcFund // which assumes that each month will get the input fund - monthlyFund := sdk.NewCoin(fund.Denom, fund.Amount.QuoRaw(int64(duration))) if monthlyFund.IsZero() { return utils.LavaFormatWarning("fund amount cannot be less than duration", fmt.Errorf("cannot create PendingIbcIprpcFund"), @@ -141,7 +140,7 @@ func (k Keeper) NewPendingIbcIprpcFund(ctx sdk.Context, creator string, spec str ) } - // leftovers will be transfered to the community pool + // leftovers will be transferred to the community pool leftovers := sdk.NewCoin(fund.Denom, fund.Amount.Sub(monthlyFund.Amount.MulRaw(int64(duration)))) if !leftovers.IsZero() { receiverName, _ := types.IbcIprpcReceiverAddress() @@ -187,16 +186,14 @@ func (k Keeper) NewPendingIbcIprpcFund(ctx sdk.Context, creator string, spec str ) } - k.SetPendingIbcIprpcFund(ctx, pendingIbcIprpcFund) - return nil } // SetPendingIbcIprpcFund set an PendingIbcIprpcFund in the PendingIbcIprpcFund store -func (k Keeper) SetPendingIbcIprpcFund(ctx sdk.Context, PendingIbcIprpcFund types.PendingIbcIprpcFund) { +func (k Keeper) SetPendingIbcIprpcFund(ctx sdk.Context, pendingIbcIprpcFund types.PendingIbcIprpcFund) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PendingIbcIprpcFundPrefix)) - b := k.cdc.MustMarshal(&PendingIbcIprpcFund) - store.Set(maps.GetIDBytes(PendingIbcIprpcFund.Index), b) + b := k.cdc.MustMarshal(&pendingIbcIprpcFund) + store.Set(maps.GetIDBytes(pendingIbcIprpcFund.Index), b) } // IsPendingIbcIprpcFund gets an PendingIbcIprpcFund from the PendingIbcIprpcFund store diff --git a/x/rewards/keeper/ibc_iprpc_test.go b/x/rewards/keeper/ibc_iprpc_test.go index 2adaddd8c3..285becef61 100644 --- a/x/rewards/keeper/ibc_iprpc_test.go +++ b/x/rewards/keeper/ibc_iprpc_test.go @@ -1,9 +1,8 @@ package keeper_test import ( - "testing" - "strconv" + "testing" "time" sdkerrors "cosmossdk.io/errors" @@ -343,7 +342,7 @@ func TestCalcPendingIbcIprpcFundExpiration(t *testing.T) { } // TestPendingIbcIprpcFundNewFunds tests that when creating a new PendingIbcIprpcFund the original -// fund gets divided by duration and the division leftovers are transfered to the community pool +// fund gets divided by duration and the division leftovers are transferred to the community pool func TestPendingIbcIprpcFundNewFunds(t *testing.T) { template := []struct { name string