Skip to content

Commit

Permalink
Add DEX Circuit Breaker 2 (#387)
Browse files Browse the repository at this point in the history
* define proto for circuit breaker

* add params for circuit breaker and test cases

* fix failed integration cli test

* add keeper logic

* add circuit breaker checker in all messages

* update naming

* update specs

* update comments

* update swagger

Co-authored-by: dongsam <[email protected]>
  • Loading branch information
jaybxyz and dongsam authored Jun 2, 2021
1 parent 33d732f commit d1c90aa
Show file tree
Hide file tree
Showing 13 changed files with 287 additions and 149 deletions.
2 changes: 1 addition & 1 deletion client/docs/statik/statik.go

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions client/docs/swagger-ui/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ paths:
format: uint32
example: '1'
description: The smallest unit batch height for every liquidity pool.
circuit_breaker_enabled:
type: boolean
format: bool
example: 'false'
title: >-
Circuit breaker enables or disables transaction messages
in liquidity module
description: >-
the response type for the QueryParamsResponse RPC method. This
includes current parameter of the liquidity module.
Expand Down Expand Up @@ -2416,6 +2423,13 @@ definitions:
format: uint32
example: '1'
description: The smallest unit batch height for every liquidity pool.
circuit_breaker_enabled:
type: boolean
format: bool
example: 'false'
title: >-
Circuit breaker enables or disables transaction messages in liquidity
module
description: Params defines the parameters for the liquidity module.
tendermint.liquidity.v1beta1.Pool:
type: object
Expand Down Expand Up @@ -2784,6 +2798,13 @@ definitions:
format: uint32
example: '1'
description: The smallest unit batch height for every liquidity pool.
circuit_breaker_enabled:
type: boolean
format: bool
example: 'false'
title: >-
Circuit breaker enables or disables transaction messages in
liquidity module
description: >-
the response type for the QueryParamsResponse RPC method. This includes
current parameter of the liquidity module.
Expand Down
8 changes: 8 additions & 0 deletions proto/tendermint/liquidity/v1beta1/liquidity.proto
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ message Params {
example: "\"1\"",
format: "uint32"
}];

// Circuit breaker enables or disables transaction messages in liquidity module
bool circuit_breaker_enabled = 10 [
(gogoproto.moretags) = "yaml:\"circuit_breaker_enabled\"",
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"false\"",
format: "bool"
}];
}

// The liquidity pool information
Expand Down
5 changes: 3 additions & 2 deletions x/liquidity/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,12 +480,13 @@ func (s *IntegrationTestSuite) TestGetCmdQueryParams() {
{
"json output",
[]string{fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
`{"pool_types":[{"id":1,"name":"DefaultPoolType","min_reserve_coin_num":2,"max_reserve_coin_num":2,"description":""}],"min_init_deposit_amount":"1000000","init_pool_coin_mint_amount":"1000000","max_reserve_coin_amount":"0","pool_creation_fee":[{"denom":"stake","amount":"100000000"}],"swap_fee_rate":"0.003000000000000000","withdraw_fee_rate":"0.003000000000000000","max_order_amount_ratio":"0.100000000000000000","unit_batch_height":1}`,
`{"pool_types":[{"id":1,"name":"DefaultPoolType","min_reserve_coin_num":2,"max_reserve_coin_num":2,"description":""}],"min_init_deposit_amount":"1000000","init_pool_coin_mint_amount":"1000000","max_reserve_coin_amount":"0","pool_creation_fee":[{"denom":"stake","amount":"100000000"}],"swap_fee_rate":"0.003000000000000000","withdraw_fee_rate":"0.003000000000000000","max_order_amount_ratio":"0.100000000000000000","unit_batch_height":1,"circuit_breaker_enabled":false}`,
},
{
"text output",
[]string{fmt.Sprintf("--%s=text", tmcli.OutputFlag)},
`init_pool_coin_mint_amount: "1000000"
`circuit_breaker_enabled: false
init_pool_coin_mint_amount: "1000000"
max_order_amount_ratio: "0.100000000000000000"
max_reserve_coin_amount: "0"
min_init_deposit_amount: "1000000"
Expand Down
6 changes: 6 additions & 0 deletions x/liquidity/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,9 @@ func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSpace.SetParamSet(ctx, &params)
}

// GetCircuitBreakerEnabled returns circuit breaker enabled param from the paramspace.
func (k Keeper) GetCircuitBreakerEnabled(ctx sdk.Context) (enabled bool) {
k.paramSpace.Get(ctx, types.KeyCircuitBreakerEnabled, &enabled)
return
}
19 changes: 16 additions & 3 deletions x/liquidity/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

lapp "github.com/tendermint/liquidity/app"
Expand Down Expand Up @@ -43,9 +44,21 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.queryClient = types.NewQueryClient(queryHelper)
}

func TestParams(t *testing.T) {
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

func TestCircuitBreakerEnabled(t *testing.T) {
app, ctx := createTestInput()

enabled := app.LiquidityKeeper.GetCircuitBreakerEnabled(ctx)
require.Equal(t, false, enabled)

params := app.LiquidityKeeper.GetParams(ctx)
params.CircuitBreakerEnabled = true

app.LiquidityKeeper.SetParams(ctx, params)

enabled = app.LiquidityKeeper.GetCircuitBreakerEnabled(ctx)
require.Equal(t, true, enabled)
}
56 changes: 40 additions & 16 deletions x/liquidity/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,21 @@ var _ types.MsgServer = msgServer{}
// Message server, handler for CreatePool msg
func (k msgServer) CreatePool(goCtx context.Context, msg *types.MsgCreatePool) (*types.MsgCreatePoolResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if k.GetCircuitBreakerEnabled(ctx) {
return nil, types.ErrCircuitBreakerEnabled
}

pool, err := k.Keeper.CreatePool(ctx, msg)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(

ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCreatePool,
sdk.NewAttribute(types.AttributeValuePoolId, strconv.FormatUint(pool.Id, 10)),
Expand All @@ -50,59 +54,71 @@ func (k msgServer) CreatePool(goCtx context.Context, msg *types.MsgCreatePool) (
sdk.NewAttribute(types.AttributeValueDepositCoins, msg.DepositCoins.String()),
sdk.NewAttribute(types.AttributeValuePoolCoinDenom, pool.PoolCoinDenom),
),
)
})

return &types.MsgCreatePoolResponse{}, nil
}

