Skip to content

Commit

Permalink
add max claimable tokens query (#375)
Browse files Browse the repository at this point in the history
## Context and purpose of the change
- Add a query to get the total number of tokens a user can claim in an airdrop period
- Add a setup script to run the airdrop locally (reproducibly) 

## Changelog
- Add `includeClaimed` arg to `TotalClaimable` query (and dependent functions)

## Testing
- Add unittest

I also tested this manually, by running
```
make start-docker
bash scripts/airdrop.sh
$STRIDE_MAIN_CMD query claim total-claimable stride stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr "true"
```
and verifying the output
```
coins:
- amount: "600000"
  denom: ustrd
```
using `"false"` returns
```
coins: []
```
  • Loading branch information
asalzmann authored Nov 21, 2022
1 parent faf3e7b commit 613e857
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 80 deletions.
1 change: 1 addition & 0 deletions proto/stride/claim/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ message QueryTotalClaimableRequest {
string airdrop_identifier = 1
[ (gogoproto.moretags) = "yaml:\"airdrop_identifier\"" ];
string address = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ];
bool include_claimed = 3;
}

message QueryTotalClaimableResponse {
Expand Down
4 changes: 1 addition & 3 deletions proto/stride/claim/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ message MsgSetAirdropAllocations {

message MsgSetAirdropAllocationsResponse {}

message MsgClaimFreeAmount {
string user = 1;
}
message MsgClaimFreeAmount { string user = 1; }

message MsgClaimFreeAmountResponse {
repeated cosmos.base.v1beta1.Coin claimed_amount = 3 [
Expand Down
59 changes: 59 additions & 0 deletions scripts/airdrop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
### AIRDROP TESTING FLOW
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source ${SCRIPT_DIR}/vars.sh

# First, start the network with `make start-docker`
# Then, run this script with `bash scripts/airdrop.sh`

# NOTE: First, store the keys using the following mnemonics
# distributor address: stride1z835j3j65nqr6ng257q0xkkc9gta72gf48txwl
# distributor mnemonic: barrel salmon half click confirm crunch sense defy salute process cart fiscal sport clump weasel render private manage picture spell wreck hill frozen before
echo "barrel salmon half click confirm crunch sense defy salute process cart fiscal sport clump weasel render private manage picture spell wreck hill frozen before" | \
$STRIDE_MAIN_CMD keys add distributor-test --recover

# airdrop-test address: stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr
# airdrop claimer mnemonic: royal auction state december october hip monster hotel south help bulk supreme history give deliver pigeon license gold carpet rabbit raw wool fatigue donate
echo "royal auction state december october hip monster hotel south help bulk supreme history give deliver pigeon license gold carpet rabbit raw wool fatigue donate" | \
$STRIDE_MAIN_CMD keys add airdrop-test --recover

## AIRDROP SETUP
echo "Funding accounts..."
# Transfer uatom from gaia to stride, so that we can liquid stake later
$GAIA_MAIN_CMD tx ibc-transfer transfer transfer channel-0 stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr 1000000uatom --from ${GAIA_VAL_PREFIX}1 -y
sleep 5
# Fund the distributor account
$STRIDE_MAIN_CMD tx bank send val1 stride1z835j3j65nqr6ng257q0xkkc9gta72gf48txwl 600000ustrd --from val1 -y
sleep 5
# Fund the airdrop account
$STRIDE_MAIN_CMD tx bank send val1 stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr 1000000000ustrd --from val1 -y
sleep 5
# Create the airdrop, so that the airdrop account can claim tokens
$STRIDE_MAIN_CMD tx claim create-airdrop stride 1666792900 40000000 ustrd --from distributor-test -y
sleep 5
# Set airdrop allocations
$STRIDE_MAIN_CMD tx claim set-airdrop-allocations stride stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr 1 --from distributor-test -y
sleep 5

# AIRDROP CLAIMS
# Check balances before claims
echo "Initial balance before claim:"
$STRIDE_MAIN_CMD query bank balances stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr
# NOTE: You can claim here using the CLI, or from the frontend!
# Claim 20% of the free tokens
echo "Claiming fee amount..."
$STRIDE_MAIN_CMD tx claim claim-free-amount --from airdrop-test --gas 400000
sleep 5
echo "Balance after claim:"
$STRIDE_MAIN_CMD query bank balances stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr
# Stake, to claim another 20%
echo "Staking..."
$STRIDE_MAIN_CMD tx staking delegate stridevaloper1nnurja9zt97huqvsfuartetyjx63tc5zrj5x9f 100ustrd --from airdrop-test --gas 400000
sleep 5
echo "Balance after stake:"
$STRIDE_MAIN_CMD query bank balances stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr
# Liquid stake, to claim the final 60% of tokens
echo "Liquid staking..."
$STRIDE_MAIN_CMD tx stakeibc liquid-stake 1000 uatom --from airdrop-test --gas 400000
sleep 5
echo "Balance after liquid stake:"
$STRIDE_MAIN_CMD query bank balances stride1nf6v2paty9m22l3ecm7dpakq2c92ueyununayr
7 changes: 4 additions & 3 deletions x/claim/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,13 @@ $ %s query claim claimable-for-action stride1h4astdfzjhcwahtfrh24qtvndzzh49xvqtf
// GetCmdQueryClaimable implements the query claimables command.
func GetCmdQueryTotalClaimable() *cobra.Command {
cmd := &cobra.Command{
Use: "total-claimable [airdrop-identifier] [address]",
Args: cobra.ExactArgs(2),
Use: "total-claimable [airdrop-identifier] [address] [include-claimed]",
Args: cobra.ExactArgs(3),
Short: "Query the total claimable amount remaining for an account.",
Long: strings.TrimSpace(
fmt.Sprintf(`Query the total claimable amount remaining for an account.
Example:
$ %s query claim total-claimable stride stride1h4astdfzjhcwahtfrh24qtvndzzh49xvqtfftk
$ %s query claim total-claimable stride stride1h4astdfzjhcwahtfrh24qtvndzzh49xvqtfftk true
`,
version.AppName,
),
Expand All @@ -201,6 +201,7 @@ $ %s query claim total-claimable stride stride1h4astdfzjhcwahtfrh24qtvndzzh49xvq
res, err := queryClient.TotalClaimable(context.Background(), &types.QueryTotalClaimableRequest{
AirdropIdentifier: args[0],
Address: args[1],
IncludeClaimed: args[2] == "true",
})
if err != nil {
return err
Expand Down
12 changes: 6 additions & 6 deletions x/claim/keeper/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ func (k Keeper) GetAirdropClaimDenom(ctx sdk.Context, airdropIdentifier string)
}

// GetClaimable returns claimable amount for a specific action done by an address
func (k Keeper) GetClaimableAmountForAction(ctx sdk.Context, addr sdk.AccAddress, action types.Action, airdropIdentifier string) (sdk.Coins, error) {
func (k Keeper) GetClaimableAmountForAction(ctx sdk.Context, addr sdk.AccAddress, action types.Action, airdropIdentifier string, includeClaimed bool) (sdk.Coins, error) {
claimRecord, err := k.GetClaimRecord(ctx, addr, airdropIdentifier)
if err != nil {
return nil, err
Expand All @@ -353,8 +353,8 @@ func (k Keeper) GetClaimableAmountForAction(ctx sdk.Context, addr sdk.AccAddress
return sdk.Coins{}, nil
}

// if action already completed, nothing is claimable
if claimRecord.ActionCompleted[action] {
// if action already completed (and we're not including claimed tokens), nothing is claimable
if !includeClaimed && claimRecord.ActionCompleted[action] {
return sdk.Coins{}, nil
}

Expand Down Expand Up @@ -408,7 +408,7 @@ func (k Keeper) GetUserVestings(ctx sdk.Context, addr sdk.AccAddress) (vestingty
}

// GetClaimable returns claimable amount for a specific action done by an address
func (k Keeper) GetUserTotalClaimable(ctx sdk.Context, addr sdk.AccAddress, airdropIdentifier string) (sdk.Coins, error) {
func (k Keeper) GetUserTotalClaimable(ctx sdk.Context, addr sdk.AccAddress, airdropIdentifier string, includeClaimed bool) (sdk.Coins, error) {
claimRecord, err := k.GetClaimRecord(ctx, addr, airdropIdentifier)
if err != nil {
return sdk.Coins{}, err
Expand All @@ -420,7 +420,7 @@ func (k Keeper) GetUserTotalClaimable(ctx sdk.Context, addr sdk.AccAddress, aird
totalClaimable := sdk.Coins{}

for action := range types.Action_name {
claimableForAction, err := k.GetClaimableAmountForAction(ctx, addr, types.Action(action), airdropIdentifier)
claimableForAction, err := k.GetClaimableAmountForAction(ctx, addr, types.Action(action), airdropIdentifier, includeClaimed)
if err != nil {
return sdk.Coins{}, err
}
Expand Down Expand Up @@ -486,7 +486,7 @@ func (k Keeper) ClaimCoinsForAction(ctx sdk.Context, addr sdk.AccAddress, action
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid airdrop identifier: ClaimCoinsForAction")
}

claimableAmount, err := k.GetClaimableAmountForAction(ctx, addr, action, airdropIdentifier)
claimableAmount, err := k.GetClaimableAmountForAction(ctx, addr, action, airdropIdentifier, false)
if err != nil {
return claimableAmount, err
}
Expand Down
22 changes: 15 additions & 7 deletions x/claim/keeper/claim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ func (suite *KeeperTestSuite) TestHookBeforeAirdropStart() {
err = suite.app.ClaimKeeper.SetClaimRecordsWithWeights(suite.ctx, claimRecords)
suite.Require().NoError(err)

coins, err := suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr1, "stride")
coins, err := suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr1, "stride", false)
suite.NoError(err)
// Now, it is before starting air drop, so this value should return the empty coins
suite.True(coins.Empty())

coins, err = suite.app.ClaimKeeper.GetClaimableAmountForAction(suite.ctx, addr1, types.ACTION_FREE, "stride")
coins, err = suite.app.ClaimKeeper.GetClaimableAmountForAction(suite.ctx, addr1, types.ACTION_FREE, "stride", false)
suite.NoError(err)
// Now, it is before starting air drop, so this value should return the empty coins
suite.True(coins.Empty())
Expand Down Expand Up @@ -188,15 +188,15 @@ func (suite *KeeperTestSuite) TestAirdropFlow() {
err := suite.app.ClaimKeeper.SetClaimRecordsWithWeights(suite.ctx, claimRecords)
suite.Require().NoError(err)

coins, err := suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr1, "stride")
coins, err := suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr1, "stride", false)
suite.Require().NoError(err)
suite.Require().Equal(coins.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 50_000_000)).String())

coins, err = suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr2, "stride")
coins, err = suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr2, "stride", false)
suite.Require().NoError(err)
suite.Require().Equal(coins.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 50_000_000)).String())

coins, err = suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr3, "stride")
coins, err = suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr3, "stride", false)
suite.Require().NoError(err)
suite.Require().Equal(coins, sdk.Coins{})

Expand Down Expand Up @@ -288,11 +288,11 @@ func (suite *KeeperTestSuite) TestMultiChainAirdropFlow() {
err := suite.app.ClaimKeeper.SetClaimRecordsWithWeights(suite.ctx, claimRecords)
suite.Require().NoError(err)

coins, err := suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr1, "stride")
coins, err := suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr1, "stride", false)
suite.Require().NoError(err)
suite.Require().Equal(coins.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)).String())

coins, err = suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr2, "juno")
coins, err = suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr2, "juno", false)
suite.Require().NoError(err)
suite.Require().Equal(coins.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)).String())

