Skip to content

Commit

Permalink
Update claim account check (#649)
Browse files Browse the repository at this point in the history
## Context and purpose of the change

`BaseAccount` and `StridePeriodicVestingAccount` types should be able to claim airdrops.


## Brief Changelog

- Check for `BaseAccount` and `StridePeriodicVestingAccount` types explicitly
- Add tests


## Author's Checklist

I have...

- [ ] Run and PASSED locally all GAIA integration tests
- [ ] If the change is contentful, I either:
    - [x] Added a new unit test OR 
    - [ ] Added test cases to existing unit tests
- [ ] OR this change is a trivial rework / code cleanup without any test coverage

If skipped any of the tests above, explain.


## Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] manually tested (if applicable)
- [ ] confirmed the author wrote unit tests for new logic
- [ ] reviewed documentation exists and is accurate


## Documentation and Release Note

  - [ ] Does this pull request introduce a new feature or user-facing behavior changes? 
  - [ ] Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`?
  - [ ] This pull request updates existing proto field values (and require a backend and frontend migration)? 
  - [ ] Does this pull request change existing proto field names (and require a frontend migration)?
  How is the feature or change documented? 
      - [ ] not applicable
      - [ ] jira ticket `XXX` 
      - [ ] specification (`x/<module>/spec/`) 
      - [ ] README.md 
      - [ ] not documented
  • Loading branch information
asalzmann authored Mar 8, 2023
1 parent 3539983 commit b7371d7
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 8 deletions.
30 changes: 22 additions & 8 deletions x/claim/keeper/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ import (
"strings"
"time"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"

errorsmod "cosmossdk.io/errors"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
authvestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
"github.com/gogo/protobuf/proto"

"github.com/Stride-Labs/stride/v6/utils"
Expand Down Expand Up @@ -173,27 +171,39 @@ func (k Keeper) EndAirdrop(ctx sdk.Context, airdropIdentifier string) error {
func (k Keeper) IsInitialPeriodPassed(ctx sdk.Context, airdropIdentifier string) bool {
airdrop := k.GetAirdropByIdentifier(ctx, airdropIdentifier)
if airdrop == nil {
k.Logger(ctx).Info("[CLAIM] airdrop is nil")
return false
}
goneTime := ctx.BlockTime().Sub(airdrop.AirdropStartTime)
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] goneTime %v", goneTime))
// Check if elapsed time since airdrop start is over the initial period of vesting
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] goneTime.Seconds() %v", goneTime.Seconds()))
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] types.DefaultVestingInitialPeriod.Seconds() %v", types.DefaultVestingInitialPeriod.Seconds()))
return goneTime.Seconds() >= types.DefaultVestingInitialPeriod.Seconds()
}

// ResetClaimStatus clear users' claimed status only after initial period of vesting is passed
func (k Keeper) ResetClaimStatus(ctx sdk.Context, airdropIdentifier string) error {
if k.IsInitialPeriodPassed(ctx, airdropIdentifier) {
passed := k.IsInitialPeriodPassed(ctx, airdropIdentifier)
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] k.IsInitialPeriodPassed(ctx, airdropIdentifier) %v", passed))
if passed {
k.Logger(ctx).Info("Resetting claim status")
// first, reset the claim records
records := k.GetClaimRecords(ctx, airdropIdentifier)
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] len(records) %v", len(records)))
for idx := range records {
records[idx].ActionCompleted = []bool{false, false, false}
}

k.Logger(ctx).Info("[CLAIM] SetClaimRecords...")
if err := k.SetClaimRecords(ctx, records); err != nil {
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] SetClaimRecords %v", err.Error()))
return err
}
// then, reset the airdrop ClaimedSoFar
k.Logger(ctx).Info("[CLAIM] ResetClaimedSoFar...")
if err := k.ResetClaimedSoFar(ctx); err != nil {
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] ResetClaimedSoFar %v", err.Error()))
return err
}
}
Expand Down Expand Up @@ -505,11 +515,13 @@ func (k Keeper) ClaimCoinsForAction(ctx sdk.Context, addr sdk.AccAddress, action
return nil, err
}

// If the account is a default vesting account, don't grant new vestings
// Only BaseAccounts and StridePeriodicVestingAccount can claim
acc := k.accountKeeper.GetAccount(ctx, addr)
_, isDefaultVestingAccount := acc.(*authvestingtypes.BaseVestingAccount)
if isDefaultVestingAccount {
return nil, err
_, isStrideVestingAccount := acc.(*vestingtypes.StridePeriodicVestingAccount)
_, isBaseAcc := acc.(*authtypes.BaseAccount)
canClaim := isStrideVestingAccount || isBaseAcc
if !canClaim {
return nil, errorsmod.Wrapf(types.ErrInvalidAccount, "Account: %v", acc)
}

// Claims don't vest if action type is ActionFree or initial period of vesting is passed
Expand Down Expand Up @@ -641,6 +653,7 @@ func (k Keeper) IncrementClaimedSoFar(ctx sdk.Context, identifier string, amount
// ResetClaimedSoFar resets ClaimedSoFar for a all airdrops
func (k Keeper) ResetClaimedSoFar(ctx sdk.Context) error {
params, err := k.GetParams(ctx)
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] params.Airdrops %v", params.Airdrops))
if err != nil {
panic(err)
}
Expand All @@ -651,6 +664,7 @@ func (k Keeper) ResetClaimedSoFar(ctx sdk.Context) error {
newAirdrops = append(newAirdrops, airdrop)
}
params.Airdrops = newAirdrops
k.Logger(ctx).Info(fmt.Sprintf("[CLAIM] params.Airdrops %v", params.Airdrops))
return k.SetParams(ctx, params)
}

Expand Down
102 changes: 102 additions & 0 deletions x/claim/keeper/claim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"

"github.com/Stride-Labs/stride/v6/x/claim/types"
stridevestingtypes "github.com/Stride-Labs/stride/v6/x/claim/vesting/types"
)

// Test functionality for loading allocation data(csv)
Expand Down Expand Up @@ -162,6 +165,89 @@ func (suite *KeeperTestSuite) TestBalancesAfterAccountConversion() {
suite.Require().Equal(spendableCoinsBal.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)).String())
}

// Check original user balances after being converted into stride vesting account
func (suite *KeeperTestSuite) TestClaimAccountTypes() {
suite.SetupTest()

// set a normal account
addr := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
// Base Account can claim
suite.app.AccountKeeper.SetAccount(suite.ctx, authtypes.NewBaseAccount(addr, nil, 0, 0))

initialBal := int64(1000)
err := suite.app.BankKeeper.SendCoins(suite.ctx, distributors["stride"], addr, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)))
suite.Require().NoError(err)

claimRecords := []types.ClaimRecord{
{
Address: addr.String(),
Weight: sdk.NewDecWithPrec(50, 2), // 50%
ActionCompleted: []bool{false, false, false},
AirdropIdentifier: types.DefaultAirdropIdentifier,
},
}

err = suite.app.ClaimKeeper.SetClaimRecordsWithWeights(suite.ctx, claimRecords)
suite.Require().NoError(err)

// check if original account tokens are not affected after stride vesting
_, err = suite.app.ClaimKeeper.ClaimCoinsForAction(suite.ctx, addr, types.ACTION_DELEGATE_STAKE, "stride")
suite.Require().NoError(err)
claimableAmountForStake := sdk.NewDecWithPrec(20, 2).
Mul(sdk.NewDec(100_000_000 - initialBal)).
RoundInt64() // remaining balance is 100000000*(80/100), claim 20% for stake

coinsBal := suite.app.BankKeeper.GetAllBalances(suite.ctx, addr)
suite.Require().Equal(coinsBal.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal+claimableAmountForStake)).String())

spendableCoinsBal := suite.app.BankKeeper.SpendableCoins(suite.ctx, addr)
suite.Require().Equal(spendableCoinsBal.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)).String())

// Verify the account type has changed to stride vesting account
acc := suite.app.AccountKeeper.GetAccount(suite.ctx, addr)
_, isVestingAcc := acc.(*stridevestingtypes.StridePeriodicVestingAccount)
suite.Require().True(isVestingAcc)

// Initialize vesting accounts
addr2 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
account := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr2)
err = suite.app.BankKeeper.SendCoins(suite.ctx, distributors["stride"], addr2, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, vestingtypes.NewBaseVestingAccount(account.(*authtypes.BaseAccount), nil, 0))

addr3 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
account = suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr3)
err = suite.app.BankKeeper.SendCoins(suite.ctx, distributors["stride"], addr3, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, vestingtypes.NewContinuousVestingAccount(account.(*authtypes.BaseAccount), nil, 0, 0))

addr4 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
account = suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr4)
err = suite.app.BankKeeper.SendCoins(suite.ctx, distributors["stride"], addr4, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, vestingtypes.NewPeriodicVestingAccount(account.(*authtypes.BaseAccount), nil, 0, nil))

// Init claim records
for _, addr := range []sdk.AccAddress{addr2, addr3, addr4} {
claimRecords := []types.ClaimRecord{
{
Address: addr.String(),
Weight: sdk.NewDecWithPrec(50, 2),
ActionCompleted: []bool{false, false, false},
AirdropIdentifier: types.DefaultAirdropIdentifier,
},
}
err = suite.app.ClaimKeeper.SetClaimRecordsWithWeights(suite.ctx, claimRecords)
suite.Require().NoError(err)
}

// Try to claim tokens with each account type
for _, addr := range []sdk.AccAddress{addr2, addr3, addr4} {
_, err = suite.app.ClaimKeeper.ClaimCoinsForAction(suite.ctx, addr, types.ACTION_DELEGATE_STAKE, "stride")
suite.Require().ErrorContains(err, "only BaseAccount and StridePeriodicVestingAccount can claim")
}
}

// Run all airdrop flow
func (suite *KeeperTestSuite) TestAirdropFlow() {
suite.SetupTest()
Expand All @@ -171,6 +257,14 @@ func (suite *KeeperTestSuite) TestAirdropFlow() {
addr3 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
weight := sdk.NewDecWithPrec(50, 2)

for _, addr := range []sdk.AccAddress{addr1, addr2, addr3} {
initialBal := int64(1000)
err := suite.app.BankKeeper.SendCoins(suite.ctx, distributors["stride"], addr, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)))
suite.Require().NoError(err)
err = suite.app.BankKeeper.SendCoins(suite.ctx, addr, distributors["stride"], sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)))
suite.Require().NoError(err)
}

claimRecords := []types.ClaimRecord{
{
Address: addr1.String(),
Expand Down Expand Up @@ -265,6 +359,14 @@ func (suite *KeeperTestSuite) TestMultiChainAirdropFlow() {
addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
addr2 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())

for _, addr := range []sdk.AccAddress{addr1, addr2} {
initialBal := int64(1000)
err := suite.app.BankKeeper.SendCoins(suite.ctx, distributors["stride"], addr, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)))
suite.Require().NoError(err)
err = suite.app.BankKeeper.SendCoins(suite.ctx, addr, distributors["stride"], sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, initialBal)))
suite.Require().NoError(err)
}

claimRecords := []types.ClaimRecord{
{
Address: addr1.String(),
Expand Down
2 changes: 2 additions & 0 deletions x/claim/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ var (
"airdrop with same distributor already exists")
ErrInvalidAmount = errorsmod.Register(ModuleName, 1108,
"cannot claim negative tokens")
ErrInvalidAccount = errorsmod.Register(ModuleName, 1109,
"only BaseAccount and StridePeriodicVestingAccount can claim")
)

0 comments on commit b7371d7

Please sign in to comment.