// Message server, handler for MsgDepositWithinBatch
func (k msgServer) DepositWithinBatch(goCtx context.Context, msg *types.MsgDepositWithinBatch) (*types.MsgDepositWithinBatchResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if k.GetCircuitBreakerEnabled(ctx) {
return nil, types.ErrCircuitBreakerEnabled
}

// TODO: remove redundant GetPoolBatch
poolBatch, found := k.GetPoolBatch(ctx, msg.PoolId)
if !found {
return nil, types.ErrPoolBatchNotExists
}

batchMsg, err := k.Keeper.DepositLiquidityPoolToBatch(ctx, msg)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(

ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeDepositWithinBatch,
sdk.NewAttribute(types.AttributeValuePoolId, strconv.FormatUint(batchMsg.Msg.PoolId, 10)),
sdk.NewAttribute(types.AttributeValueBatchIndex, strconv.FormatUint(poolBatch.Index, 10)),
sdk.NewAttribute(types.AttributeValueMsgIndex, strconv.FormatUint(batchMsg.MsgIndex, 10)),
sdk.NewAttribute(types.AttributeValueDepositCoins, batchMsg.Msg.DepositCoins.String()),
),
)
})

return &types.MsgDepositWithinBatchResponse{}, nil
}

// Message server, handler for MsgWithdrawWithinBatch
func (k msgServer) WithdrawWithinBatch(goCtx context.Context, msg *types.MsgWithdrawWithinBatch) (*types.MsgWithdrawWithinBatchResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if k.GetCircuitBreakerEnabled(ctx) {
return nil, types.ErrCircuitBreakerEnabled
}

// TODO: remove redundant GetPoolBatch
poolBatch, found := k.GetPoolBatch(ctx, msg.PoolId)
if !found {
return nil, types.ErrPoolBatchNotExists
}

batchMsg, err := k.Keeper.WithdrawLiquidityPoolToBatch(ctx, msg)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(

ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeWithdrawWithinBatch,
sdk.NewAttribute(types.AttributeValuePoolId, strconv.FormatUint(batchMsg.Msg.PoolId, 10)),
Expand All @@ -111,33 +127,40 @@ func (k msgServer) WithdrawWithinBatch(goCtx context.Context, msg *types.MsgWith
sdk.NewAttribute(types.AttributeValuePoolCoinDenom, batchMsg.Msg.PoolCoin.Denom),
sdk.NewAttribute(types.AttributeValuePoolCoinAmount, batchMsg.Msg.PoolCoin.Amount.String()),
),
)
})

return &types.MsgWithdrawWithinBatchResponse{}, nil
}