Expand Down Expand Up @@ -343,6 +343,14 @@ func (suite *KeeperTestSuite) TestMultiChainAirdropFlow() {
coins = suite.app.BankKeeper.GetAllBalances(suite.ctx, addr1)
suite.Require().Equal(coins.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, (claimableAmountForFree+claimableAmountForStake+claimableAmountForLiquidStake)*2)).String())

// Verify that the max claimable amount is unchanged, even after claims
maxCoins, err := suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr1, "stride", true)
suite.Require().NoError(err)
suite.Require().Equal(maxCoins.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000)).String())
claimableCoins, err := suite.app.ClaimKeeper.GetUserTotalClaimable(suite.ctx, addr1, "stride", false)
suite.Require().NoError(err)
suite.Require().Equal(claimableCoins.String(), sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)).String())

// check if stride and osmosis airdrops ended properly
suite.ctx = suite.ctx.WithBlockHeight(1000)
suite.app.ClaimKeeper.EndBlocker(suite.ctx.WithBlockTime(time.Now().Add(types.DefaultAirdropDuration)))
Expand Down
4 changes: 2 additions & 2 deletions x/claim/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (k Keeper) ClaimableForAction(
return nil, err
}

coins, err := k.GetClaimableAmountForAction(ctx, addr, req.Action, req.AirdropIdentifier)
coins, err := k.GetClaimableAmountForAction(ctx, addr, req.Action, req.AirdropIdentifier, false)

