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!: add MsgTerminatePrivatePlan #154

Merged
merged 4 commits into from
May 16, 2023
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ The documentation is available in [docs](docs) directory. If you are a developer
* [Crescent Official Docs](https://docs.crescent.network/)
* [Swagger API Docs](https://app.swaggerhub.com/apis-docs/crescent/crescent/2.0.0)

<!-- markdown-link-check-disable -->

## Community

* [Official Website](https://crescent.network/)
Expand Down
2 changes: 1 addition & 1 deletion docs/cli/claim.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Description: A high-level overview of how the command-line interfaces (CLI) work

## Synopsis

This document provides a high-level overview of how the command line (CLI) interface works for the `claim` module. To set up a local testing environment, it requires 0.24.1 or lower versions of [Ignite CLI](https://docs.ignite.com/). If you don't have Ignite CLI set up in your local machine, see [this guide](https://docs.ignite.com/guide/install.html) to install it. Run this command under the project root directory `$ ignite chain serve -c config-test.yml`.
This document provides a high-level overview of how the command line (CLI) interface works for the `claim` module.

Note that [jq](https://stedolan.github.io/jq/) is recommended to be installed as it is used to process JSON throughout the document.

Expand Down
2 changes: 1 addition & 1 deletion docs/cli/farming.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Description: A high-level overview of how the command-line interfaces (CLI) work

## Synopsis

This document provides a high-level overview of how the command line (CLI) interface works for the `farming` module. To set up a local testing environment, it requires 0.24.1 or lower versions of [Ignite CLI](https://docs.ignite.com/). If you don't have Ignite CLI set up in your local machine, see [this guide](https://docs.ignite.com/guide/install.html) to install it. Run this command under the project root directory `$ ignite chain serve -c config-test.yml`.
This document provides a high-level overview of how the command line (CLI) interface works for the `farming` module.

Note that [jq](https://stedolan.github.io/jq/) is recommended to be installed as it is used to process JSON throughout the document.

Expand Down
3 changes: 0 additions & 3 deletions docs/cli/liquidfarming.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ Description: A high-level overview of how the command-line interfaces (CLI) work
## Synopsis

This document provides a high-level overview of how the command line (CLI) interface works for the `liquidfarming` module.
To set up a local testing environment, it requires 0.24.1 or lower versions of [Ignite CLI](https://docs.ignite.com/).
If you don't have Ignite CLI set up in your local machine, see [this guide](https://docs.ignite.com/guide/install.html) to install it.
Run this command under the project root directory `$ ignite chain serve -c config-test.yml`.

Note that [jq](https://stedolan.github.io/jq/) is recommended to be installed as it is used to process JSON throughout the document.

Expand Down
2 changes: 1 addition & 1 deletion docs/cli/liquidity.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Description: A high-level overview of how the command-line interfaces (CLI) work

## Synopsis

This document provides a high-level overview of how the command line (CLI) interface works for the `liquidity` module. To set up a local testing environment, it requires 0.24.1 or lower versions of [Ignite CLI](https://docs.ignite.com/). If you don't have Ignite CLI set up in your local machine, see [this guide](https://docs.ignite.com/guide/install.html) to install it. Run this command under the project root directory `$ ignite chain serve -c config-test.yml`.
This document provides a high-level overview of how the command line (CLI) interface works for the `liquidity` module.

Note that [jq](https://stedolan.github.io/jq/) is recommended to be installed as it is used to process JSON throughout the document.

Expand Down
2 changes: 1 addition & 1 deletion docs/cli/liquidstaking.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Description: A high-level overview of how the command-line interfaces (CLI) work

## Synopsis

This document provides a high-level overview of how the command line (CLI) interface works for the `liquidstaking` module. To set up a local testing environment, it requires 0.24.1 or lower versions of [Ignite CLI](https://docs.ignite.com/). If you don't have Ignite CLI set up in your local machine, see [this guide](https://docs.ignite.com/guide/install.html) to install it. Run this command under the project root directory `$ ignite chain serve -c config-test.yml`.
This document provides a high-level overview of how the command line (CLI) interface works for the `liquidstaking` module.

Note that [jq](https://stedolan.github.io/jq/) is recommended to be installed as it is used to process JSON throughout the document.

Expand Down
6 changes: 1 addition & 5 deletions docs/cli/lpfarm.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ Description: A high-level overview of how the command-line interfaces (CLI) work
## Synopsis

This document provides a high-level overview of how the command line (CLI)
interface works for the `lpfarm` module. To set up a local testing environment, it requires the latest
[Ignite CLI](https://docs.ignite.com/). If you don't have Ignite CLI set up in your local machine,
see [this guide](https://docs.ignite.com/guide/install.html) to install it. Run this command under the project root
directory
`$ ignite chain serve -v -c config-test.yml`.
interface works for the `lpfarm` module.

Note that [jq](https://stedolan.github.io/jq/) is recommended to be installed as it is used to process JSON throughout
the document.
Expand Down
2 changes: 1 addition & 1 deletion docs/cli/marketmaker.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Description: A high-level overview of how the command-line interfaces (CLI) work

## Synopsis

This document provides a high-level overview of how the command line (CLI) interface works for the `marketmaker` module. To set up a local testing environment, it requires 0.24.1 or lower versions of [Ignite CLI](https://docs.ignite.com/). If you don't have Ignite CLI set up in your local machine, see [this guide](https://docs.ignite.com/guide/install.html) to install it. Run this command under the project root directory `$ ignite chain serve -v -c config-test.yml`.
This document provides a high-level overview of how the command line (CLI) interface works for the `marketmaker` module.

Note that [jq](https://stedolan.github.io/jq/) is recommended to be installed as it is used to process JSON throughout the document.

Expand Down
8 changes: 8 additions & 0 deletions proto/crescent/lpfarm/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ option (gogoproto.goproto_getters_all) = false;

service Msg {
rpc CreatePrivatePlan(MsgCreatePrivatePlan) returns (MsgCreatePrivatePlanResponse);
rpc TerminatePrivatePlan(MsgTerminatePrivatePlan) returns (MsgTerminatePrivatePlanResponse);
rpc Farm(MsgFarm) returns (MsgFarmResponse);
rpc Unfarm(MsgUnfarm) returns (MsgUnfarmResponse);
rpc Harvest(MsgHarvest) returns (MsgHarvestResponse);
Expand All @@ -30,6 +31,13 @@ message MsgCreatePrivatePlanResponse {
string farming_pool_address = 2;
}

message MsgTerminatePrivatePlan {
string creator = 1;
uint64 plan_id = 2;
}

message MsgTerminatePrivatePlanResponse {}

message MsgFarm {
string farmer = 1;
cosmos.base.v1beta1.Coin coin = 2 [(gogoproto.nullable) = false];
Expand Down
37 changes: 37 additions & 0 deletions x/lpfarm/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func GetTxCmd() *cobra.Command {

cmd.AddCommand(
NewCreatePrivatePlanCmd(),
NewTerminatePrivatePlanCmd(),
NewFarmCmd(),
NewUnfarmCmd(),
NewHarvestCmd(),
Expand Down Expand Up @@ -117,6 +118,42 @@ $ %s tx %s create-private-plan "New Farming Plan" 2022-01-01T00:00:00Z 2023-01-0
return cmd
}

func NewTerminatePrivatePlanCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "terminate-private-plan [plan-id]",
Args: cobra.ExactArgs(1),
Short: "Terminate a private farming plan",
Long: strings.TrimSpace(
fmt.Sprintf(`Terminate a private farming plan.
The plan's termination address must be same with the message sender(original plan creator).

Example:
$ %s tx %s terminate-private-plan 1 --from mykey
`,
version.AppName, types.ModuleName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

planId, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid plan id: %w", err)
}

msg := types.NewMsgTerminatePrivatePlan(clientCtx.GetFromAddress(), planId)
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}

func NewFarmCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "farm [coin]",
Expand Down
3 changes: 3 additions & 0 deletions x/lpfarm/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgCreatePrivatePlan:
res, err := msgServer.CreatePrivatePlan(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgTerminatePrivatePlan:
res, err := msgServer.TerminatePrivatePlan(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgFarm:
res, err := msgServer.Farm(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
Expand Down
25 changes: 25 additions & 0 deletions x/lpfarm/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

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

"github.com/crescent-network/crescent/v4/x/lpfarm/types"
)
Expand Down Expand Up @@ -41,6 +42,30 @@ func (k msgServer) CreatePrivatePlan(goCtx context.Context, msg *types.MsgCreate
}, nil
}

// TerminatePrivatePlan defines a method to terminate a private plan.
func (k msgServer) TerminatePrivatePlan(goCtx context.Context, msg *types.MsgTerminatePrivatePlan) (*types.MsgTerminatePrivatePlanResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

plan, found := k.GetPlan(ctx, msg.PlanId)
if !found {
return nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "plan not found: %d", msg.PlanId)
}
if !plan.IsPrivate {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "cannot terminate public plan")
}
if plan.TerminationAddress != msg.Creator {
return nil, sdkerrors.Wrapf(
sdkerrors.ErrUnauthorized,
"plan's termination address must be same with the sender's address")
}

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

return &types.MsgTerminatePrivatePlanResponse{}, nil
}

// Farm defines a method for farming coins.
func (k msgServer) Farm(goCtx context.Context, msg *types.MsgFarm) (*types.MsgFarmResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
Expand Down
51 changes: 51 additions & 0 deletions x/lpfarm/keeper/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package keeper_test

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

utils "github.com/crescent-network/crescent/v4/types"
"github.com/crescent-network/crescent/v4/x/lpfarm/keeper"
"github.com/crescent-network/crescent/v4/x/lpfarm/types"
)

Expand Down Expand Up @@ -61,6 +63,55 @@ func (s *KeeperTestSuite) TestCreatePrivatePlan_PairNotFound() {
err, "pair 2 not found: not found")
}

func (s *KeeperTestSuite) TestTerminatePrivatePlan() {
s.createPairWithLastPrice("denom1", "denom2", sdk.NewDec(1))
s.createPool(1, utils.ParseCoins("100_000000denom1,100_000000denom2"))

creatorAddr := utils.TestAddress(1)
s.fundAddr(creatorAddr, s.keeper.GetPrivatePlanCreationFee(s.ctx))
plan, err := s.keeper.CreatePrivatePlan(
s.ctx, creatorAddr, "", []types.RewardAllocation{
types.NewPairRewardAllocation(1, utils.ParseCoins("100_000000stake")),
}, sampleStartTime, sampleEndTime)
s.Require().NoError(err)
s.fundAddr(plan.GetFarmingPoolAddress(), utils.ParseCoins("10000_000000stake"))

s.farm(utils.TestAddress(2), utils.ParseCoin("1000000pool1"))

s.nextBlock()
s.nextBlock()
s.nextBlock()

balancesBefore := s.getBalances(creatorAddr)
remainingFarmingRewards := s.getBalances(plan.GetFarmingPoolAddress())

msgServer := keeper.NewMsgServerImpl(s.keeper)
msg := types.NewMsgTerminatePrivatePlan(creatorAddr, 1)
_, err = msgServer.TerminatePrivatePlan(sdk.WrapSDKContext(s.ctx), msg)
s.Require().NoError(err)

s.assertEq(sdk.Coins{}, s.getBalances(plan.GetFarmingPoolAddress()))
s.assertEq(balancesBefore.Add(remainingFarmingRewards...), s.getBalances(creatorAddr))
}

func (s *KeeperTestSuite) TestTerminatePrivatePlan_Unauthorized() {
s.createPairWithLastPrice("denom1", "denom2", sdk.NewDec(1))

creatorAddr := utils.TestAddress(1)
s.fundAddr(creatorAddr, s.keeper.GetPrivatePlanCreationFee(s.ctx))
plan, err := s.keeper.CreatePrivatePlan(
s.ctx, creatorAddr, "", []types.RewardAllocation{
types.NewPairRewardAllocation(1, utils.ParseCoins("100_000000stake")),
}, sampleStartTime, sampleEndTime)
s.Require().NoError(err)
s.fundAddr(plan.GetFarmingPoolAddress(), utils.ParseCoins("10000_000000stake"))

msgServer := keeper.NewMsgServerImpl(s.keeper)
msg := types.NewMsgTerminatePrivatePlan(utils.TestAddress(2), 1)
_, err = msgServer.TerminatePrivatePlan(sdk.WrapSDKContext(s.ctx), msg)
s.Require().ErrorIs(err, sdkerrors.ErrUnauthorized)
}

func (s *KeeperTestSuite) TestAllocateRewards_NoFarmer() {
s.createPairWithLastPrice("denom1", "denom2", sdk.NewDec(1))
s.createPool(1, utils.ParseCoins("100_000000denom1,100_000000denom2"))
Expand Down
83 changes: 71 additions & 12 deletions x/lpfarm/simulation/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ import (

// Simulation operation weights constants.
const (
OpWeightMsgCreatePrivatePlan = "op_weight_msg_create_private_plan"
OpWeightMsgFarm = "op_weight_msg_farm"
OpWeightMsgUnfarm = "op_weight_msg_unfarm"
OpWeightMsgHarvest = "op_weight_msg_harvest"
OpWeightMsgCreatePrivatePlan = "op_weight_msg_create_private_plan"
OpWeightMsgTerminatePrivatePlan = "op_weight_msg_terminate_private_plan"
OpWeightMsgFarm = "op_weight_msg_farm"
OpWeightMsgUnfarm = "op_weight_msg_unfarm"
OpWeightMsgHarvest = "op_weight_msg_harvest"

DefaultWeightCreatePrivatePlan = 10
DefaultWeightFarm = 40
DefaultWeightUnfarm = 50
DefaultWeightHarvest = 20
DefaultWeightCreatePrivatePlan = 10
DefaultWeightTerminatePrivatePlan = 5
DefaultWeightFarm = 40
DefaultWeightUnfarm = 50
DefaultWeightHarvest = 20
)

var (
Expand All @@ -43,14 +45,18 @@ func WeightedOperations(
ak types.AccountKeeper, bk types.BankKeeper, lk types.LiquidityKeeper, k keeper.Keeper,
) simulation.WeightedOperations {
var (
weightMsgCreatePrivatePlan int
weightMsgFarm int
weightMsgUnfarm int
weightMsgHarvest int
weightMsgCreatePrivatePlan int
weightMsgTerminatePrivatePlan int
weightMsgFarm int
weightMsgUnfarm int
weightMsgHarvest int
)
appParams.GetOrGenerate(cdc, OpWeightMsgCreatePrivatePlan, &weightMsgCreatePrivatePlan, nil, func(_ *rand.Rand) {
weightMsgCreatePrivatePlan = DefaultWeightCreatePrivatePlan
})
appParams.GetOrGenerate(cdc, OpWeightMsgTerminatePrivatePlan, &weightMsgTerminatePrivatePlan, nil, func(_ *rand.Rand) {
weightMsgTerminatePrivatePlan = DefaultWeightTerminatePrivatePlan
})
appParams.GetOrGenerate(cdc, OpWeightMsgFarm, &weightMsgFarm, nil, func(_ *rand.Rand) {
weightMsgFarm = DefaultWeightFarm
})
Expand All @@ -66,6 +72,10 @@ func WeightedOperations(
weightMsgCreatePrivatePlan,
SimulateMsgCreatePrivatePlan(ak, bk, lk, k),
),
simulation.NewWeightedOperation(
weightMsgTerminatePrivatePlan,
SimulateMsgTerminatePrivatePlan(ak, bk, k),
),
simulation.NewWeightedOperation(
weightMsgFarm,
SimulateMsgFarm(ak, bk),
Expand Down Expand Up @@ -137,6 +147,55 @@ func SimulateMsgCreatePrivatePlan(
}
}

func SimulateMsgTerminatePrivatePlan(
ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
accs = utils.ShuffleSimAccounts(r, accs)
var simAccount simtypes.Account
var planId uint64
skip := true
for _, simAccount = range accs {
k.IterateAllPlans(ctx, func(plan types.Plan) (stop bool) {
if !plan.IsTerminated && plan.GetTerminationAddress().Equals(simAccount.Address) {
planId = plan.Id
return true
}
return false
})
if planId > 0 {
skip = false
break
}
}
if skip {
return simtypes.NoOpMsg(
types.ModuleName, types.TypeMsgFarm, "no account to farm"), nil, nil
}

msg := types.NewMsgTerminatePrivatePlan(simAccount.Address, planId)

txCtx := simulation.OperationInput{
R: r,
App: app,
TxGen: appparams.MakeTestEncodingConfig().TxConfig,
Msg: msg,
MsgType: msg.Type(),
Context: ctx,
SimAccount: simAccount,
AccountKeeper: ak,
Bankkeeper: bk,
ModuleName: types.ModuleName,
CoinsSpentInMsg: bk.SpendableCoins(ctx, simAccount.Address),
}

return utils.GenAndDeliverTxWithFees(txCtx, gas, fees)
}
}

func SimulateMsgFarm(ak types.AccountKeeper, bk types.BankKeeper) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
Expand Down
Loading