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

feat(campaign): create special allocations type #786

Merged
merged 16 commits into from
May 16, 2022
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 @@ -22,5 +23,6 @@ message Campaign {
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.Coin",
(gogoproto.castrepeated) = "Shares"
];
bytes metadata = 8;
SpecialAllocations specialAllocations = 8 [(gogoproto.nullable) = false];
bytes metadata = 9;
}
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"
];
}
5 changes: 5 additions & 0 deletions 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
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: 6 additions & 18 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,9 @@ 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)
resp.Campaign.SpecialAllocations = types.EmptySpecialAllocations()
require.Equal(t, nullify.Fill(tc.obj), resp.Campaign)
}
})
}
Expand All @@ -87,12 +85,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 +109,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 +122,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 +135,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
10 changes: 10 additions & 0 deletions x/campaign/keeper/invariants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,14 @@ 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 = sample.SpecialAllocations(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"
giunatale marked this conversation as resolved.
Show resolved Hide resolved
)

const (
Expand All @@ -20,6 +20,7 @@ func NewCampaign(campaignID uint64, campaignName string, coordinatorID uint64, t
MainnetInitialized: false,
TotalSupply: totalSupply,
AllocatedShares: EmptyShares(),
SpecialAllocations: EmptySpecialAllocations(),
Metadata: metadata,
}
}
Expand All @@ -38,10 +39,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
112 changes: 84 additions & 28 deletions x/campaign/types/campaign.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading