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: Liquid Staking module #114

Open
wants to merge 42 commits into
base: main
Choose a base branch
from

Conversation

zsystm
Copy link
Collaborator

@zsystm zsystm commented Aug 23, 2023

Description

secure liquidstaking module on Canto

Brief summary (please read above links for details)

  • Liquid staking makes staked assets that are locked as tradable
  • Insurance removes the slashing risk that stakers take on when staking. Insurance makes secure liquidstaking mechanism possible.
  • This module is epoch based module. It means that most of the core logic (trigger liquid unstake, trigger withdraw insurance, etc…) is done in epoch (not a realtime). An epoch is a predefined period of time which is longer than unbonding period.

This PR is ready to be reviewed and audited but some additional commits can be pushed (not a big change)

cc. @dongsam @poorphd


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • targeted the correct branch (see PR Targeting)
  • provided a link to the relevant issue or specification
  • included the necessary unit and integration tests
  • reviewed "Files changed" and left comments if necessary

Reviewers Checklist

All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.

I have...

  • confirmed all author checklist items have been addressed
  • reviewed state machine logic
  • reviewed API design and naming
  • reviewed documentation is accurate
  • reviewed tests and test coverage
  • manually tested (if applicable)

zsystm and others added 30 commits May 2, 2023 12:49
implement skeleton for liquidstaking module
* most state transitions of core states (chunk, insurance, etc…) must occur in EndBlocker at Epoch.
  * withdraw insurance and liquid unstake are queued until epoch.
* implement hooks for tracking changes of delegation object
    * this is for tracking delegation rewards so we can distriibute it to reward module and insurance fee pool
* fix bugs
* implement genesis logic
* implement epoch logic
* add helpful tracking fields for NetAmountState
* delete hooks
  * to simplify core logics related with delegation, we use custom tracking obj (e.g. UnpairingForUnstakingChunkInfo)
  * instead we should make sure for our epoch of liquidtstaking module must be same with unbonding period of staking module
    * when liquid unstaking is started at previous epoch and current epoch is reached, we can unstake it right away if unbonding period is just same with epoch period. it is good for capital efficiency and we can manage states as clean and easy.
* apply atomic sending coins using InputOutputCoins of bank keeper.
* remove un-used keys and methods
* remove PendingLiquidUnstake and use UnpairingForUnstakingChunkInfo and chunk status to indicate the status of chunk
* update comment
* fix grammar
also includes
* refactor logics
* add state queries
* refactor test codes
* add helpful prints to debug easily (while development period)
* re-size chunk from 500M to 250K
* apply dynamic fee rate
also includes
* update swagger
* add some queries
* implement claim discounted reward msg
* security patch
* added tc for dynamic fee
HandleUnprocessedQueuedLiquidUnstakes step is added to abci.
during testing we found that, without this logic some unstaking request remains forever.

also includes
* refactor slashing ante handler
* add util function to easily create tx
* update swagger
* update init test script
* update cli and setup integration testing
* add testing for genesis and grpc query
* conservative invariants
* add invariants tcs
* add tcs for edge cases
* add events
* diet net amount state
* setup simulation basic (wip)
* add and update spec
* add test files and tcs to raise code coverage
same depth but different path can't be served
penalty from src validator happened during re-delegation period must be covered by unpairing insurance.
…and etc

fix core logics
* if duty-free insurance is un-paired and it has enough collateral and have valid validator, then it becomes pairing insurance. (it is in spec, but missed)
* when finishing unstaking, unpairing insurance's balance can be smaller than penalty. in that situation, unpairing ins must send it's all balance instead of penalty amt itself(cannot pay it because lack of balance).
* when handling paired chunk which is in re-pairing situation, must consider tombstone case(evidence height < epoch(re-pairing) height)

update core logics
* when finishing unstaking, unstaker will get whole chunk amount tokens if unpairing insurance can cover the penalty.
* if penalty occurred during re-delegation period is bigger than src insurance, then dst insurance should avoid covering that penalty instead because it is not dst's duty.
* add edge cases handling logics.

error & panic
* clarify panic and error situations with additional sanity checks.

added
* Staking param also checked in param change ante handler.
* integration testing
* cli docs.

refactor
* unify name convention
* short variable name for readability
* no return err during iteration (most iterations must success. that means if there are error during iteration, it should be panic. so no need to return err).

simulation
* implement sim operations
* add maximum cap for insurance fee rate

insurance fee rate + validator's fee rate must be smaller than 50%.

* chore: fix typo
* fix: seperate antehandler for simulation

* fix: simulation errors

