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

fix: terminate plan when deleting public plan proposal #162

Merged
merged 7 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions x/farming/keeper/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,12 @@ func (k Keeper) CreateRatioPlan(ctx sdk.Context, msg *types.MsgCreateRatioPlan,
// TerminatePlan sends all remaining coins in the plan's farming pool to
// the termination address and mark the plan as terminated.
func (k Keeper) TerminatePlan(ctx sdk.Context, plan types.PlanI) error {
balances := k.bankKeeper.GetAllBalances(ctx, plan.GetFarmingPoolAddress())
if balances.IsAllPositive() {
if err := k.bankKeeper.SendCoins(ctx, plan.GetFarmingPoolAddress(), plan.GetTerminationAddress(), balances); err != nil {
return err
if plan.GetFarmingPoolAddress().String() != plan.GetTerminationAddress().String() {
balances := k.bankKeeper.GetAllBalances(ctx, plan.GetFarmingPoolAddress())
if balances.IsAllPositive() {
if err := k.bankKeeper.SendCoins(ctx, plan.GetFarmingPoolAddress(), plan.GetTerminationAddress(), balances); err != nil {
return err
}
}
}

Expand All @@ -258,7 +260,6 @@ func (k Keeper) TerminatePlan(ctx sdk.Context, plan types.PlanI) error {
sdk.NewAttribute(types.AttributeKeyPlanId, strconv.FormatUint(plan.GetId(), 10)),
sdk.NewAttribute(types.AttributeKeyFarmingPoolAddress, plan.GetFarmingPoolAddress().String()),
sdk.NewAttribute(types.AttributeKeyTerminationAddress, plan.GetTerminationAddress().String()),
// TODO: add refunded coins?
),
})

Expand Down
4 changes: 4 additions & 0 deletions x/farming/keeper/proposal_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ func (k Keeper) DeletePublicPlanProposal(ctx sdk.Context, proposals []*types.Del
return sdkerrors.Wrapf(sdkerrors.ErrNotFound, "plan %d is not found", p.GetPlanId())
}

if err := k.TerminatePlan(ctx, plan); err != nil {
panic(err)
}

k.RemovePlan(ctx, plan)

logger := k.Logger(ctx)
Expand Down
169 changes: 122 additions & 47 deletions x/farming/keeper/proposal_handler_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package keeper_test

import (
"fmt"

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

Expand Down Expand Up @@ -406,80 +408,153 @@ func (suite *KeeperTestSuite) TestValidateDeletePublicPlanProposal() {

func (suite *KeeperTestSuite) TestUpdatePlanType() {
// create a ratio public plan
addRequests := []*types.AddRequestProposal{
types.NewAddRequestProposal(
"testPlan",
suite.addrs[0].String(),
suite.addrs[0].String(),
sdk.NewDecCoins(
sdk.NewDecCoinFromDec(denom1, sdk.NewDecWithPrec(3, 1)), // 30%
sdk.NewDecCoinFromDec(denom2, sdk.NewDecWithPrec(7, 1)), // 70%
),
types.ParseTime("2021-08-01T00:00:00Z"),
types.ParseTime("2021-08-30T00:00:00Z"),
nil,
sdk.NewDecWithPrec(10, 2), // 10%
),
}

err := keeper.HandlePublicPlanProposal(
suite.ctx,
suite.keeper,
types.NewPublicPlanProposal("testTitle", "testDescription", addRequests, nil, nil),
types.NewPublicPlanProposal("testTitle", "testDescription", []*types.AddRequestProposal{
types.NewAddRequestProposal(
"testPlan",
suite.addrs[0].String(),
suite.addrs[0].String(),
sdk.NewDecCoins(
sdk.NewDecCoinFromDec(denom1, sdk.NewDecWithPrec(3, 1)),
sdk.NewDecCoinFromDec(denom2, sdk.NewDecWithPrec(7, 1)),
),
types.ParseTime("0001-01-01T00:00:00Z"),
types.ParseTime("9999-12-31T00:00:00Z"),
sdk.NewCoins(),
sdk.NewDecWithPrec(10, 2),
),
}, nil, nil),
)
suite.Require().NoError(err)

plan, found := suite.keeper.GetPlan(suite.ctx, uint64(1))
suite.Require().Equal(true, found)
suite.Require().Equal(plan.(*types.RatioPlan).EpochRatio, sdk.NewDecWithPrec(10, 2))

// update the ratio plan to fixed amount plan type
updateRequests := []*types.UpdateRequestProposal{
types.NewUpdateRequestProposal(
plan.GetId(),
plan.GetName(),
plan.GetFarmingPoolAddress().String(),
plan.GetTerminationAddress().String(),
plan.GetStakingCoinWeights(),
plan.GetStartTime(),
plan.GetEndTime(),
sdk.NewCoins(sdk.NewInt64Coin("stake", 100_000)),
sdk.ZeroDec(),
)}

// update the ratio plan type to fixed amount plan type
err = keeper.HandlePublicPlanProposal(
suite.ctx,
suite.keeper,
types.NewPublicPlanProposal("testTitle", "testDescription", nil, updateRequests, nil),
types.NewPublicPlanProposal("testTitle", "testDescription", nil, []*types.UpdateRequestProposal{
types.NewUpdateRequestProposal(
plan.GetId(),
plan.GetName(),
plan.GetFarmingPoolAddress().String(),
plan.GetTerminationAddress().String(),
plan.GetStakingCoinWeights(),
plan.GetStartTime(),
plan.GetEndTime(),
sdk.NewCoins(sdk.NewInt64Coin("stake", 100_000)),
sdk.ZeroDec(),
),
}, nil),
)
suite.Require().NoError(err)

plan, found = suite.keeper.GetPlan(suite.ctx, uint64(1))
suite.Require().Equal(true, found)
suite.Require().Equal(plan.(*types.FixedAmountPlan).EpochAmount, sdk.NewCoins(sdk.NewInt64Coin("stake", 100_000)))

// update the fixed amount plan back to ratio plan
updateRequests = []*types.UpdateRequestProposal{
types.NewUpdateRequestProposal(
plan.GetId(),
plan.GetName(),
plan.GetFarmingPoolAddress().String(),
plan.GetTerminationAddress().String(),
plan.GetStakingCoinWeights(),
plan.GetStartTime(),
plan.GetEndTime(),
nil,
sdk.NewDecWithPrec(7, 2), // 7%
)}

// update back to ratio plan with different epoch ratio
err = keeper.HandlePublicPlanProposal(
suite.ctx,
suite.keeper,
types.NewPublicPlanProposal("testTitle", "testDescription", nil, updateRequests, nil),
types.NewPublicPlanProposal("testTitle", "testDescription", nil, []*types.UpdateRequestProposal{
types.NewUpdateRequestProposal(
plan.GetId(),
plan.GetName(),
plan.GetFarmingPoolAddress().String(),
plan.GetTerminationAddress().String(),
plan.GetStakingCoinWeights(),
plan.GetStartTime(),
plan.GetEndTime(),
nil,
sdk.NewDecWithPrec(7, 2), // 7%
),
}, nil),
)
suite.Require().NoError(err)

plan, found = suite.keeper.GetPlan(suite.ctx, uint64(1))
suite.Require().Equal(true, found)
suite.Require().Equal(plan.(*types.RatioPlan).EpochRatio, sdk.NewDecWithPrec(7, 2))
}

func (suite *KeeperTestSuite) TestDeletePublicPlan() {
for _, tc := range []struct {
name string
farmingPoolAddr sdk.AccAddress
terminationAddr sdk.AccAddress
expectedBalances sdk.Coins
}{
{
"farming pool address and termination address are equal",
suite.addrs[0],
suite.addrs[0],
initialBalances,
},
{
"farming pool address and termination address are not equal",
suite.addrs[1],
suite.addrs[2],
sdk.Coins{},
},
} {
suite.Run(tc.name, func() {
cacheCtx, _ := suite.ctx.CacheContext()

// create a public plan
err := keeper.HandlePublicPlanProposal(
cacheCtx,
suite.keeper,
types.NewPublicPlanProposal("testTitle", "testDescription", []*types.AddRequestProposal{
types.NewAddRequestProposal(
"testPlan",
tc.farmingPoolAddr.String(),
tc.terminationAddr.String(),
sdk.NewDecCoins(
sdk.NewDecCoinFromDec(denom1, sdk.NewDecWithPrec(3, 1)),
sdk.NewDecCoinFromDec(denom2, sdk.NewDecWithPrec(7, 1)),
),
types.ParseTime("0001-01-01T00:00:00Z"),
types.ParseTime("9999-12-31T00:00:00Z"),
sdk.NewCoins(sdk.NewInt64Coin(denom3, 100_000_000)),
sdk.ZeroDec(),
),
}, nil, nil),
)
suite.Require().NoError(err)

plans := suite.keeper.GetPlans(cacheCtx)

// delete the plan
err = keeper.HandlePublicPlanProposal(
cacheCtx,
suite.keeper,
types.NewPublicPlanProposal("testTitle", "testDescription", nil, nil, []*types.DeleteRequestProposal{
types.NewDeleteRequestProposal(plans[0].GetId()),
}),
)
suite.Require().NoError(err)

// the plan should be successfully removed and coins meet the expected balances
_, found := suite.keeper.GetPlan(cacheCtx, plans[0].GetId())
suite.Require().Equal(false, found)
suite.Require().Equal(tc.expectedBalances, suite.app.BankKeeper.GetAllBalances(cacheCtx, tc.farmingPoolAddr))

isPlanTerminatedEventType := false
for _, e := range cacheCtx.EventManager().ABCIEvents() {
if e.Type == types.EventTypePlanTerminated {
suite.Require().Equal(fmt.Sprint(plans[0].GetId()), string(e.Attributes[0].Value))
suite.Require().Equal(tc.farmingPoolAddr.String(), string(e.Attributes[1].Value))
suite.Require().Equal(tc.terminationAddr.String(), string(e.Attributes[2].Value))
isPlanTerminatedEventType = true
break
}
}
suite.Require().True(isPlanTerminatedEventType, "plan_terminated events should be emitted")
})
}
}
12 changes: 10 additions & 2 deletions x/farming/spec/08_proposal.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

# Proposal

The farming module contains the following public plan governance proposal that receives one of the following requests. A request `AddRequestProposal` that creates a public farming plan, a request `UpdateRequestProposal` that updates the plan, and a request `DeleteRequestProposal` that deletes the plan. For most cases, it expects that a single request is used, but note that `PublicPlanProposal` accepts more than a single request to cover broader use cases. Also,
The farming module contains the following public plan governance proposal that receives one of the following requests.

- `AddRequestProposal` is the request proposal that requests the module to create a public farming plan. You can either input epoch amount `EpochAmount` or epoch ratio `EpochRatio`. Depending on which value of the parameter you input, it creates the following plan type `FixedAmountPlan` or `RatioPlan`.

- `UpdateRequestProposal` is the request proposal that requests the module to update the plan. You can also update the plan type.

- `DeleteRequestProposal` is the request proposal that requests the module to delete the plan. It sends all remaining coins in the plan's farming pool `FarmingPoolAddress` to the termination address `TerminationAddress` and mark the plan as terminated.

## PublicPlanProposal

Expand All @@ -26,7 +32,7 @@ type PublicPlanProposal struct {

## AddRequestProposal

Note that when requesting `AddRequestProposal` depending on which field is passed, either `EpochAmount` or `EpochRatio`, it creates a `FixedAmountPlan` or `RatioPlan`.
You can either input epoch amount `EpochAmount` or epoch ratio `EpochRatio`. Depending on which value of the parameter you input, it creates the following plan type `FixedAmountPlan` or `RatioPlan`.

```go
// AddRequestProposal details a proposal for creating a public plan.
Expand Down Expand Up @@ -82,6 +88,8 @@ type UpdateRequestProposal struct {

## DeleteRequestProposal

The request requests the module to delete the plan and it sends all remaining coins in the plan's farming pool `FarmingPoolAddress` to the termination address `TerminationAddress` and mark the plan as terminated.

```go
// DeleteRequestProposal details a proposal for deleting an existing public plan.
type DeleteRequestProposal struct {
Expand Down