return &types.QueryClaimableForActionResponse{
Coins: coins,
Expand All @@ -90,7 +90,7 @@ func (k Keeper) TotalClaimable(
return nil, err
}

coins, err := k.GetUserTotalClaimable(ctx, addr, req.AirdropIdentifier)
coins, err := k.GetUserTotalClaimable(ctx, addr, req.AirdropIdentifier, req.IncludeClaimed)

return &types.QueryTotalClaimableResponse{
Coins: coins,
Expand Down
2 changes: 1 addition & 1 deletion x/claim/spec/04_keeper.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Claim keeper module provides utility functions to manage epochs.
GetClaimRecords(ctx sdk.Context) []types.ClaimRecord
SetClaimRecord(ctx sdk.Context, claimRecord types.ClaimRecord) error
SetClaimRecords(ctx sdk.Context, claimRecords []types.ClaimRecord) error
GetClaimableAmountForAction(ctx sdk.Context, addr sdk.AccAddress, action types.Action) (sdk.Coins, error)
GetClaimableAmountForAction(ctx sdk.Context, addr sdk.AccAddress, action types.Action, includeClaimed bool) (sdk.Coins, error)
GetUserTotalClaimable(ctx sdk.Context, addr sdk.AccAddress) (sdk.Coins, error)
ClaimCoinsForAction(ctx sdk.Context, addr sdk.AccAddress, action types.Action) (sdk.Coins, error)
clearInitialClaimables(ctx sdk.Context)
Expand Down
Loading

0 comments on commit 613e857

Please sign in to comment.