Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(claim): add checks in genesis validate for already completed missions #877

Merged
merged 11 commits into from
Jun 21, 2022
2 changes: 1 addition & 1 deletion x/claim/keeper/mission.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (k Keeper) CompleteMission(ctx sdk.Context, missionID uint64, address strin
claimRecord.CompletedMissions = append(claimRecord.CompletedMissions, missionID)

// calculate claimable from mission weight and claim
claimableAmount := mission.Weight.Mul(claimRecord.Claimable.ToDec()).TruncateInt()
claimableAmount := claimRecord.ClaimableFromMission(mission)
claimable := sdk.NewCoins(sdk.NewCoin(airdropSupply.Denom, claimableAmount))

// decrease airdrop supply
Expand Down
5 changes: 5 additions & 0 deletions x/claim/types/claim_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ func (m ClaimRecord) IsMissionCompleted(missionID uint64) bool {
}
return false
}

// ClaimableFromMission returns the amount claimable for this claim record from the provided mission completion
func (m ClaimRecord) ClaimableFromMission(mission Mission) sdk.Int {
return mission.Weight.Mul(m.Claimable.ToDec()).TruncateInt()
}
70 changes: 70 additions & 0 deletions x/claim/types/claim_record_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types_test

import (
tc "github.com/tendermint/spn/testutil/constructor"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
lumtis marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -98,3 +99,72 @@ func TestClaimRecord_IsMissionCompleted(t *testing.T) {
CompletedMissions: []uint64{0, 1, 2, 3},
}.IsMissionCompleted(3))
}

func TestClaimRecord_ClaimableFromMission(t *testing.T) {
for _, tt := range []struct {
desc string
claimRecord claim.ClaimRecord
mission claim.Mission
expected sdk.Int
}{
{
desc: "should allow get claimable from mission with full weight",
claimRecord: claim.ClaimRecord{
Claimable: sdk.NewIntFromUint64(100),
},
mission: claim.Mission{
Weight: sdk.OneDec(),
},
expected: sdk.NewIntFromUint64(100),
},
{
desc: "should allow get claimable from mission with empty weight",
claimRecord: claim.ClaimRecord{
Claimable: sdk.NewIntFromUint64(100),
},
mission: claim.Mission{
Weight: sdk.ZeroDec(),
},
expected: sdk.ZeroInt(),
},
{
desc: "should allow get claimable from mission with half weight",
claimRecord: claim.ClaimRecord{
Claimable: sdk.NewIntFromUint64(100),
},
mission: claim.Mission{
Weight: tc.Dec(t, "0.5"),
},
expected: sdk.NewIntFromUint64(50),
},
{
desc: "should allow get claimable and cut decimal",
claimRecord: claim.ClaimRecord{
Claimable: sdk.NewIntFromUint64(201),
},
mission: claim.Mission{
Weight: tc.Dec(t, "0.5"),
},
expected: sdk.NewIntFromUint64(100),
},
{
desc: "should return no claimable if decimal cut",
claimRecord: claim.ClaimRecord{
Claimable: sdk.NewIntFromUint64(1),
},
mission: claim.Mission{
Weight: tc.Dec(t, "0.99"),
},
expected: sdk.NewIntFromUint64(0),
},
} {
t.Run(tt.desc, func(t *testing.T) {
got := tt.claimRecord.ClaimableFromMission(tt.mission)
require.True(t, got.Equal(tt.expected),
"expected: %s, got %s",
tt.expected.String(),
got.String(),
)
})
}
}
70 changes: 43 additions & 27 deletions x/claim/types/genesis.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"errors"
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -20,54 +21,69 @@ func DefaultGenesis() *GenesisState {
// Validate performs basic genesis state validation returning an error upon any
// failure.
func (gs GenesisState) Validate() error {
// Check airdrop supply
err := gs.AirdropSupply.Validate()
if err != nil {
return err
}

// Check for duplicated address in claimRecord
claimSum := sdk.ZeroInt()
claimRecordIndexMap := make(map[string]struct{})
for _, elem := range gs.ClaimRecords {
err := elem.Validate()
// Check missions
weightSum := sdk.ZeroDec()
missionMap := make(map[uint64]Mission)
for _, mission := range gs.Missions {
err := mission.Validate()
if err != nil {
return err
}

address := string(ClaimRecordKey(elem.Address))
claimSum = claimSum.Add(elem.Claimable)
if _, ok := claimRecordIndexMap[address]; ok {
return fmt.Errorf("duplicated address for claim record")
weightSum = weightSum.Add(mission.Weight)
if _, ok := missionMap[mission.MissionID]; ok {
return errors.New("duplicated id for mission")
}
claimRecordIndexMap[address] = struct{}{}
missionMap[mission.MissionID] = mission
}

// verify airdropSupply == sum of claimRecords
if !gs.AirdropSupply.Amount.Equal(claimSum) {
return fmt.Errorf("airdrop supply amount not equal to sum of claimable amounts")
// ensure mission weight sum is 1
if len(gs.Missions) > 0 {
if !weightSum.Equal(sdk.OneDec()) {
return errors.New("sum of mission weights must be 1")
}
}

// Check for duplicated ID in mission
weightSum := sdk.ZeroDec()
missionIDMap := make(map[uint64]struct{})
for _, elem := range gs.Missions {
err := elem.Validate()
// Check claim records
claimSum := sdk.ZeroInt()
claimRecordMap := make(map[string]struct{})
for _, claimRecord := range gs.ClaimRecords {
err := claimRecord.Validate()
if err != nil {
return err
}

weightSum = weightSum.Add(elem.Weight)
if _, ok := missionIDMap[elem.MissionID]; ok {
return fmt.Errorf("duplicated id for mission")
// Check claim record completed missions
claimable := claimRecord.Claimable
for _, completedMission := range claimRecord.CompletedMissions {
mission, ok := missionMap[completedMission]
if !ok {
return fmt.Errorf("address %s completed a non existing mission %d",
claimRecord.Address,
completedMission,
)
}

// Reduce clamaible with already claimed funds
claimable = claimable.Sub(claimRecord.ClaimableFromMission(mission))
}
missionIDMap[elem.MissionID] = struct{}{}
}

// ensure mission weight sum is 1
if len(gs.Missions) > 0 {
if !weightSum.Equal(sdk.OneDec()) {
return fmt.Errorf("sum of mission weights must be 1")
claimSum = claimSum.Add(claimable)
if _, ok := claimRecordMap[claimRecord.Address]; ok {
return errors.New("duplicated address for claim record")
}
claimRecordMap[claimRecord.Address] = struct{}{}
}

// verify airdropSupply == sum of claimRecords
if !gs.AirdropSupply.Amount.Equal(claimSum) {
return errors.New("airdrop supply amount not equal to sum of claimable amounts")
}

// this line is used by starport scaffolding # genesis/types/validate
Expand Down
Loading