Skip to content

Commit

Permalink
feat(campaign): add event for campaign auction creation (#841)
Browse files Browse the repository at this point in the history
* initialize hook

* add campaign auction event

* add hooks

* upgrade fundraising

* add hooks

* emit event

* emit for both auction type

* error handling

* add remove keeper methods

* test body

* test cases

* liunt

* add issue links

* Update proto/campaign/events.proto

Co-authored-by: Alex Johnson <alex.johnson@tendermint.com>

* ID

Co-authored-by: Alex Johnson <alex.johnson@tendermint.com>
Co-authored-by: Danilo Pantani <danpantani@gmail.com>
  • Loading branch information
3 people authored Jun 20, 2022
1 parent ee188db commit b6a5d1d
Show file tree
Hide file tree
Showing 11 changed files with 598 additions and 40 deletions.
5 changes: 5 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,11 @@ func New(
app.BankKeeper,
)

// set fundraising hooks
app.FundraisingKeeper = *app.FundraisingKeeper.SetHooks(
app.CampaignKeeper.CampaignAuctionEventHooks(),
)

// this line is used by starport scaffolding # stargate/app/keeperDefinition

// register the staking hooks
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/spf13/cast v1.4.1
github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.1
github.com/tendermint/fundraising v0.3.0
github.com/tendermint/fundraising v0.3.1-0.20220613014523-03b4a2d4481a
github.com/tendermint/tendermint v0.34.19
github.com/tendermint/tm-db v0.6.7
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1504,8 +1504,8 @@ github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s
github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U=
github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI=
github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk=
github.com/tendermint/fundraising v0.3.0 h1:VtHfmVlAS93MUDlt6Em21l3taw6s9kLY/w8Cd1FB9fM=
github.com/tendermint/fundraising v0.3.0/go.mod h1:oJFZUZ/GsACtkYeWScKpHLdqMUThNWpMAi/G47LJUi4=
github.com/tendermint/fundraising v0.3.1-0.20220613014523-03b4a2d4481a h1:DIxap6r3z89JLoaLp6TTtt8XS7Zgfy4XACfG6b+4plE=
github.com/tendermint/fundraising v0.3.1-0.20220613014523-03b4a2d4481a/go.mod h1:oJFZUZ/GsACtkYeWScKpHLdqMUThNWpMAi/G47LJUi4=
github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E=
github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/tendermint/spm v0.1.8 h1:ya0o4Um6Hht2LC1X/y0+9Rz0Q8DyUhRIIXR9vgDUTGA=
Expand Down
5 changes: 5 additions & 0 deletions proto/campaign/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,8 @@ message EventMainnetVestingAccountUpdated {
string address = 2;
ShareVestingOptions vestingOptions = 3 [(gogoproto.nullable) = false];
}

message EventCampaignAuctionCreated {
uint64 campaignID = 1;
uint64 auctionID = 2;
}
6 changes: 6 additions & 0 deletions x/campaign/keeper/campaign.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ func (k Keeper) GetCampaign(ctx sdk.Context, id uint64) (val types.Campaign, fou
return val, true
}

// RemoveCampaign removes a campaign from the store
func (k Keeper) RemoveCampaign(ctx sdk.Context, id uint64) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.CampaignKey))
store.Delete(GetCampaignIDBytes(id))
}

// GetAllCampaign returns all campaign
func (k Keeper) GetAllCampaign(ctx sdk.Context) (list []types.Campaign) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.CampaignKey))
Expand Down
200 changes: 200 additions & 0 deletions x/campaign/keeper/campaign_auction_event_hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package keeper

import (
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

fundraisingtypes "github.com/tendermint/fundraising/x/fundraising/types"
"github.com/tendermint/spn/x/campaign/types"
profiletypes "github.com/tendermint/spn/x/profile/types"
)

// EmitCampaignAuctionCreated emits EventCampaignAuctionCreated event if an auction is created for a campaign from a coordinator
func (k Keeper) EmitCampaignAuctionCreated(
ctx sdk.Context,
auctionID uint64,
auctioneer string,
sellingCoin sdk.Coin,
) (bool, error) {
campaignID, err := types.VoucherCampaign(sellingCoin.Denom)
if err != nil {
// not a campaign auction
return false, nil
}

// verify the auctioneer is the coordinator of the campaign
campaign, found := k.GetCampaign(ctx, campaignID)
if !found {
return false, sdkerrors.Wrapf(types.ErrCampaignNotFound,
"voucher %s is associated to an non-existing campaign %d",
sellingCoin.Denom,
campaignID,
)

}
coord, found := k.profileKeeper.GetCoordinator(ctx, campaign.CoordinatorID)
if !found {
return false, sdkerrors.Wrapf(profiletypes.ErrCoordInvalid,
"campaign %d coordinator doesn't exist %d",
campaignID,
campaign.CoordinatorID,
)
}

// if the coordinator if the auctioneer, we emit a CampaignAuctionCreated event
if coord.Address != auctioneer {
return false, nil
}

err = ctx.EventManager().EmitTypedEvents(
&types.EventCampaignAuctionCreated{
CampaignID: campaignID,
AuctionID: auctionID,
},
)
if err != nil {
return false, err
}

return true, nil
}