// Message server, handler for MsgSwapWithinBatch
func (k msgServer) Swap(goCtx context.Context, msg *types.MsgSwapWithinBatch) (*types.MsgSwapWithinBatchResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if k.GetCircuitBreakerEnabled(ctx) {
return nil, types.ErrCircuitBreakerEnabled
}

params := k.GetParams(ctx)
if msg.OfferCoinFee.IsZero() {
msg.OfferCoinFee = types.GetOfferCoinFee(msg.OfferCoin, params.SwapFeeRate)
}

// TODO: remove redundant GetPoolBatch
poolBatch, found := k.GetPoolBatch(ctx, msg.PoolId)
if !found {
return nil, types.ErrPoolBatchNotExists
}

batchMsg, err := k.Keeper.SwapLiquidityPoolToBatch(ctx, msg, types.CancelOrderLifeSpan)
if err != nil {
return &types.MsgSwapWithinBatchResponse{}, err
}
ctx.EventManager().EmitEvent(

ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
),
)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeSwapWithinBatch,
sdk.NewAttribute(types.AttributeValuePoolId, strconv.FormatUint(batchMsg.Msg.PoolId, 10)),
Expand All @@ -150,6 +173,7 @@ func (k msgServer) Swap(goCtx context.Context, msg *types.MsgSwapWithinBatch) (*
sdk.NewAttribute(types.AttributeValueDemandCoinDenom, batchMsg.Msg.DemandCoinDenom),
sdk.NewAttribute(types.AttributeValueOrderPrice, batchMsg.Msg.OrderPrice.String()),
),
)
})

return &types.MsgSwapWithinBatchResponse{}, nil
}
22 changes: 13 additions & 9 deletions x/liquidity/spec/08_params.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ The liquidity module contains the following parameters:

Key | Type | Example
---------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------
PoolTypes | []PoolType | [{"id":1,"name":"ConstantProductLiquidityPool","min_reserve_coin_num":2,"max_reserve_coin_num":2,"description":""}]
MinInitDepositAmount | string (sdk.Int) | "1000000"
InitPoolCoinMintAmount | string (sdk.Int) | "1000000"
MaxReserveCoinAmount | string (sdk.Int) | "1000000000000"
PoolCreationFee | sdk.Coins | [{"denom":"stake","amount":"100000000"}]
SwapFeeRate | string (sdk.Dec) | "0.003000000000000000"
WithdrawFeeRate | string (sdk.Dec) | "0.003000000000000000"
MaxOrderAmountRatio | string (sdk.Dec) | "0.100000000000000000"
UnitBatchHeight | uint32 | 1
PoolTypes | []PoolType | [{"id":1,"name":"ConstantProductLiquidityPool","min_reserve_coin_num":2,"max_reserve_coin_num":2,"description":""}]
MinInitDepositAmount | string (sdk.Int) | "1000000"
InitPoolCoinMintAmount | string (sdk.Int) | "1000000"
MaxReserveCoinAmount | string (sdk.Int) | "1000000000000"
PoolCreationFee | sdk.Coins | [{"denom":"stake","amount":"100000000"}]
SwapFeeRate | string (sdk.Dec) | "0.003000000000000000"
WithdrawFeeRate | string (sdk.Dec) | "0.003000000000000000"
MaxOrderAmountRatio | string (sdk.Dec) | "0.100000000000000000"
UnitBatchHeight | uint32 | 1
CircuitBreakerEnabled | CircuitBreakerEnabled | false

## PoolTypes

Expand Down Expand Up @@ -71,6 +72,9 @@ Maximum ratio of reserve coins that can be ordered at a swap order.

The smallest unit batch size for every liquidity pool.

## CircuitBreakerEnabled

The intention of circuit breaker is to have a contingency plan for a running network which maintains network liveness. This parameter enables or disables all transaction message types in liquidity module.
# Constant Variables

Key | Type | Constant Value
Expand Down
1 change: 1 addition & 0 deletions x/liquidity/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ var (
ErrBadPoolTypeId = sdkerrors.Register(ModuleName, 37, "invalid index of the pool type")
ErrExceededReserveCoinLimit = sdkerrors.Register(ModuleName, 38, "can not exceed reserve coin limit amount")
ErrDepletedPool = sdkerrors.Register(ModuleName, 39, "the pool is depleted of reserve coin, reinitializing is required by deposit")
ErrCircuitBreakerEnabled = sdkerrors.Register(ModuleName, 40, "circuit breaker is triggered")
)
Loading

0 comments on commit d1c90aa

Please sign in to comment.