Skip to content

Commit

Permalink
Merge pull request #125 from terra-money/fix/rewards
Browse files Browse the repository at this point in the history
init: fix rewards before start time tests
  • Loading branch information
javiersuweijie authored Feb 17, 2023
2 parents b8175fb + b68ea7d commit 7a99808
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 44 deletions.
1 change: 1 addition & 0 deletions docs/proto/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ key: denom value: AllianceAsset
| `reward_change_interval` | [google.protobuf.Duration](#google.protobuf.Duration) | | |
| `last_reward_change_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
| `reward_weight_range` | [RewardWeightRange](#alliance.alliance.RewardWeightRange) | | set a bound of weight range to limit how much reward weights can scale. |
| `is_initialized` | [bool](#bool) | | flag to check if an asset has completed the initialization process after the reward delay |



Expand Down
4 changes: 3 additions & 1 deletion proto/alliance/alliance.proto
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ message AllianceAsset {
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
// set a bound of weight range to limit how much reward weights can scale.
// set a bound of weight range to limit how much reward weights can scale.
RewardWeightRange reward_weight_range = 10 [(gogoproto.nullable) = false];
// flag to check if an asset has completed the initialization process after the reward delay
bool is_initialized = 11;
}

message RewardWeightChangeSnapshot {
Expand Down
1 change: 1 addition & 0 deletions x/alliance/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate {
}

assets := k.GetAllAssets(ctx)
k.InitializeAllianceAssets(ctx, assets)
if _, err := k.DeductAssetsHook(ctx, assets); err != nil {
panic(fmt.Errorf("failed to deduct take rate from alliance in x/alliance module: %s", err))
}
Expand Down
25 changes: 25 additions & 0 deletions x/alliance/keeper/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ import (
"github.com/terra-money/alliance/x/alliance/types"
)

// InitializeAllianceAssets this hooks adds a reward change snapshot when time > asset.RewardStartTime
// A reward change snapshot of 0 weight is added to signify that the asset did not accrue any rewards during the
// warm up period so we can calculate the correct rewards when claiming
func (k Keeper) InitializeAllianceAssets(ctx sdk.Context, assets []*types.AllianceAsset) {
for _, asset := range assets {
if asset.IsInitialized || !asset.RewardsStarted(ctx.BlockTime()) {
continue
}
asset.IsInitialized = true
k.IterateAllianceValidatorInfo(ctx, func(valAddr sdk.ValAddress, info types.AllianceValidatorInfo) bool {
k.CreateInitialRewardWeightChangeSnapshot(ctx, asset.Denom, valAddr, info)
return false
})
k.SetAsset(ctx, *asset)
}
}

// UpdateAllianceAsset updates the alliance asset with new params
// Also saves a snapshot whenever rewards weight changes to make sure delegation reward calculation has reference to
// historical reward rates
Expand Down Expand Up @@ -310,6 +327,14 @@ func (k Keeper) SetRewardWeightChangeSnapshot(ctx sdk.Context, asset types.Allia
k.setRewardWeightChangeSnapshot(ctx, asset.Denom, val.GetOperator(), uint64(ctx.BlockHeight()), snapshot)
}

func (k Keeper) CreateInitialRewardWeightChangeSnapshot(ctx sdk.Context, denom string, valAddr sdk.ValAddress, info types.AllianceValidatorInfo) {
snapshot := types.RewardWeightChangeSnapshot{
PrevRewardWeight: sdk.ZeroDec(),
RewardHistories: info.GlobalRewardHistory,
}
k.setRewardWeightChangeSnapshot(ctx, denom, valAddr, uint64(ctx.BlockHeight()), snapshot)
}

func (k Keeper) setRewardWeightChangeSnapshot(ctx sdk.Context, denom string, valAddr sdk.ValAddress, height uint64, snapshot types.RewardWeightChangeSnapshot) {
key := types.GetRewardWeightChangeSnapshotKey(denom, valAddr, height)
store := ctx.KVStore(k.storeKey)
Expand Down
1 change: 1 addition & 0 deletions x/alliance/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, validator type
return nil, status.Errorf(codes.NotFound, "asset with denom: %s does not exist in alliance whitelist", coin.Denom)
}

// for the AllianceDenomTwo.
// Check and send delegated tokens into the alliance module address
err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, delAddr, types.ModuleName, sdk.NewCoins(coin))
if err != nil {
Expand Down
10 changes: 9 additions & 1 deletion x/alliance/keeper/reward.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func (k Keeper) ClaimDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress,
if !found {
return nil, types.ErrUnknownAsset
}

if !asset.RewardsStarted(ctx.BlockTime()) {
return sdk.NewCoins(), nil
}

delegation, found := k.GetDelegation(ctx, delAddr, val, denom)
if !found {
return sdk.Coins{}, stakingtypes.ErrNoDelegatorForAddress
Expand Down Expand Up @@ -77,7 +82,7 @@ func (k Keeper) ClaimDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress,
// CalculateDelegationRewards calculates the rewards that can be claimed for a delegation
// It takes past reward_rate changes into account by using the RewardRateChangeSnapshot entry
func (k Keeper) CalculateDelegationRewards(ctx sdk.Context, delegation types.Delegation, val types.AllianceValidator, asset types.AllianceAsset) (sdk.Coins, types.RewardHistories, error) {
var totalRewards sdk.Coins
totalRewards := sdk.NewCoins()
currentRewardHistory := types.NewRewardHistories(val.GlobalRewardHistory)
delegationRewardHistories := types.NewRewardHistories(delegation.RewardHistory)
// If there are reward rate changes between last and current claim, sequentially claim with the help of the snapshots
Expand Down Expand Up @@ -161,6 +166,9 @@ func (k Keeper) totalAssetWeight(ctx sdk.Context, val types.AllianceValidator) s
if !found {
continue
}
if !asset.RewardsStarted(ctx.BlockTime()) {
continue
}
totalValTokens := val.TotalDecTokensWithAsset(asset)
total = total.Add(asset.RewardWeight.Mul(totalValTokens))
}
Expand Down
109 changes: 109 additions & 0 deletions x/alliance/keeper/tests/reward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

test_helpers "github.com/terra-money/alliance/app"
"github.com/terra-money/alliance/x/alliance/keeper"
"github.com/terra-money/alliance/x/alliance/types"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -255,6 +256,114 @@ func TestClaimRewards(t *testing.T) {
require.Equal(t, indices, types.NewRewardHistories(delegation.RewardHistory))
}

func TestClaimRewardsBeforeRewardsIssuance(t *testing.T) {
app, ctx := createTestContext(t)
ctx = ctx.WithBlockTime(time.Now())
app.AllianceKeeper.InitGenesis(ctx, &types.GenesisState{
Params: types.DefaultParams(),
Assets: []types.AllianceAsset{
types.NewAllianceAsset(AllianceDenom, sdk.NewDec(2), sdk.NewDec(0), sdk.NewDec(5), sdk.NewDec(0), ctx.BlockTime().Add(-time.Hour)),
types.NewAllianceAsset(AllianceDenomTwo, sdk.NewDec(10), sdk.NewDec(2), sdk.NewDec(12), sdk.NewDec(0), ctx.BlockTime().Add(time.Hour)),
},
})
queryServer := keeper.NewQueryServerImpl(app.AllianceKeeper)

// Set tax and rewards to be zero for easier calculation
distParams := app.DistrKeeper.GetParams(ctx)
distParams.CommunityTax = sdk.ZeroDec()
distParams.BaseProposerReward = sdk.ZeroDec()
distParams.BonusProposerReward = sdk.ZeroDec()
app.DistrKeeper.SetParams(ctx, distParams)

// Accounts
mintPoolAddr := app.AccountKeeper.GetModuleAddress(minttypes.ModuleName)
delegations := app.StakingKeeper.GetAllDelegations(ctx)
valAddr1, err := sdk.ValAddressFromBech32(delegations[0].ValidatorAddress)
require.NoError(t, err)
val1, err := app.AllianceKeeper.GetAllianceValidator(ctx, valAddr1)
require.NoError(t, err)
addrs := test_helpers.AddTestAddrsIncremental(app, ctx, 2, sdk.NewCoins(
sdk.NewCoin(AllianceDenom, sdk.NewInt(1000_000)),
sdk.NewCoin(AllianceDenomTwo, sdk.NewInt(1000_000)),
))
user1 := addrs[0]
user2 := addrs[1]

// Mint tokens
err = app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(6000_000))))
require.NoError(t, err)
err = app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, sdk.NewCoins(sdk.NewCoin("stake2", sdk.NewInt(6000_000))))
require.NoError(t, err)

// New delegation from user 1
_, err = app.AllianceKeeper.Delegate(ctx, user1, val1, sdk.NewCoin(AllianceDenom, sdk.NewInt(1000_000)))
require.NoError(t, err)
assets := app.AllianceKeeper.GetAllAssets(ctx)
app.AllianceKeeper.InitializeAllianceAssets(ctx, assets)
err = app.AllianceKeeper.RebalanceBondTokenWeights(ctx, assets)
require.NoError(t, err)

// Transfer to reward pool
err = app.AllianceKeeper.AddAssetsToRewardPool(ctx, mintPoolAddr, val1, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(2000_000))))
require.NoError(t, err)

// New delegation from user 2
_, err = app.AllianceKeeper.Delegate(ctx, user2, val1, sdk.NewCoin(AllianceDenomTwo, sdk.NewInt(1000_000)))
require.NoError(t, err)
assets = app.AllianceKeeper.GetAllAssets(ctx)
app.AllianceKeeper.InitializeAllianceAssets(ctx, assets)
err = app.AllianceKeeper.RebalanceBondTokenWeights(ctx, assets)
require.NoError(t, err)

// Transfer to reward pool
err = app.AllianceKeeper.AddAssetsToRewardPool(ctx, mintPoolAddr, val1, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(2000_000))))
require.NoError(t, err)

// User 1 claims rewards
// Should get all the rewards in the pool
coins, err := app.AllianceKeeper.ClaimDelegationRewards(ctx, user1, val1, AllianceDenom)
require.NoError(t, err)
require.Equal(t, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(4_000_000))), coins)

// SInce user 1 claimed rewards, there should be no tokens in rewards pool
res, err := queryServer.AllianceDelegationRewards(ctx, &types.QueryAllianceDelegationRewardsRequest{
DelegatorAddr: user1.String(),
ValidatorAddr: val1.OperatorAddress,
Denom: AllianceDenom,
})
require.NoError(t, err)
require.Equal(t, []sdk.Coin(nil), res.Rewards)

// User 2 shouldn't have staking rewards
// because RewardStartTime is in the future
// for the AllianceDenomTwo.
coins, err = app.AllianceKeeper.ClaimDelegationRewards(ctx, user2, val1, AllianceDenomTwo)
require.NoError(t, err)
require.Equal(t, sdk.NewCoins(), coins)

// Move time forward so alliance 2 is enabled
ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1).WithBlockTime(ctx.BlockTime().Add(2 * time.Hour))
assets = app.AllianceKeeper.GetAllAssets(ctx)
app.AllianceKeeper.InitializeAllianceAssets(ctx, assets)
err = app.AllianceKeeper.RebalanceBondTokenWeights(ctx, assets)
require.NoError(t, err)

// User 2 should still not have staking rewards
// because all reward distributions happened before activation
coins, err = app.AllianceKeeper.ClaimDelegationRewards(ctx, user2, val1, AllianceDenomTwo)
require.NoError(t, err)
require.Len(t, coins, 0)

// Transfer to reward pool
err = app.AllianceKeeper.AddAssetsToRewardPool(ctx, mintPoolAddr, val1, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(2000_000))))
require.NoError(t, err)

// User 2 should now have rewards
coins, err = app.AllianceKeeper.ClaimDelegationRewards(ctx, user2, val1, AllianceDenomTwo)
require.NoError(t, err)
require.Len(t, coins, 1)
}

func TestClaimRewardsWithMultipleValidators(t *testing.T) {
var err error
app, ctx := createTestContext(t)
Expand Down
3 changes: 3 additions & 0 deletions x/alliance/migrations/v4/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ func migrateAssetsWithDefaultRewardWeightRange(ctx sdk.Context, k alliancekeeper
Min: sdk.ZeroDec(),
Max: sdk.NewDec(math.MaxInt),
}
if asset.RewardsStarted(ctx.BlockTime()) {
asset.IsInitialized = true
}
k.SetAsset(ctx, *asset)
}
return nil
Expand Down
Loading

0 comments on commit 7a99808

Please sign in to comment.