// CampaignAuctionEventHooks returns a CampaignAuctionEventHooks associated with the campaign keeper
func (k Keeper) CampaignAuctionEventHooks() CampaignAuctionEventHooks {
return CampaignAuctionEventHooks{
campaignKeeper: k,
}
}

// CampaignAuctionEventHooks implements fundraising hooks and emit events on auction creation
type CampaignAuctionEventHooks struct {
campaignKeeper Keeper
}

// Implements FundraisingHooks interface
var _ fundraisingtypes.FundraisingHooks = CampaignAuctionEventHooks{}

// AfterFixedPriceAuctionCreated emits a CampaignAuctionCreated event if created for a campaign
func (h CampaignAuctionEventHooks) AfterFixedPriceAuctionCreated(
ctx sdk.Context,
auctionID uint64,
auctioneer string,
_ sdk.Dec,
sellingCoin sdk.Coin,
_ string,
_ []fundraisingtypes.VestingSchedule,
_ time.Time,
_ time.Time,
) {
// TODO: investigate error handling for hooks
// https://github.com/tendermint/spn/issues/869
_, _ = h.campaignKeeper.EmitCampaignAuctionCreated(ctx, auctionID, auctioneer, sellingCoin)
}

// AfterBatchAuctionCreated emits a CampaignAuctionCreated event if created for a campaign
func (h CampaignAuctionEventHooks) AfterBatchAuctionCreated(
ctx sdk.Context,
auctionID uint64,
auctioneer string,
_ sdk.Dec,
_ sdk.Dec,
sellingCoin sdk.Coin,
_ string,
_ []fundraisingtypes.VestingSchedule,
_ uint32,
_ sdk.Dec,
_ time.Time,
_ time.Time,
) {
// TODO: investigate error handling for hooks
// https://github.com/tendermint/spn/issues/869
_, _ = h.campaignKeeper.EmitCampaignAuctionCreated(ctx, auctionID, auctioneer, sellingCoin)
}

// BeforeFixedPriceAuctionCreated implements FundraisingHooks
func (h CampaignAuctionEventHooks) BeforeFixedPriceAuctionCreated(
_ sdk.Context,
_ string,
_ sdk.Dec,
_ sdk.Coin,
_ string,
_ []fundraisingtypes.VestingSchedule,
_ time.Time,
_ time.Time,
) {
}

// BeforeBatchAuctionCreated implements FundraisingHooks
func (h CampaignAuctionEventHooks) BeforeBatchAuctionCreated(
_ sdk.Context,
_ string,
_ sdk.Dec,
_ sdk.Dec,
_ sdk.Coin,
_ string,
_ []fundraisingtypes.VestingSchedule,
_ uint32,
_ sdk.Dec,
_ time.Time,
_ time.Time,
) {
}

// BeforeAuctionCanceled implements FundraisingHooks
func (h CampaignAuctionEventHooks) BeforeAuctionCanceled(
_ sdk.Context,
_ uint64,
_ string,
) {
}

// BeforeBidPlaced implements FundraisingHooks
func (h CampaignAuctionEventHooks) BeforeBidPlaced(
_ sdk.Context,
_ uint64,
_ uint64,
_ string,
_ fundraisingtypes.BidType,
_ sdk.Dec,
_ sdk.Coin,
) {
}

// BeforeBidModified implements FundraisingHooks
func (h CampaignAuctionEventHooks) BeforeBidModified(
_ sdk.Context,
_ uint64,
_ uint64,
_ string,
_ fundraisingtypes.BidType,
_ sdk.Dec,
_ sdk.Coin,
) {
}

// BeforeAllowedBiddersAdded implements FundraisingHooks
func (h CampaignAuctionEventHooks) BeforeAllowedBiddersAdded(
_ sdk.Context,
_ []fundraisingtypes.AllowedBidder,
) {
}

// BeforeAllowedBidderUpdated implements FundraisingHooks
func (h CampaignAuctionEventHooks) BeforeAllowedBidderUpdated(
_ sdk.Context,
_ uint64,
_ sdk.AccAddress,
_ sdk.Int,
) {
}

