Skip to content

Commit

Permalink
imp: claims invariants (evmos#385)
Browse files Browse the repository at this point in the history
* imp: claims invariants

* register invariant

* go mod tidy

* update test

* use Dec to prevent errors

* add tests

* fix

* fix test

* rm FIXME

* changelog

* typo

* add test for not broken invariant

* address comments
  • Loading branch information
fedekunze authored Mar 21, 2022
1 parent daf5b06 commit bb46a34
Show file tree
Hide file tree
Showing 9 changed files with 485 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

- (deps) [\#402](https://github.com/tharsis/evmos/pull/402) Bump IBC go to [`v3.0.0`](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0)
- (ibctesting) [\#388](https://github.com/tharsis/evmos/pull/388) Support Cosmos and EVM chains in IBC testing `Coordinator`.
- (claims) [\#385](https://github.com/tharsis/evmos/pull/385) Add claims invariant.
- (inflation) [\#383](https://github.com/tharsis/evmos/pull/383) Add gRPC endpoints for inflation rate and total supply
- (inflation) [\#369](https://github.com/tharsis/evmos/pull/369) Add `enableInflation` parameter.

Expand Down
7 changes: 4 additions & 3 deletions x/claims/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,11 @@ func (suite *GenesisTestSuite) TestClaimExportGenesis() {
ActionsCompleted: []bool{false, false, false, false},
})

claimableAmount := suite.app.ClaimsKeeper.GetClaimableAmountForAction(suite.ctx, claimsRecord, types.ActionIBCTransfer, suite.genesis.Params)
suite.Require().Equal(claimableAmount, sdk.NewInt(100))
claimableAmount, remainder := suite.app.ClaimsKeeper.GetClaimableAmountForAction(suite.ctx, claimsRecord, types.ActionIBCTransfer, suite.genesis.Params)
suite.Require().Equal(sdk.NewInt(100), claimableAmount)
suite.Require().Equal(sdk.ZeroInt(), remainder)

genesisExported := claims.ExportGenesis(suite.ctx, suite.app.ClaimsKeeper)
suite.Require().Equal(genesisExported.Params, suite.genesis.Params)
suite.Require().Equal(genesisExported.ClaimsRecords, suite.genesis.ClaimsRecords)
}
}
58 changes: 43 additions & 15 deletions x/claims/keeper/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,27 @@ import (
var actions = []types.Action{types.ActionVote, types.ActionDelegate, types.ActionEVM, types.ActionIBCTransfer}

// GetClaimableAmountForAction returns claimable amount for a specific action done by an address
// and the remainder amount to be claimed by the community pool
func (k Keeper) GetClaimableAmountForAction(
ctx sdk.Context,
claimsRecord types.ClaimsRecord,
action types.Action,
params types.Params,
) sdk.Int {
) (claimableCoins, remainder sdk.Int) {
// return zero if there are no coins to claim
if claimsRecord.InitialClaimableAmount.IsNil() || claimsRecord.InitialClaimableAmount.IsZero() {
return sdk.ZeroInt()
return sdk.ZeroInt(), sdk.ZeroInt()
}

// Safety check: the entire airdrop has completed
// NOTE: This shouldn't occur since at the end of the airdrop, the EnableClaims
// param is disabled.
if !params.IsClaimsActive(ctx.BlockTime()) {
return sdk.ZeroInt()
return sdk.ZeroInt(), sdk.ZeroInt()
}

if claimsRecord.HasClaimedAction(action) {
return sdk.ZeroInt()
return sdk.ZeroInt(), sdk.ZeroInt()
}

// NOTE: use len(actions)-1 we don't consider the Unspecified Action
Expand All @@ -39,7 +40,7 @@ func (k Keeper) GetClaimableAmountForAction(
// claim amount is the full amount if the elapsed time is before or equal
// the decay start time
if !ctx.BlockTime().After(decayStartTime) {
return initialClaimablePerAction
return initialClaimablePerAction, sdk.ZeroInt()
}

// calculate the claimable percent based on the elapsed time since the decay period started
Expand All @@ -55,8 +56,9 @@ func (k Keeper) GetClaimableAmountForAction(
claimableRatio := sdk.OneDec().Sub(elapsedDecayRatio)

// calculate the claimable coins, while rounding the decimals
claimableCoins := initialClaimablePerAction.ToDec().Mul(claimableRatio).RoundInt()
return claimableCoins
claimableCoins = initialClaimablePerAction.ToDec().Mul(claimableRatio).RoundInt()
remainder = initialClaimablePerAction.Sub(claimableCoins)
return claimableCoins, remainder
}

// GetUserTotalClaimable returns claimable amount for a specific action done by an address
Expand All @@ -74,7 +76,7 @@ func (k Keeper) GetUserTotalClaimable(ctx sdk.Context, addr sdk.AccAddress) sdk.
actions := []types.Action{types.ActionVote, types.ActionDelegate, types.ActionEVM, types.ActionIBCTransfer}

for _, action := range actions {
claimableForAction := k.GetClaimableAmountForAction(ctx, claimsRecord, action, params)
claimableForAction, _ := k.GetClaimableAmountForAction(ctx, claimsRecord, action, params)
totalClaimable = totalClaimable.Add(claimableForAction)
}

Expand Down Expand Up @@ -103,18 +105,28 @@ func (k Keeper) ClaimCoinsForAction(
return sdk.ZeroInt(), nil
}

claimableAmount := k.GetClaimableAmountForAction(ctx, claimsRecord, action, params)
claimableAmount, remainderAmount := k.GetClaimableAmountForAction(ctx, claimsRecord, action, params)

if claimableAmount.IsZero() {
return sdk.ZeroInt(), nil
}

claimedCoins := sdk.Coins{{Denom: params.ClaimsDenom, Amount: claimableAmount}}
claimedCoins := sdk.Coins{sdk.Coin{Denom: params.ClaimsDenom, Amount: claimableAmount}}
remainderCoins := sdk.Coins{sdk.Coin{Denom: params.ClaimsDenom, Amount: remainderAmount}}

if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, claimedCoins); err != nil {
return sdk.ZeroInt(), err
}

// fund community pool if remainder is not 0
if !remainderAmount.IsZero() {
escrowAddr := k.GetModuleAccountAddress()

if err := k.distrKeeper.FundCommunityPool(ctx, remainderCoins, escrowAddr); err != nil {
return sdk.ZeroInt(), err
}
}

claimsRecord.MarkClaimed(action)

ctx.EventManager().EmitEvents(sdk.Events{
Expand Down Expand Up @@ -156,6 +168,7 @@ func (k Keeper) MergeClaimsRecords(
params types.Params,
) (mergedRecord types.ClaimsRecord, err error) {
claimedAmt := sdk.ZeroInt()
remainderAmt := sdk.ZeroInt()

// new total is the sum of the sender and recipient claims records amounts
totalClaimableAmt := senderClaimsRecord.InitialClaimableAmount.Add(recipientClaimsRecord.InitialClaimableAmount)
Expand All @@ -175,13 +188,15 @@ func (k Keeper) MergeClaimsRecords(
mergedRecord.MarkClaimed(action)
case recipientCompleted && !senderCompleted:
// claim action for sender since the recipient completed it
amt := k.GetClaimableAmountForAction(ctx, senderClaimsRecord, action, params)
amt, remainder := k.GetClaimableAmountForAction(ctx, senderClaimsRecord, action, params)
claimedAmt = claimedAmt.Add(amt)
remainderAmt = remainderAmt.Add(remainder)
mergedRecord.MarkClaimed(action)
case !recipientCompleted && senderCompleted:
// claim action for recipient since the sender completed it
amt := k.GetClaimableAmountForAction(ctx, recipientClaimsRecord, action, params)
amt, remainder := k.GetClaimableAmountForAction(ctx, recipientClaimsRecord, action, params)
claimedAmt = claimedAmt.Add(amt)
remainderAmt = remainderAmt.Add(remainder)
mergedRecord.MarkClaimed(action)
case !senderCompleted && !recipientCompleted:
// Neither sender or recipient completed the action.
Expand All @@ -191,9 +206,10 @@ func (k Keeper) MergeClaimsRecords(
}

// claim IBC action for both sender and recipient
amtIBCRecipient := k.GetClaimableAmountForAction(ctx, recipientClaimsRecord, action, params)
amtIBCSender := k.GetClaimableAmountForAction(ctx, senderClaimsRecord, action, params)
amtIBCRecipient, remainderRecipient := k.GetClaimableAmountForAction(ctx, recipientClaimsRecord, action, params)
amtIBCSender, remainderSender := k.GetClaimableAmountForAction(ctx, senderClaimsRecord, action, params)
claimedAmt = claimedAmt.Add(amtIBCRecipient).Add(amtIBCSender)
remainderAmt = remainderAmt.Add(remainderRecipient).Add(remainderSender)
mergedRecord.MarkClaimed(action)
}
}
Expand All @@ -203,11 +219,23 @@ func (k Keeper) MergeClaimsRecords(
return mergedRecord, nil
}

claimedCoins := sdk.Coins{{Denom: params.ClaimsDenom, Amount: claimedAmt}}
claimedCoins := sdk.Coins{sdk.Coin{Denom: params.ClaimsDenom, Amount: claimedAmt}}
remainderCoins := sdk.Coins{sdk.Coin{Denom: params.ClaimsDenom, Amount: remainderAmt}}

if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, claimedCoins); err != nil {
return types.ClaimsRecord{}, err
}

// short-circuit: don't fund community pool if remainder is 0
if remainderCoins.IsZero() {
return mergedRecord, nil
}

escrowAddr := k.GetModuleAccountAddress()

if err := k.distrKeeper.FundCommunityPool(ctx, remainderCoins, escrowAddr); err != nil {
return types.ClaimsRecord{}, err
}

return mergedRecord, nil
}
Loading

0 comments on commit bb46a34

Please sign in to comment.