(cherry picked from commit 6961967)

* fix: simulation workflow

* fix: update sims.yml, add GOPATH on makefile

---------

Co-authored-by: dongsam <[email protected]>
* wip: simplify penalty logic

when insurance is under 5.75% of chunk size, then un-delegate it.
now we can make sure that during unbonding or re-delegation period, there must be no case where un-pairing insurance cannot cover penalty.

remainings:
* fix broken tests
* remove re-delegation info's deletable and amt field
* add tcs for new logics.

* handle decimal penalty and fix broken tests

when calc penaltyAmt, must check shares value based on amt.

* update re-delegation info

Those fields are introduced to cover the situation where unpairing ins at previous epoch cannot cover the penalty during re-delegation period.
But now we check paired ins with IsEnoughToCoverSlash so we don't need PenaltyAmt and Deletable anymore. Because that scenario will not happen.

* apply feedbacks

* panic when validator is invalid (this must not happen)
* remove empty branch
* fix wrong calc in IsEnoughToCoverSlash

feedbacks:
* #9 (comment)
* #9 (comment)

* add unit test for CalcCeiledPenalty

* remove un-reached checking logic and un-necessary comments

by adding IsEnoughToCoverSlash function, the edge cases we were previously worried about are gone.
(unpairing ins at previous epoch cannot cover the penalty during re-delegation period).

so comments and checking logics for that edge should be deleted as now.

* update spec related with this pr

updated spec related with this pr

remove tc created during test locally

change function name of mustDelegate
mustDelegatePenaltyAmt is more readable and meaningful

* Update lintk in x/liquidstaking/spec/02_state.md

Co-authored-by: dongsam <[email protected]>

* Update link in x/liquidstaking/spec/02_state.md

Co-authored-by: dongsam <[email protected]>

---------

Co-authored-by: dongsam <[email protected]>
* add api docs and fix duplicate restful path

* complete api and cli docs

* chore: fix link

* apply feedback

* unify format
* reduce huge response

* unify surfic
* sync state.md with latest implementation

* update state transition diagrams

* sync state transitions, messages and fix some logics

DoCancelProvideInsurance
when cancel provide insurance, we should return all of its spendable coins from both derived address and fee pool address.

DoWithdrawInsurance
we can accept request only paired or unpaired insurances, not unpairing insurance.
it because unpairing insurance is already in state transition situation at epoch, so its weird to queue the request for that insurance.

* sync latest spec with latest code and fix some core logics

fix core logics
before:
* there can be bug because chunk's status is changed to unpairing but, current paired chunk's status is still Paired and chunk have paired insurance id even if it is unpairing chunk.
after:
if paired insurance of paired chunk have invalid insurance, then unpairing it and add it to out insurances to hande just like other unpairing chunks.

add missing invariant checks
* newly added RedelegationInfosInvariant was not included

chore
* refactor variables name in invariants.go
* use lsm's own event key types, not other module's.
* add module name to each event

* add detailed comments for net amount and param change ante handlers

also updated swagger docs and statik

* add missed swagger yaml

* add description for 5.75%

* remove ConservativeNetAmount and update calc method of TotalLiquidTokens

