Skip to content

Commit

Permalink
feat(participation): decay support (#915)
Browse files Browse the repository at this point in the history
* proto

* decay validate

* enabled

* decay info param

* implement validate tests

* decay factor

* add apply tests

* fix test

* decay constructors

* apply decay factor

* decay tests

* remove unnecessary condition

* format

Co-authored-by: Alex Johnson <[email protected]>
  • Loading branch information
lumtis and Alex Johnson authored Aug 10, 2022
1 parent 9366849 commit 0cf0d76
Show file tree
Hide file tree
Showing 15 changed files with 903 additions and 32 deletions.
10 changes: 10 additions & 0 deletions proto/claim/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ syntax = "proto3";
package tendermint.spn.claim;

import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";

option go_package = "github.com/tendermint/spn/x/claim/types";

// Params defines the parameters for the module.
message Params {
DecayInformation decayInformation = 1 [(gogoproto.nullable) = false];
option (gogoproto.goproto_stringer) = false;
}

// DecayInformation defines the information about decay for the airdrop
// when claimable airdrop amount starts to decrease and when it ends
message DecayInformation {
bool enabled = 1;
google.protobuf.Timestamp decayStart = 2 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
google.protobuf.Timestamp decayEnd = 3 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
}
5 changes: 3 additions & 2 deletions testutil/gen_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import (
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
spnapp "github.com/tendermint/spn/app"
"github.com/tendermint/spn/cmd"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tm-db"

spnapp "github.com/tendermint/spn/app"
"github.com/tendermint/spn/cmd"
)

func GenApp(withGenesis bool, invCheckPeriod uint) (*spnapp.App, spnapp.GenesisState) {
Expand Down
2 changes: 1 addition & 1 deletion testutil/keeper/initializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ func (i initializer) Claim(
i.StateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil)

paramKeeper.Subspace(claimtypes.ModuleName)
subspace, _ := paramKeeper.GetSubspace(participationtypes.ModuleName)
subspace, _ := paramKeeper.GetSubspace(claimtypes.ModuleName)

return claimkeeper.NewKeeper(
i.Codec,
Expand Down
11 changes: 10 additions & 1 deletion x/claim/keeper/mission.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,17 @@ func (k Keeper) CompleteMission(ctx sdk.Context, missionID uint64, address strin
claimableAmount := claimRecord.ClaimableFromMission(mission)
claimable := sdk.NewCoins(sdk.NewCoin(airdropSupply.Denom, claimableAmount))

// calculate claimable after decay factor
decayInfo := k.DecayInformation(ctx)
claimable = decayInfo.ApplyDecayFactor(claimable, ctx.BlockTime())

// check final claimable non-zero
if claimable.Empty() {
return types.ErrNoClaimable
}

// decrease airdrop supply
airdropSupply.Amount = airdropSupply.Amount.Sub(claimableAmount)
airdropSupply.Amount = airdropSupply.Amount.Sub(claimable.AmountOf(airdropSupply.Denom))
if airdropSupply.Amount.IsNegative() {
return spnerrors.Critical("airdrop supply is lower than total claimable")
}
Expand Down
102 changes: 94 additions & 8 deletions x/claim/keeper/mission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper_test

import (
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -70,6 +71,8 @@ func TestKeeper_CompleteMission(t *testing.T) {
airdropSupply sdk.Coin
mission types.Mission
claimRecord types.ClaimRecord
params types.Params
blockTime time.Time
}

// prepare addresses
Expand All @@ -92,6 +95,7 @@ func TestKeeper_CompleteMission(t *testing.T) {
noAirdropSupply: true,
claimRecord: sample.ClaimRecord(r),
mission: sample.Mission(r),
params: types.DefaultParams(),
},
missionID: 1,
address: sample.Address(r),
Expand All @@ -107,6 +111,7 @@ func TestKeeper_CompleteMission(t *testing.T) {
Claimable: sdk.OneInt(),
CompletedMissions: []uint64{1},
},
params: types.DefaultParams(),
},
missionID: 1,
address: addr[0],
Expand All @@ -121,6 +126,7 @@ func TestKeeper_CompleteMission(t *testing.T) {
MissionID: 1,
Weight: sdk.OneDec(),
},
params: types.DefaultParams(),
},
missionID: 1,
address: sample.Address(r),
Expand All @@ -139,6 +145,7 @@ func TestKeeper_CompleteMission(t *testing.T) {
Claimable: sdk.OneInt(),
CompletedMissions: []uint64{1},
},
params: types.DefaultParams(),
},
missionID: 1,
address: addr[1],
Expand All @@ -156,6 +163,7 @@ func TestKeeper_CompleteMission(t *testing.T) {
Address: addr[2],
Claimable: sdk.NewIntFromUint64(10000),
},
params: types.DefaultParams(),
},
missionID: 1,
address: addr[2],
Expand All @@ -173,6 +181,7 @@ func TestKeeper_CompleteMission(t *testing.T) {
Address: "invalid",
Claimable: sdk.OneInt(),
},
params: types.DefaultParams(),
},
missionID: 1,
address: "invalid",
Expand All @@ -190,13 +199,14 @@ func TestKeeper_CompleteMission(t *testing.T) {
Address: addr[3],
Claimable: sdk.NewIntFromUint64(1000),
},
params: types.DefaultParams(),
},
missionID: 1,
address: addr[3],
expectedBalance: tc.Coin(t, "1000foo"),
},
{
name: "should allow distributing no fund for mission with 0 weight",
name: "should prevent distributing fund for mission with 0 weight",
inputState: inputState{
airdropSupply: tc.Coin(t, "1000foo"),
mission: types.Mission{
Expand All @@ -207,10 +217,11 @@ func TestKeeper_CompleteMission(t *testing.T) {
Address: addr[4],
Claimable: sdk.NewIntFromUint64(1000),
},
params: types.DefaultParams(),
},
missionID: 1,
address: addr[4],
expectedBalance: tc.Coin(t, "0foo"),
missionID: 1,
address: addr[4],
err: types.ErrNoClaimable,
},
{
name: "should allow distributing half for mission with 0.5 weight",
Expand All @@ -224,6 +235,7 @@ func TestKeeper_CompleteMission(t *testing.T) {
Address: addr[5],
Claimable: sdk.NewIntFromUint64(500),
},
params: types.DefaultParams(),
},
missionID: 1,
address: addr[5],
Expand All @@ -241,13 +253,14 @@ func TestKeeper_CompleteMission(t *testing.T) {
Address: addr[6],
Claimable: sdk.NewIntFromUint64(201),
},
params: types.DefaultParams(),
},
missionID: 1,
address: addr[6],
expectedBalance: tc.Coin(t, "100foo"),
},
{
name: "should allow distributing no fund for empty claim record",
name: "should prevent distributing fund for empty claim record",
inputState: inputState{
airdropSupply: tc.Coin(t, "1000foo"),
mission: types.Mission{
Expand All @@ -258,10 +271,11 @@ func TestKeeper_CompleteMission(t *testing.T) {
Address: addr[7],
Claimable: sdk.ZeroInt(),
},
params: types.DefaultParams(),
},
missionID: 1,
address: addr[7],
expectedBalance: tc.Coin(t, "0foo"),
missionID: 1,
address: addr[7],
err: types.ErrNoClaimable,
},
{
name: "should allow distributing airdrop with other already completed missions",
Expand All @@ -276,15 +290,84 @@ func TestKeeper_CompleteMission(t *testing.T) {
Claimable: sdk.NewIntFromUint64(10000),
CompletedMissions: []uint64{0, 1, 2, 4, 5, 6},
},
params: types.DefaultParams(),
},
missionID: 3,
address: addr[8],
expectedBalance: tc.Coin(t, "3000bar"),
},
{
name: "should allow applying decay factor if enabled",
inputState: inputState{
airdropSupply: tc.Coin(t, "1000foo"),
mission: types.Mission{
MissionID: 1,
Weight: tc.Dec(t, "0.5"),
},
claimRecord: types.ClaimRecord{
Address: addr[9],
Claimable: sdk.NewIntFromUint64(1000),
},
params: types.NewParams(types.NewEnabledDecay(
time.Unix(1000, 0),
time.Unix(2000, 0),
)),
blockTime: time.Unix(1500, 0),
},
missionID: 1,
address: addr[9],
expectedBalance: tc.Coin(t, "250foo"),
},
{
name: "should allow distributing all funds if decay factor if enabled and decay not started",
inputState: inputState{
airdropSupply: tc.Coin(t, "1000foo"),
mission: types.Mission{
MissionID: 1,
Weight: tc.Dec(t, "0.5"),
},
claimRecord: types.ClaimRecord{
Address: addr[10],
Claimable: sdk.NewIntFromUint64(1000),
},
params: types.NewParams(types.NewEnabledDecay(
time.Unix(1000, 0),
time.Unix(2000, 0),
)),
blockTime: time.Unix(999, 0),
},
missionID: 1,
address: addr[10],
expectedBalance: tc.Coin(t, "500foo"),
},
{
name: "should prevent distributing funds if decay ended",
inputState: inputState{
airdropSupply: tc.Coin(t, "1000foo"),
mission: types.Mission{
MissionID: 1,
Weight: tc.Dec(t, "0.5"),
},
claimRecord: types.ClaimRecord{
Address: addr[11],
Claimable: sdk.NewIntFromUint64(1000),
},
params: types.NewParams(types.NewEnabledDecay(
time.Unix(1000, 0),
time.Unix(2000, 0),
)),
blockTime: time.Unix(2001, 0),
},
missionID: 1,
address: addr[11],
err: types.ErrNoClaimable,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// initialize input state
require.NoError(t, tt.inputState.params.Validate())
tk.ClaimKeeper.SetParams(ctx, tt.inputState.params)
if !tt.inputState.noAirdropSupply {
err := tk.ClaimKeeper.InitializeAirdropSupply(ctx, tt.inputState.airdropSupply)
require.NoError(t, err)
Expand All @@ -295,6 +378,9 @@ func TestKeeper_CompleteMission(t *testing.T) {
if !tt.inputState.noClaimRecord {
tk.ClaimKeeper.SetClaimRecord(ctx, tt.inputState.claimRecord)
}
if !tt.inputState.blockTime.IsZero() {
ctx = ctx.WithBlockTime(tt.inputState.blockTime)
}

err := tk.ClaimKeeper.CompleteMission(ctx, tt.missionID, tt.address)
if tt.err != nil {
Expand Down
11 changes: 9 additions & 2 deletions x/claim/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ import (
)

// GetParams get all parameters as types.Params
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
return types.NewParams()
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
k.paramstore.GetParamSet(ctx, &params)
return params
}

// SetParams set the params
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramstore.SetParamSet(ctx, &params)
}

// DecayInformation returns the param that defines decay information
func (k Keeper) DecayInformation(ctx sdk.Context) (totalSupplyRange types.DecayInformation) {
k.paramstore.Get(ctx, types.KeyDecayInformation, &totalSupplyRange)
return
}
8 changes: 5 additions & 3 deletions x/claim/keeper/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper_test

import (
"testing"
"time"

"github.com/stretchr/testify/require"

Expand All @@ -12,9 +13,10 @@ import (
func TestGetParams(t *testing.T) {
ctx, tk, _ := testkeeper.NewTestSetup(t)

params := types.DefaultParams()

params := types.NewParams(types.NewEnabledDecay(
time.Unix(1000, 0),
time.Unix(10000, 0),
))
tk.ClaimKeeper.SetParams(ctx, params)

require.EqualValues(t, params, tk.ClaimKeeper.GetParams(ctx))
}
Loading

0 comments on commit 0cf0d76

Please sign in to comment.