Skip to content

Commit

Permalink
feat(campaign): prevent adding shares to account after mainnet laun…
Browse files Browse the repository at this point in the history
…ch is triggered (#711)

* add check for triggered chain in messages

* add tests

* add simulations and refactor

* fix tests

* remove unnecessary error-checking

* refactor sim tests

* do not return on err

* address comments
  • Loading branch information
giunatale authored Apr 12, 2022
1 parent 9db3978 commit 3d612dd
Show file tree
Hide file tree
Showing 15 changed files with 683 additions and 252 deletions.
30 changes: 30 additions & 0 deletions x/campaign/keeper/campaign_mainnet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package keeper

import (
"fmt"

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

// IsCampaignMainnetLaunchTriggered returns true if the provided campaign has an associated mainnet chain whose launch
// has been already triggered
func (k Keeper) IsCampaignMainnetLaunchTriggered(ctx sdk.Context, campaignID uint64) (bool, error) {
campaign, found := k.GetCampaign(ctx, campaignID)
if !found {
return false, fmt.Errorf("campaign %d not found", campaignID)
}

if campaign.MainnetInitialized {
chain, found := k.launchKeeper.GetChain(ctx, campaign.MainnetID)
if !found {
return false, fmt.Errorf("mainnet chain %d for campaign %d not found", campaign.MainnetID, campaignID)
}
if !chain.IsMainnet {
return false, fmt.Errorf("chain %d for campaign %d is not a mainnet chain", campaign.MainnetID, campaignID)
}
if chain.LaunchTriggered {
return true, nil
}
}
return false, nil
}
76 changes: 76 additions & 0 deletions x/campaign/keeper/campaign_mainnet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package keeper_test

import (
"testing"

"github.com/stretchr/testify/require"

testkeeper "github.com/tendermint/spn/testutil/keeper"
"github.com/tendermint/spn/testutil/sample"
)

func TestIsCampaignMainnetLaunchTriggered(t *testing.T) {
ctx, tk, _ := testkeeper.NewTestSetup(t)

t.Run("campaign has mainnet with launch triggered", func(t *testing.T) {
campaignMainnetLaunched := sample.Campaign(r, 0)
campaignMainnetLaunched.MainnetInitialized = true
chainLaunched := sample.Chain(r, 0, 0)
chainLaunched.LaunchTriggered = true
chainLaunched.IsMainnet = true
campaignMainnetLaunched.MainnetID = tk.LaunchKeeper.AppendChain(ctx, chainLaunched)
campaignMainnetLaunched.CampaignID = tk.CampaignKeeper.AppendCampaign(ctx, campaignMainnetLaunched)
res, err := tk.CampaignKeeper.IsCampaignMainnetLaunchTriggered(ctx, campaignMainnetLaunched.CampaignID)
require.NoError(t, err)
require.True(t, res)
})

t.Run("campaign has mainnet with launch not triggered", func(t *testing.T) {
campaignMainnetInitialized := sample.Campaign(r, 1)
campaignMainnetInitialized.MainnetInitialized = true
chain := sample.Chain(r, 0, 0)
chain.LaunchTriggered = false
chain.IsMainnet = true
campaignMainnetInitialized.MainnetID = tk.LaunchKeeper.AppendChain(ctx, chain)
campaignMainnetInitialized.CampaignID = tk.CampaignKeeper.AppendCampaign(ctx, campaignMainnetInitialized)
res, err := tk.CampaignKeeper.IsCampaignMainnetLaunchTriggered(ctx, campaignMainnetInitialized.CampaignID)
require.NoError(t, err)
require.False(t, res)
})

t.Run("campaign with mainnnet not initialized", func(t *testing.T) {
campaignMainnetNotInitialized := sample.Campaign(r, 2)
campaignMainnetNotInitialized.MainnetInitialized = false
campaignMainnetNotInitialized.CampaignID = tk.CampaignKeeper.AppendCampaign(ctx, campaignMainnetNotInitialized)
res, err := tk.CampaignKeeper.IsCampaignMainnetLaunchTriggered(ctx, campaignMainnetNotInitialized.CampaignID)
require.NoError(t, err)
require.False(t, res)
})

t.Run("mainnet not found", func(t *testing.T) {
campaignMainnetNotFound := sample.Campaign(r, 3)
campaignMainnetNotFound.MainnetInitialized = true
campaignMainnetNotFound.MainnetID = 1000
campaignMainnetNotFound.CampaignID = tk.CampaignKeeper.AppendCampaign(ctx, campaignMainnetNotFound)
_, err := tk.CampaignKeeper.IsCampaignMainnetLaunchTriggered(ctx, campaignMainnetNotFound.CampaignID)
require.Error(t, err)
})

t.Run("associated mainnet chain is not mainnet", func(t *testing.T) {
campaignInvalidMainnet := sample.Campaign(r, 4)
campaignInvalidMainnet.MainnetInitialized = true
chainNoMainnet := sample.Chain(r, 0, 0)
chainNoMainnet.LaunchTriggered = false
chainNoMainnet.IsMainnet = false
campaignInvalidMainnet.MainnetID = tk.LaunchKeeper.AppendChain(ctx, chainNoMainnet)
campaignInvalidMainnet.CampaignID = tk.CampaignKeeper.AppendCampaign(ctx, campaignInvalidMainnet)
_, err := tk.CampaignKeeper.IsCampaignMainnetLaunchTriggered(ctx, campaignInvalidMainnet.CampaignID)
require.Error(t, err)
})

t.Run("campaign not found", func(t *testing.T) {
_, err := tk.CampaignKeeper.IsCampaignMainnetLaunchTriggered(ctx, 1000)
require.Error(t, err)
})

}
12 changes: 12 additions & 0 deletions x/campaign/keeper/msg_add_shares.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

spnerrors "github.com/tendermint/spn/pkg/errors"
"github.com/tendermint/spn/x/campaign/types"
profiletypes "github.com/tendermint/spn/x/profile/types"
)
Expand All @@ -19,6 +20,17 @@ func (k msgServer) AddShares(goCtx context.Context, msg *types.MsgAddShares) (*t
return nil, sdkerrors.Wrapf(types.ErrCampaignNotFound, "%d", msg.CampaignID)
}

mainnetLaunched, err := k.IsCampaignMainnetLaunchTriggered(ctx, campaign.CampaignID)
if err != nil {
return nil, spnerrors.Critical(err.Error())
}
if mainnetLaunched {
return nil, sdkerrors.Wrap(types.ErrMainnetLaunchTriggered, fmt.Sprintf(
"mainnet %d launch is already triggered",
campaign.MainnetID,
))
}

coordID, err := k.profileKeeper.CoordinatorIDFromAddress(ctx, msg.Coordinator)
if err != nil {
return nil, err
Expand Down
30 changes: 30 additions & 0 deletions x/campaign/keeper/msg_add_shares_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ func TestMsgAddShares(t *testing.T) {
coordAddr1 = sample.Address(r)
coordAddr2 = sample.Address(r)
coordAddrMainnetInitialized = sample.Address(r)
coordAddrMainnetLaunched = sample.Address(r)
campaign = sample.Campaign(r, 0)
campaignInvalidAllocatedShares = sample.Campaign(r, 2)
campaignMainnetInitialized = sample.Campaign(r, 1)
campaignMainnetLaunched = sample.Campaign(r, 3)

sdkCtx, tk, ts = testkeeper.NewTestSetup(t)
ctx = sdk.WrapSDKContext(sdkCtx)
Expand All @@ -45,8 +47,26 @@ func TestMsgAddShares(t *testing.T) {
campaignMainnetInitialized.CoordinatorID = res.CoordinatorID
campaignMainnetInitialized.MainnetInitialized = true
campaignMainnetInitialized.AllocatedShares = allocatedShares
chain := sample.Chain(r, 0, res.CoordinatorID)
chain.LaunchTriggered = false
chain.IsMainnet = true
campaignMainnetInitialized.MainnetID = tk.LaunchKeeper.AppendChain(sdkCtx, chain)
campaignMainnetInitialized.CampaignID = tk.CampaignKeeper.AppendCampaign(sdkCtx, campaignMainnetInitialized)

res, err = ts.ProfileSrv.CreateCoordinator(ctx, &profiletypes.MsgCreateCoordinator{
Address: coordAddrMainnetLaunched,
Description: sample.CoordinatorDescription(r),
})
require.NoError(t, err)
campaignMainnetLaunched.CoordinatorID = res.CoordinatorID
campaignMainnetLaunched.MainnetInitialized = true
campaignMainnetLaunched.AllocatedShares = allocatedShares
chainLaunched := sample.Chain(r, 1, res.CoordinatorID)
chainLaunched.LaunchTriggered = true
chainLaunched.IsMainnet = true
campaignMainnetLaunched.MainnetID = tk.LaunchKeeper.AppendChain(sdkCtx, chainLaunched)
campaignMainnetLaunched.CampaignID = tk.CampaignKeeper.AppendCampaign(sdkCtx, campaignMainnetLaunched)

res, err = ts.ProfileSrv.CreateCoordinator(ctx, &profiletypes.MsgCreateCoordinator{
Address: coordAddr1,
Description: sample.CoordinatorDescription(r),
Expand Down Expand Up @@ -110,6 +130,16 @@ func TestMsgAddShares(t *testing.T) {
Shares: sample.Shares(r),
},
},
{
name: "campaign with launched mainnet",
msg: types.MsgAddShares{
Coordinator: coordAddrMainnetLaunched,
CampaignID: campaignMainnetLaunched.CampaignID,
Address: addr1,
Shares: sample.Shares(r),
},
err: types.ErrMainnetLaunchTriggered,
},
{
name: "allocated shares greater than total shares",
msg: types.MsgAddShares{
Expand Down
11 changes: 11 additions & 0 deletions x/campaign/keeper/msg_add_vesting_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ func (k msgServer) AddVestingOptions(goCtx context.Context, msg *types.MsgAddVes
return nil, sdkerrors.Wrapf(types.ErrCampaignNotFound, "%d", msg.CampaignID)
}

mainnetLaunched, err := k.IsCampaignMainnetLaunchTriggered(ctx, campaign.CampaignID)
if err != nil {
return nil, spnerrors.Critical(err.Error())
}
if mainnetLaunched {
return nil, sdkerrors.Wrap(types.ErrMainnetLaunchTriggered, fmt.Sprintf(
"mainnet %d launch is already triggered",
campaign.MainnetID,
))
}

// Get the coordinator ID associated to the sender address
coordID, err := k.profileKeeper.CoordinatorIDFromAddress(ctx, msg.Coordinator)
if err != nil {
Expand Down
30 changes: 30 additions & 0 deletions x/campaign/keeper/msg_add_vesting_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ func TestMsgAddVestingOptions(t *testing.T) {
coordAddr1 = sample.Address(r)
coordAddr2 = sample.Address(r)
coordAddrMainnetInitialized = sample.Address(r)
coordAddrMainnetLaunched = sample.Address(r)
campaign = sample.Campaign(r, 0)
campaignInvalidAllocatedShares = sample.Campaign(r, 2)
campaignMainnetInitialized = sample.Campaign(r, 1)
campaignMainnetLaunched = sample.Campaign(r, 3)

sdkCtx, tk, ts = testkeeper.NewTestSetup(t)
ctx = sdk.WrapSDKContext(sdkCtx)
Expand All @@ -45,8 +47,26 @@ func TestMsgAddVestingOptions(t *testing.T) {
campaignMainnetInitialized.CoordinatorID = res.CoordinatorID
campaignMainnetInitialized.MainnetInitialized = true
campaignMainnetInitialized.AllocatedShares = allocatedShares
chain := sample.Chain(r, 0, res.CoordinatorID)
chain.IsMainnet = true
chain.LaunchTriggered = false
campaignMainnetInitialized.MainnetID = tk.LaunchKeeper.AppendChain(sdkCtx, chain)
campaignMainnetInitialized.CampaignID = tk.CampaignKeeper.AppendCampaign(sdkCtx, campaignMainnetInitialized)

res, err = ts.ProfileSrv.CreateCoordinator(ctx, &profiletypes.MsgCreateCoordinator{
Address: coordAddrMainnetLaunched,
Description: sample.CoordinatorDescription(r),
})
require.NoError(t, err)
campaignMainnetLaunched.CoordinatorID = res.CoordinatorID
campaignMainnetLaunched.MainnetInitialized = true
campaignMainnetLaunched.AllocatedShares = allocatedShares
chainLaunched := sample.Chain(r, 1, res.CoordinatorID)
chainLaunched.IsMainnet = true
chainLaunched.LaunchTriggered = true
campaignMainnetLaunched.MainnetID = tk.LaunchKeeper.AppendChain(sdkCtx, chainLaunched)
campaignMainnetLaunched.CampaignID = tk.CampaignKeeper.AppendCampaign(sdkCtx, campaignMainnetLaunched)

res, err = ts.ProfileSrv.CreateCoordinator(ctx, &profiletypes.MsgCreateCoordinator{
Address: coordAddr1,
Description: sample.CoordinatorDescription(r),
Expand Down Expand Up @@ -112,6 +132,16 @@ func TestMsgAddVestingOptions(t *testing.T) {
VestingOptions: sample.ShareVestingOptions(r),
},
},
{
name: "campaign with launched mainnet",
msg: types.MsgAddVestingOptions{
Coordinator: coordAddrMainnetLaunched,
CampaignID: campaignMainnetLaunched.CampaignID,
Address: addr1,
VestingOptions: sample.ShareVestingOptions(r),
},
err: types.ErrMainnetLaunchTriggered,
},
{
name: "allocated shares greater them total shares",
msg: types.MsgAddVestingOptions{
Expand Down
12 changes: 12 additions & 0 deletions x/campaign/keeper/msg_redeem_vouchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand All @@ -18,6 +19,17 @@ func (k msgServer) RedeemVouchers(goCtx context.Context, msg *types.MsgRedeemVou
return nil, sdkerrors.Wrapf(types.ErrCampaignNotFound, "%d", msg.CampaignID)
}

mainnetLaunched, err := k.IsCampaignMainnetLaunchTriggered(ctx, campaign.CampaignID)
if err != nil {
return nil, spnerrors.Critical(err.Error())
}
if mainnetLaunched {
return nil, sdkerrors.Wrap(types.ErrMainnetLaunchTriggered, fmt.Sprintf(
"mainnet %d launch is already triggered",
campaign.MainnetID,
))
}

// Convert and validate vouchers first
shares, err := types.VouchersToShares(msg.Vouchers, msg.CampaignID)
if err != nil {
Expand Down
31 changes: 25 additions & 6 deletions x/campaign/keeper/msg_redeem_vouchers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ func TestMsgRedeemVouchers(t *testing.T) {
var (
sdkCtx, tk, ts = testkeeper.NewTestSetup(t)

ctx = sdk.WrapSDKContext(sdkCtx)
addr = sample.AccAddress(r)
existAddr = sample.AccAddress(r)
campaign = sample.Campaign(r, 0)
vouchersTooBig = sdk.NewCoins(
ctx = sdk.WrapSDKContext(sdkCtx)
addr = sample.AccAddress(r)
existAddr = sample.AccAddress(r)
campaign = sample.Campaign(r, 0)
campaignMainnetLaunched = sample.Campaign(r, 1)
vouchersTooBig = sdk.NewCoins(
sdk.NewCoin("v/0/foo", sdk.NewInt(spntypes.TotalShareNumber+1)),
)
)
Expand All @@ -30,10 +31,18 @@ func TestMsgRedeemVouchers(t *testing.T) {
shares, err := types.NewShares("1000foo,500bar,300foobar")
require.NoError(t, err)

// Set campaign
// Set campaigns
campaign.AllocatedShares = shares
campaign.CampaignID = tk.CampaignKeeper.AppendCampaign(sdkCtx, campaign)

campaignMainnetLaunched.MainnetInitialized = true
campaignMainnetLaunched.AllocatedShares = shares
chainLaunched := sample.Chain(r, 0, 0)
chainLaunched.LaunchTriggered = true
chainLaunched.IsMainnet = true
campaignMainnetLaunched.MainnetID = tk.LaunchKeeper.AppendChain(sdkCtx, chainLaunched)
campaignMainnetLaunched.CampaignID = tk.CampaignKeeper.AppendCampaign(sdkCtx, campaignMainnetLaunched)

// Create vouchers
vouchers, err := types.SharesToVouchers(shares, campaign.CampaignID)
require.NoError(t, err)
Expand Down Expand Up @@ -155,6 +164,16 @@ func TestMsgRedeemVouchers(t *testing.T) {
},
err: types.ErrInsufficientVouchers,
},
{
name: "campaign with launched mainnet",
msg: types.MsgRedeemVouchers{
Sender: addr.String(),
Account: addr.String(),
CampaignID: campaignMainnetLaunched.CampaignID,
Vouchers: sample.Coins(r),
},
err: types.ErrMainnetLaunchTriggered,
},
} {
t.Run(tc.name, func(t *testing.T) {
var previousAccount types.MainnetAccount
Expand Down
15 changes: 13 additions & 2 deletions x/campaign/keeper/msg_unredeem_vouchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper

import (
"context"
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand All @@ -13,18 +14,28 @@ import (
func (k msgServer) UnredeemVouchers(goCtx context.Context, msg *types.MsgUnredeemVouchers) (*types.MsgUnredeemVouchersResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

_, found := k.GetCampaign(ctx, msg.CampaignID)
campaign, found := k.GetCampaign(ctx, msg.CampaignID)
if !found {
return nil, sdkerrors.Wrapf(types.ErrCampaignNotFound, "%d", msg.CampaignID)
}

mainnetLaunched, err := k.IsCampaignMainnetLaunchTriggered(ctx, campaign.CampaignID)
if err != nil {
return nil, spnerrors.Critical(err.Error())
}
if mainnetLaunched {
return nil, sdkerrors.Wrap(types.ErrMainnetLaunchTriggered, fmt.Sprintf(
"mainnet %d launch is already triggered",
campaign.MainnetID,
))
}

account, found := k.GetMainnetAccount(ctx, msg.CampaignID, msg.Sender)
if !found {
return nil, sdkerrors.Wrap(types.ErrAccountNotFound, msg.Sender)
}

// Update the shares of the account
var err error
account.Shares, err = types.DecreaseShares(account.Shares, msg.Shares)
if err != nil {
return nil, sdkerrors.Wrap(types.ErrSharesDecrease, err.Error())
Expand Down
Loading

0 comments on commit 3d612dd

Please sign in to comment.