* if there were slashing (token value of chunk's del < chunk size), then we consider insurance balance now.

* remove NativeTokenToLiquidStakeToken, just use MintRate. it is more easy to understand.

* update remaining reward calc method

* chore: update name of tc

* update spec

* optimize operations

* optimize redundant calls

* add todo

* update and fix spec

* add description about utilization ratio

* definition of NetAmount is changed. we consider insurance coverage now.
  * add missing item to a formula of NetAmount: sum of all unbonding balance of chunks (insurance coverage included)

* MaximumDiscountRate is defined as governance parameter

* chore: fix grammar

* update formula for calculating discount rate

* add missing diagrams and security cap description

* wip: update and refactor net amount state part

TODO: fix broken tests

* fix broken tcs and refactor some functions

* remove netAmountBeforeModuleFee from net amount state

* remove un-used function

* add NetAmountBeforeModuleFee at spec

* chore: fix variable name

* refactor: calc functions / add: dynamic fee tcs

those functions are moved to type level functions

* add custom stringer for Params

* add tc for CalcUtilizationRatio / chore: fix number expression

* remove redundant func and logic
* sync state.md with latest implementation

* update state transition diagrams

* sync state transitions, messages and fix some logics

DoCancelProvideInsurance
when cancel provide insurance, we should return all of its spendable coins from both derived address and fee pool address.

DoWithdrawInsurance
we can accept request only paired or unpaired insurances, not unpairing insurance.
it because unpairing insurance is already in state transition situation at epoch, so its weird to queue the request for that insurance.

* sync latest spec with latest code and fix some core logics

fix core logics
before:
* there can be bug because chunk's status is changed to unpairing but, current paired chunk's status is still Paired and chunk have paired insurance id even if it is unpairing chunk.
after:
if paired insurance of paired chunk have invalid insurance, then unpairing it and add it to out insurances to hande just like other unpairing chunks.

add missing invariant checks
* newly added RedelegationInfosInvariant was not included

chore
* refactor variables name in invariants.go
* use lsm's own event key types, not other module's.
* add module name to each event

* wip: refactor simulation codes

todo: need to run it in local

* fix and update app.go

* wip: implementing proposal simulations

TODO: Solve cycle problem

* solve import cycle and make it work

TODO: deal with staking module's initial stake part

* fix simulations

* update reduction

* resolve errors after merge

* fix epoch and wrong deduction reduce advance epoch weight

* update invariant and refactor some codes

del shares can be less than 250K (e.g. slashing during re-delegation period, not yet reached epoch)

* remove un-used check and add more context to panic message

now chunk invariants does not check its delegation shares.
it can be changed by slashing during re-delegation.

* fix: re-delegation matured logic

decision must be based on current block time.
if current block time >= info.CompletionTime, then it is matured.
if not, it is un-matured.

* wip: fix some isseus and trouble shooting for seed 1623992154303935393

disabled mimicking begin block logic of distribution module at epoch

* fix: simulation cancel provide insurance

target insurance must be pairing

* fix negative penaltyAmt

* sim: change power reduction

* fix: not updated validator problem and power reduction

must use validator retrieved from latest state.

* exclude epochs key

EpochInfo.CurrentEpochStartHeight is set to the block height at the time of init genesis.
This state is changeable when export and import, so exclude it.

* fix: missing ReDelegationInfo at genesis import and export

* chore: fix wrong variable name

* fix: restOutIns can have paired chunk

when ins directs Unbonding validator, then this situation can happen.
In this situation, we just unpair and undelegate it.

* temp: disable lsm param change

* fix dynamic fee rate as default

* fix: out insurance's status must not be updated

this is related with recent patch.
outIns is already unpairing for unpairing_for_withdrawa, so we must not update it.

chore: removed un-used method.

* fix: out insurance status

out insurance can be PAIRED status (ranked out but no replacement).
if it is not unpairing or unpairing for withdrawal we must change its status to unpairing because unbonding will be started.

* fix: simop liquid unstake

when decide unstakable chunk, we should consider whether it is already queued or not.

* optimize gas consumption (add InsuranceState) and update sim

optimize gas consumption
* added new internal state InsuranceState
* when calc NetAmount we don't need insurance states. so separate that state which will be used when query.

update sim
* apply default genesis
* don't allow param change
* enable inflation module

temp: apply sdk.DefaultPowerReduction when calc epoch provision. this will be changed at next PR

* add missing files

* use b-harvest cosmos-sdk patched for canto-lsm simulation testing

* clarify tc

more understandable and readable tc

* add missing proto file (separate insurance related field as InsuranceState)

* remove un-used weights

* refactor: just assign base keeper to BankKeeper right away

* add missing proto file

* remove epoch infos keys

epoch module's state can be changed when initGenesis.

* roll-back change and add NetAmountStateEssentials

all core logics using NetAmount before now use NetAmountEssentials.
NetAmount is used just for querying.

* add missing files

* apply ethermint reduction

for this, applied custom ibc-go and cosmos-sdk to updated initialStake as sdk.Int

* update spec related to NetAmountStateEssentials

* fix syntax error(should use sdk.Int)
# Conflicts:
#	.github/workflows/build.yml
#	.github/workflows/sims.yml
#	.github/workflows/test.yml
#	Makefile
#	app/ante/handler_options.go
#	app/ante/vesting.go
#	app/app.go
#	app/sim_test.go
#	app/state.go
#	app/test_helpers.go
#	client/docs/config.json
#	client/docs/statik/statik.go
#	client/docs/swagger-ui/swagger.yaml
#	cmd/cantod/genaccounts.go
#	ibc/testing/app.go
#	proto/canto/csr/v1/query.proto
#	proto/canto/fees/v1/fees.proto
#	proto/canto/fees/v1/genesis.proto
#	proto/canto/fees/v1/query.proto
#	proto/canto/fees/v1/tx.proto
#	proto/canto/onboarding/v1/query.proto
#	proto/canto/recovery/v1/genesis.proto
#	proto/canto/vesting/v1/query.proto
#	proto/canto/vesting/v1/tx.proto
#	proto/canto/vesting/v1/vesting.proto
#	x/fees/client/cli/query.go
#	x/fees/client/cli/tx.go
#	x/fees/genesis.go
#	x/fees/handler.go
#	x/fees/keeper/fees.go
#	x/fees/keeper/fees_test.go
#	x/fees/keeper/grpc_query.go
#	x/fees/keeper/grpc_query_test.go
#	x/fees/keeper/integration_test.go
#	x/fees/keeper/keeper.go
#	x/fees/keeper/keeper_test.go
#	x/fees/keeper/msg_server.go
#	x/fees/keeper/msg_server_test.go
#	x/fees/keeper/params.go
#	x/fees/keeper/params_test.go
#	x/fees/module.go
#	x/onboarding/client/cli/query.go
#	x/onboarding/genesis.go
#	x/onboarding/genesis_test.go
#	x/onboarding/ibc_middleware.go
#	x/onboarding/keeper/grpc_query.go
#	x/onboarding/keeper/grpc_query_test.go
#	x/onboarding/keeper/keeper.go
#	x/onboarding/keeper/keeper_test.go
#	x/onboarding/keeper/params.go
#	x/onboarding/keeper/utils_test.go
#	x/onboarding/module.go
#	x/recovery/keeper/ibc_callbacks.go
#	x/recovery/keeper/ibc_callbacks_integration_suite_test.go
#	x/recovery/keeper/ibc_callbacks_integration_test.go
#	x/recovery/keeper/ibc_callbacks_test.go
#	x/vesting/client/cli/query.go
#	x/vesting/client/cli/tx.go
#	x/vesting/handler.go
#	x/vesting/keeper/grpc_query.go
#	x/vesting/keeper/grpc_query_test.go
#	x/vesting/keeper/integration_test.go
#	x/vesting/keeper/keeper.go
#	x/vesting/keeper/keeper_test.go
#	x/vesting/keeper/msg_server.go
#	x/vesting/keeper/msg_server_test.go
#	x/vesting/module.go
#	x/vesting/types/clawback_vesting_account_test.go
zsystm and others added 10 commits August 11, 2023 13:02
lstoken can be used as maxToken which is not registered as params. with no-op, we can proceed sim tests.
* disable minus fee rate

* chore: remove un-used errors and bump cosmos-sdk

* fix duplicate error code

* update simop

if calculated fee rate is negative, then apply zeroFeeRate
* tmp: add logger to analyze gas consumption

logs gas consumption at fileLogger

* disable minus fee rate

* chore: remove un-used errors and bump cosmos-sdk

* fix duplicate error code

* update simop

if calculated fee rate is negative, then apply zeroFeeRate

* limit minting if it exceeds mainnet total supply

* update: initialize at least one validator in AppStateRandomizedFn

* apply Canto mainnet supply

based on mainnet supply, rich account get fund when initialize genesis on simulation testing.

each operation which needs token should get fund from rich account.

* update CalcNetAmountStateEssentials: return multiple data structures

this is for optimizing gas consumption when QueueLiquidUnstake

instead of Iterate chunks separately, iterate it once when calc net amount state essentials.

* refund gas used by cachedCtx

* remove logger and print lines for debugging

* remove un-necessary lines

* chore: change variable name

* reduce computation

GetValidator().String is a redundant computation. We can just use ValidatorAddress field.
* add v8 upgrade handler and link issue related with go.mod

* Fix comment

Co-authored-by: dongsam <[email protected]>

---------

Co-authored-by: dongsam <[email protected]>
* fix un-used variables and unify code usages

DoLiquidStake must returns total new shares and total ls token mint amount because user can liquid stake multiple chunks.

use sdk.Int instead of int64 when calc chunksToLiquidUnstake. sanity patch to avoid overflow.

* add sanity checks

* use SpendableCoins instead of GetBalance
* WIP: spec update

* WIP: spec update applying comments

* WIP: spec update
* set liquid bond denom at upgrade and msg_server

* add tc for liquidBondDenom at upgrade

* fix: set epoch duration same with staking unbonding time

---------

Co-authored-by: dongsam <[email protected]>
@zsystm zsystm requested a review from tkkwon1998 August 24, 2023 01:24
* add abci spec

* docs: updating abci spec

* fix typo

* update contents and figure

* remove begin block and block mds

* updated related contents
* re-order docs

* update diagram

more detail description for covering penalty

---------

Co-authored-by: poorphd <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants