Skip to content

Commit

Permalink
feat(campaign): create special allocations type (#786)
Browse files Browse the repository at this point in the history
* proto

* special allocations type

* type test

* fix proto

* add campaign validate

* add invariant

* fix cli testrs

* alex commen

* fix tests

* fix invariant test

* fix simulation

Co-authored-by: Giuseppe Natale <12249307+giunatale@users.noreply.github.com>
  • Loading branch information
lumtis and giunatale authored May 16, 2022
1 parent e1cca3e commit 9e38ac7
Show file tree
Hide file tree
Showing 16 changed files with 765 additions and 67 deletions.
4 changes: 3 additions & 1 deletion proto/campaign/campaign.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ option go_package = "github.com/tendermint/spn/x/campaign/types";

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";
import "campaign/special_allocations.proto";

message Campaign {
uint64 campaignID = 1;
Expand All @@ -23,5 +24,6 @@ message Campaign {
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.Coin",
(gogoproto.castrepeated) = "Shares"
];
bytes metadata = 9;
SpecialAllocations specialAllocations = 9 [(gogoproto.nullable) = false];
bytes metadata = 10;
}
20 changes: 20 additions & 0 deletions proto/campaign/special_allocations.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
syntax = "proto3";
package tendermint.spn.campaign;

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

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";

message SpecialAllocations {
repeated cosmos.base.v1beta1.Coin genesisDistribution = 1 [
(gogoproto.nullable) = false,
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.Coin",
(gogoproto.castrepeated) = "Shares"
];
repeated cosmos.base.v1beta1.Coin claimableAirdrop = 2 [
(gogoproto.nullable) = false,
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.Coin",
(gogoproto.castrepeated) = "Shares"
];
}
18 changes: 17 additions & 1 deletion testutil/sample/campaign.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ func Shares(r *rand.Rand) campaign.Shares {
return campaign.NewSharesFromCoins(Coins(r))
}

// SpecialAllocations returns a sample special allocations
func SpecialAllocations(r *rand.Rand) campaign.SpecialAllocations {
return campaign.NewSpecialAllocations(Shares(r), Shares(r))
}

// ShareVestingOptions returns a sample ShareVestingOptions
func ShareVestingOptions(r *rand.Rand) campaign.ShareVestingOptions {
// use vesting shares as total shares
Expand Down Expand Up @@ -63,14 +68,25 @@ func CampaignName(r *rand.Rand) string {

// Campaign returns a sample campaign
func Campaign(r *rand.Rand, id uint64) campaign.Campaign {
return campaign.NewCampaign(
genesisDistribution := Shares(r)
claimableAirdrop := Shares(r)
shares := campaign.IncreaseShares(genesisDistribution, claimableAirdrop)

campaign := campaign.NewCampaign(
id,
CampaignName(r),
Uint64(r),
TotalSupply(r),
Metadata(r, 20),
Duration(r).Milliseconds(),
)

// set random shares for special allocations
campaign.AllocatedShares = shares
campaign.SpecialAllocations.GenesisDistribution = genesisDistribution
campaign.SpecialAllocations.ClaimableAirdrop = claimableAirdrop

return campaign
}

// MainnetAccount returns a sample MainnetAccount
Expand Down
14 changes: 8 additions & 6 deletions x/campaign/client/cli/query_campaign_summary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"google.golang.org/grpc/status"

"github.com/tendermint/spn/testutil/network"
"github.com/tendermint/spn/testutil/nullify"
"github.com/tendermint/spn/x/campaign/client/cli"
"github.com/tendermint/spn/x/campaign/types"
launchtypes "github.com/tendermint/spn/x/launch/types"
Expand All @@ -30,9 +31,10 @@ func networkWithCampaignSummariesObjects(t *testing.T, n int) (*network.Network,

for i := 0; i < n; i++ {
campaign := types.Campaign{
CampaignID: uint64(i),
TotalSupply: sdk.NewCoins(),
AllocatedShares: types.Shares(sdk.NewCoins()),
CampaignID: uint64(i),
TotalSupply: sdk.NewCoins(),
AllocatedShares: types.Shares(sdk.NewCoins()),
SpecialAllocations: types.EmptySpecialAllocations(),
}
campaignState.CampaignChainsList = append(campaignState.CampaignChainsList, types.CampaignChains{
CampaignID: uint64(i),
Expand Down Expand Up @@ -93,7 +95,7 @@ func TestListCampaignSummary(t *testing.T) {
var resp types.QueryCampaignSummariesResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
require.LessOrEqual(t, len(resp.CampaignSummaries), step)
require.Subset(t, objs, resp.CampaignSummaries)
require.Subset(t, nullify.Fill(objs), nullify.Fill(resp.CampaignSummaries))
}
})
t.Run("ByKey", func(t *testing.T) {
Expand All @@ -106,7 +108,7 @@ func TestListCampaignSummary(t *testing.T) {
var resp types.QueryCampaignSummariesResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
require.LessOrEqual(t, len(resp.CampaignSummaries), step)
require.Subset(t, objs, resp.CampaignSummaries)
require.Subset(t, nullify.Fill(objs), nullify.Fill(resp.CampaignSummaries))
next = resp.Pagination.NextKey
}
})
Expand All @@ -118,7 +120,7 @@ func TestListCampaignSummary(t *testing.T) {
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
require.NoError(t, err)
require.Equal(t, len(objs), int(resp.Pagination.Total))
require.ElementsMatch(t, objs, resp.CampaignSummaries)
require.ElementsMatch(t, nullify.Fill(objs), nullify.Fill(resp.CampaignSummaries))
})
}

Expand Down
24 changes: 5 additions & 19 deletions x/campaign/client/cli/query_campaign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"google.golang.org/grpc/status"

"github.com/tendermint/spn/testutil/network"
"github.com/tendermint/spn/testutil/nullify"
"github.com/tendermint/spn/testutil/sample"
"github.com/tendermint/spn/x/campaign/client/cli"
"github.com/tendermint/spn/x/campaign/types"
Expand Down Expand Up @@ -73,12 +74,7 @@ func TestShowCampaign(t *testing.T) {
require.NoError(t, err)
var resp types.QueryGetCampaignResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))

// EmptyShares should be Shares(nil) however UnmarshalJSON returns Shares{}
// Both are empty shares, this allows to fix the tests
resp.Campaign.AllocatedShares = types.EmptyShares()

require.EqualValues(t, tc.obj, resp.Campaign)
require.Equal(t, nullify.Fill(tc.obj), nullify.Fill(resp.Campaign))
}
})
}
Expand All @@ -87,12 +83,6 @@ func TestShowCampaign(t *testing.T) {
func TestListCampaign(t *testing.T) {
net, objs := networkWithCampaignObjects(t, 5)

nullify := func(campaigns []types.Campaign) {
for i := range campaigns {
campaigns[i].AllocatedShares = types.EmptyShares()
}
}

ctx := net.Validators[0].ClientCtx
request := func(next []byte, offset, limit uint64, total bool) []string {
args := []string{
Expand All @@ -117,9 +107,8 @@ func TestListCampaign(t *testing.T) {
require.NoError(t, err)
var resp types.QueryAllCampaignResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
nullify(resp.Campaign)
require.LessOrEqual(t, len(resp.Campaign), step)
require.Subset(t, objs, resp.Campaign)
require.Subset(t, nullify.Fill(objs), nullify.Fill(resp.Campaign))
}
})
t.Run("ByKey", func(t *testing.T) {
Expand All @@ -131,9 +120,8 @@ func TestListCampaign(t *testing.T) {
require.NoError(t, err)
var resp types.QueryAllCampaignResponse
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
nullify(resp.Campaign)
require.LessOrEqual(t, len(resp.Campaign), step)
require.Subset(t, objs, resp.Campaign)
require.Subset(t, nullify.Fill(objs), nullify.Fill(resp.Campaign))
next = resp.Pagination.NextKey
}
})
Expand All @@ -145,8 +133,6 @@ func TestListCampaign(t *testing.T) {
require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp))
require.NoError(t, err)
require.Equal(t, len(objs), int(resp.Pagination.Total))

nullify(resp.Campaign)
require.ElementsMatch(t, objs, resp.Campaign)
require.ElementsMatch(t, nullify.Fill(objs), nullify.Fill(resp.Campaign))
})
}
10 changes: 9 additions & 1 deletion x/campaign/keeper/invariants.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ func VestingAccountWithoutCampaignInvariant(k Keeper) sdk.Invariant {
}