// BeforeSellingCoinsAllocated implements FundraisingHooks
func (h CampaignAuctionEventHooks) BeforeSellingCoinsAllocated(
_ sdk.Context,
_ uint64,
_ map[string]sdk.Int,
_ map[string]sdk.Int,
) {
}
126 changes: 126 additions & 0 deletions x/campaign/keeper/campaign_auction_event_hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package keeper_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
tc "github.com/tendermint/spn/testutil/constructor"
testkeeper "github.com/tendermint/spn/testutil/keeper"
"github.com/tendermint/spn/testutil/sample"
"github.com/tendermint/spn/x/campaign/types"
profiletypes "github.com/tendermint/spn/x/profile/types"
)

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

type inputState struct {
noCampaign bool
noCoordinator bool
campaign types.Campaign
coordinator profiletypes.Coordinator
}

coordinator := sample.Address(r)

tests := []struct {
name string
inputState inputState
auctionId uint64
auctioneer string
sellingCoin sdk.Coin
emitted bool
err error
}{
{
name: "should prevent emitting event if selling coin is not a voucher",
inputState: inputState{
noCampaign: true,
noCoordinator: true,
},
sellingCoin: tc.Coin(t, "1000foo"),
emitted: false,
},
{
name: "should return error if selling coin is a voucher of a non existing campaign",
inputState: inputState{
noCampaign: true,
noCoordinator: true,
},
sellingCoin: tc.Coin(t, "1000"+types.VoucherDenom(5, "foo")),
err: types.ErrCampaignNotFound,
},
{
name: "should return error if selling coin is a voucher of a campaign with non existing coordinator",
inputState: inputState{
campaign: types.Campaign{
CampaignID: 10,
CoordinatorID: 20,
},
noCoordinator: true,
},
sellingCoin: tc.Coin(t, "1000"+types.VoucherDenom(10, "foo")),
err: profiletypes.ErrCoordInvalid,
},
{
name: "should prevent emitting event if the auctioneer is not the coordinator of the campaign",
inputState: inputState{
campaign: types.Campaign{
CampaignID: 100,
CoordinatorID: 200,
},
coordinator: profiletypes.Coordinator{
CoordinatorID: 200,
Address: sample.Address(r),
},
},
auctioneer: sample.Address(r),
sellingCoin: tc.Coin(t, "1000"+types.VoucherDenom(100, "foo")),
emitted: false,
},
{
name: "should allow emitting event if the auctioneer is the coordinator of the campaign",
inputState: inputState{
campaign: types.Campaign{
CampaignID: 1000,
CoordinatorID: 2000,
},
coordinator: profiletypes.Coordinator{
CoordinatorID: 2000,
Address: coordinator,
},
},
auctioneer: coordinator,
sellingCoin: tc.Coin(t, "1000"+types.VoucherDenom(1000, "foo")),
emitted: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// initialize input state
if !tt.inputState.noCampaign {
tk.CampaignKeeper.SetCampaign(ctx, tt.inputState.campaign)
}
if !tt.inputState.noCoordinator {
tk.ProfileKeeper.SetCoordinator(ctx, tt.inputState.coordinator)
}

emitted, err := tk.CampaignKeeper.EmitCampaignAuctionCreated(ctx, tt.auctionId, tt.auctioneer, tt.sellingCoin)
if tt.err != nil {
require.ErrorIs(t, err, tt.err)
} else {
require.NoError(t, err)
require.EqualValues(t, tt.emitted, emitted)
}

// clean state
if !tt.inputState.noCampaign {
tk.CampaignKeeper.RemoveCampaign(ctx, tt.inputState.campaign.CampaignID)
}
if !tt.inputState.noCoordinator {
tk.ProfileKeeper.RemoveCoordinator(ctx, tt.inputState.coordinator.CoordinatorID)
}
})
}
}
10 changes: 10 additions & 0 deletions x/campaign/keeper/campaign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ func TestCampaignGet(t *testing.T) {
}
}

func TestCampaignRemove(t *testing.T) {
ctx, tk, _ := testkeeper.NewTestSetup(t)
items := createNCampaign(tk.CampaignKeeper, ctx, 10)
for _, item := range items {
tk.CampaignKeeper.RemoveCampaign(ctx, item.CampaignID)
_, found := tk.CampaignKeeper.GetCampaign(ctx, item.CampaignID)
require.False(t, found)
}
}

func TestCampaignGetAll(t *testing.T) {
ctx, tk, _ := testkeeper.NewTestSetup(t)
items := createNCampaign(tk.CampaignKeeper, ctx, 10)
Expand Down
Loading

0 comments on commit b6a5d1d

Please sign in to comment.