// CampaignSharesInvariant invariant that checks, for all campaigns, if the amount of allocated shares is equal to
// the sum of `MainnetVestingAccount` and `MainnetAccount` shares plus the amount of vouchers in circulation
// the sum of `MainnetVestingAccount` and `MainnetAccount` shares plus
// the amount of vouchers in circulation plus
// the total shares of special allocations
func CampaignSharesInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
accountSharesByCampaign := make(map[uint64]types.Shares)
Expand Down Expand Up @@ -141,6 +143,12 @@ func CampaignSharesInvariant(k Keeper) sdk.Invariant {
vShares, _ := types.VouchersToShares(vouchers, campaignID)
expectedAllocatedSharesShares = types.IncreaseShares(expectedAllocatedSharesShares, vShares)

// increase expected shares with special allocations
expectedAllocatedSharesShares = types.IncreaseShares(
expectedAllocatedSharesShares,
campaign.SpecialAllocations.TotalShares(),
)

if !types.IsEqualShares(expectedAllocatedSharesShares, campaign.GetAllocatedShares()) {
return sdk.FormatInvariant(
types.ModuleName, campaignSharesRoute,
Expand Down
13 changes: 13 additions & 0 deletions x/campaign/keeper/invariants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,17 @@ func TestCampaignSharesInvariant(t *testing.T) {
msg, broken := keeper.CampaignSharesInvariant(*tk.CampaignKeeper)(ctx)
require.True(t, broken, msg)
})

t.Run("campaign with special allocations not tracked by allocated shares", func(t *testing.T) {
ctx, tk, _ := testkeeper.NewTestSetup(t)
campaign := sample.Campaign(r, 3)
campaign.SpecialAllocations.GenesisDistribution = types.IncreaseShares(
campaign.SpecialAllocations.GenesisDistribution,
sample.Shares(r),
)
tk.CampaignKeeper.SetCampaign(ctx, campaign)

msg, broken := keeper.CampaignSharesInvariant(*tk.CampaignKeeper)(ctx)
require.True(t, broken, msg)
})
}
8 changes: 6 additions & 2 deletions x/campaign/types/campaign.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package types

import (
"errors"
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/pkg/errors"
)

const (
Expand All @@ -27,6 +27,7 @@ func NewCampaign(
MainnetInitialized: false,
TotalSupply: totalSupply,
AllocatedShares: EmptyShares(),
SpecialAllocations: EmptySpecialAllocations(),
Metadata: metadata,
CreatedAt: createdAt,
}
Expand All @@ -46,10 +47,13 @@ func (m Campaign) Validate(totalShareNumber uint64) error {
}

if IsTotalSharesReached(m.AllocatedShares, totalShareNumber) {

return errors.New("more allocated shares than total shares")
}

if err := m.SpecialAllocations.Validate(); err != nil {
return errors.Wrap(err, "invalid special allocations")
}

return nil
}

Expand Down
Loading

0 comments on commit 9e38ac7

Please sign in to comment.