diff --git a/x/stablestake/spec/01_concepts.md b/x/stablestake/spec/01_concepts.md new file mode 100644 index 000000000..cf9866f87 --- /dev/null +++ b/x/stablestake/spec/01_concepts.md @@ -0,0 +1,7 @@ + + +# Concepts + +The `stablestake` module in the Elys Network extends the basic staking capabilities by providing functionalities for borrowing and lending, managing interest rates, and handling debt effectively. This module aims to ensure a stable and efficient staking environment within the network. diff --git a/x/stablestake/spec/02_usage.md b/x/stablestake/spec/02_usage.md new file mode 100644 index 000000000..7d9c8ee07 --- /dev/null +++ b/x/stablestake/spec/02_usage.md @@ -0,0 +1,31 @@ + + +# Usage + +## Commands + +### Querying Parameters + +```bash +elysd query stablestake params +``` + +### Querying Borrow Ratio + +```bash +elysd query stablestake borrow-ratio +``` + +### Bonding Tokens + +```bash +elysd tx stablestake bond 1000000000000uusdc --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +``` + +### Unbonding Tokens + +```bash +elysd tx stablestake unbond 500000000000uusdc --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +``` diff --git a/x/stablestake/spec/03_keeper.md b/x/stablestake/spec/03_keeper.md new file mode 100644 index 000000000..66c3d37c0 --- /dev/null +++ b/x/stablestake/spec/03_keeper.md @@ -0,0 +1,82 @@ + + +# Keeper + +## Interest Rate Management + +The `stablestake` module's keeper handles the computation and updating of interest rates, ensuring they are adjusted based on the network's parameters and conditions. + +### BeginBlocker + +The `BeginBlocker` function is invoked at the beginning of each block. It checks if an epoch has passed, updates interest rates, and recalculates the stacked interest for all debts. + +```go +func (k Keeper) BeginBlocker(ctx sdk.Context) { + // check if epoch has passed then execute + epochLength := k.GetEpochLength(ctx) + epochPosition := k.GetEpochPosition(ctx, epochLength) + + if epochPosition == 0 { // if epoch has passed + params := k.GetParams(ctx) + rate := k.InterestRateComputation(ctx) + params.InterestRate = rate + k.SetParams(ctx, params) + + debts := k.AllDebts(ctx) + for _, debt := range debts { + k.UpdateInterestStacked(ctx, debt) + } + } +} +``` + +### Borrowing and Repaying + +The `Borrow` function allows a user to borrow tokens, while the `Repay` function allows a user to repay borrowed tokens, including any accrued interest. + +```go +func (k Keeper) Borrow(ctx sdk.Context, addr sdk.AccAddress, amount sdk.Coin) error { + depositDenom := k.GetDepositDenom(ctx) + if depositDenom != amount.Denom { + return types.ErrInvalidBorrowDenom + } + debt := k.UpdateInterestStackedByAddress(ctx, addr) + debt.Borrowed = debt.Borrowed.Add(amount.Amount) + k.SetDebt(ctx, debt) + return k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, sdk.Coins{amount}) +} + +func (k Keeper) Repay(ctx sdk.Context, addr sdk.AccAddress, amount sdk.Coin) error { + depositDenom := k.GetDepositDenom(ctx) + if depositDenom != amount.Denom { + return types.ErrInvalidBorrowDenom + } + + err := k.bk.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, sdk.Coins{amount}) + if err != nil { + return err + } + + // calculate latest interest stacked + debt := k.UpdateInterestStackedByAddress(ctx, addr) + + // repay interest + interestPayAmount := debt.InterestStacked.Sub(debt.InterestPaid) + if interestPayAmount.GT(amount.Amount) { + interestPayAmount = amount.Amount + } + + // repay borrowed + repayAmount := amount.Amount.Sub(interestPayAmount) + debt.Borrowed = debt.Borrowed.Sub(repayAmount) + + if !debt.Borrowed.IsPositive() { + k.DeleteDebt(ctx, debt) + } else { + k.SetDebt(ctx, debt) + } + return nil +} +``` diff --git a/x/stablestake/spec/04_protobuf_definitions.md b/x/stablestake/spec/04_protobuf_definitions.md new file mode 100644 index 000000000..d53431ac5 --- /dev/null +++ b/x/stablestake/spec/04_protobuf_definitions.md @@ -0,0 +1,197 @@ + + +# Protobuf Definitions + +## Types + +### Debt + +The `Debt` message tracks the borrowed amount, interest paid, and interest stacked for a given address, along with timestamps for borrowing and last interest calculation. + +```proto +message Debt { + string address = 1; + string borrowed = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string interest_paid = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string interest_stacked = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + uint64 borrow_time = 5; + uint64 last_interest_calc_time = 6; +} +``` + +### Params + +The `Params` message defines the parameters for the `stablestake` module, including deposit denomination, redemption rate, epoch length, interest rates, and total value. + +```proto +message Params { + option (gogoproto.goproto_stringer) = false; + + string deposit_denom = 1; + string redemption_rate = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + int64 epoch_length = 3; + string interest_rate = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string interest_rate_max = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string interest_rate_min = 6 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string interest_rate_increase = 7 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string interest_rate_decrease = 8 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string health_gain_factor = 9 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string total_value = 10 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} +``` + +### GenesisState + +The `GenesisState` message defines the initial state of the `stablestake` module at genesis. + +```proto +message GenesisState { + Params params = 1 [(gogoproto.nullable) = false]; +} +``` + +## Messages + +### Msg Service + +The `Msg` service defines the transactions available in the `stablestake` module. + +```proto +service Msg { + rpc Bond(MsgBond) returns (MsgBondResponse); + rpc Unbond(MsgUnbond) returns (MsgUnbondResponse); +} +``` + +#### MsgBond + +This message allows a user to bond a specified amount of tokens. + +```proto +message MsgBond { + string creator = 1; + string amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +message MsgBondResponse {} +``` + +#### MsgUnbond + +This message allows a user to unbond a specified amount of tokens. + +```proto +message MsgUnbond { + string creator = 1; + string amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos + +/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +message MsgUnbondResponse {} +``` + +## Queries + +### Query Service + +The `Query` service defines the gRPC querier service for the `stablestake` module. + +```proto +service Query { + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/elys-network/elys/stablestake/params"; + } + rpc BorrowRatio(QueryBorrowRatioRequest) returns (QueryBorrowRatioResponse) { + option (google.api.http).get = "/elys-network/elys/stablestake/borrow-ratio"; + } +} +``` + +#### QueryParamsRequest + +This message requests the parameters of the `stablestake` module. + +```proto +message QueryParamsRequest {} +``` + +#### QueryParamsResponse + +This message responds with the parameters of the `stablestake` module. + +```proto +message QueryParamsResponse { + Params params = 1 [(gogoproto.nullable) = false]; +} +``` + +#### QueryBorrowRatioRequest + +This message requests the borrow ratio in the `stablestake` module. + +```proto +message QueryBorrowRatioRequest {} +``` + +#### QueryBorrowRatioResponse + +This message responds with the total deposits, total borrowings, and the borrow ratio in the `stablestake` module. + +```proto +message QueryBorrowRatioResponse { + string total_deposit = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string total_borrow = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string borrow_ratio = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} +``` diff --git a/x/stablestake/spec/05_functions.md b/x/stablestake/spec/05_functions.md new file mode 100644 index 000000000..0a79c1148 --- /dev/null +++ b/x/stablestake/spec/05_functions.md @@ -0,0 +1,157 @@ + + +# Functions + +## BeginBlocker + +The `BeginBlocker` function is called at the beginning of each block to perform necessary updates and maintenance for the `stablestake` module. It updates interest rates and recalculates interest for all debts if an epoch has passed. + +```go +func (k Keeper) BeginBlocker(ctx sdk.Context) { + // check if epoch has passed then execute + epochLength := k.GetEpochLength(ctx) + epochPosition := k.GetEpochPosition(ctx, epochLength) + + if epochPosition == 0 { // if epoch has passed + params := k.GetParams(ctx) + rate := k.InterestRateComputation(ctx) + params.InterestRate = rate + k.SetParams(ctx, params) + + debts := k.AllDebts(ctx) + for _, debt := range debts { + k.UpdateInterestStacked(ctx, debt) + } + } +} +``` + +### Borrow + +The `Borrow` function allows a user to borrow a specified amount of tokens, updating the debt and transferring the borrowed tokens to the user's account. + +```go +func (k Keeper) Borrow(ctx sdk.Context, addr sdk.AccAddress, amount sdk.Coin) error { + depositDenom := k.GetDepositDenom(ctx) + if depositDenom != amount.Denom { + return types.ErrInvalidBorrowDenom + } + debt := k.UpdateInterestStackedByAddress(ctx, addr) + debt.Borrowed = debt.Borrowed.Add(amount.Amount) + k.SetDebt(ctx, debt) + return k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, sdk.Coins{amount}) +} +``` + +### Repay + +The `Repay` function allows a user to repay a specified amount of borrowed tokens, updating the debt and handling the repayment of interest and principal amounts. + +```go +func (k Keeper) Repay(ctx sdk.Context, addr sdk.AccAddress, amount sdk.Coin) error { + depositDenom := k.GetDepositDenom(ctx) + if depositDenom != amount.Denom { + return types.ErrInvalidBorrowDenom + } + + err := k.bk.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, sdk.Coins{amount}) + if err != nil { + return err + } + + // calculate latest interest stacked + debt := k.UpdateInterestStackedByAddress(ctx, addr) + + // repay interest + interestPayAmount := debt.InterestStacked.Sub(debt.InterestPaid) + if interestPayAmount.GT(amount.Amount) { + interestPayAmount = amount.Amount + } + + // repay borrowed + repayAmount := amount.Amount.Sub(interestPayAmount) + debt.Borrowed = debt.Borrowed.Sub(repayAmount) + + if !debt.Borrowed.IsPositive() { + k.DeleteDebt(ctx, debt) + } else { + k.SetDebt(ctx, debt) + } + return nil +} +``` + +### UpdateInterestStacked + +The `UpdateInterestStacked` function updates the stacked interest for a given debt based on the current interest rate and the time elapsed since the last interest calculation. + +```go +func (k Keeper) UpdateInterestStacked(ctx sdk.Context, debt types.Debt) types.Debt { + params := k.GetParams(ctx) + newInterest := sdk.NewDecFromInt(debt.Borrowed). + Mul(params.InterestRate). + Mul(sdk.NewDec(ctx.BlockTime().Unix() - int64(debt.LastInterestCalcTime))). + Quo(sdk.NewDec(86400 * 365)). + RoundInt() + + debt.InterestStacked = debt.InterestStacked.Add(newInterest) + debt.LastInterestCalcTime = uint64(ctx.BlockTime().Unix()) + k.SetDebt(ctx, debt) + + params.TotalValue = params.TotalValue.Add(newInterest) + k.SetParams(ctx, params) + return debt +} +``` + +### InterestRateComputation + +The `InterestRateComputation` function computes the current interest rate based on the network's parameters, total value, and health gain factor. + +```go +func (k Keeper) InterestRateComputation(ctx sdk.Context) sdk.Dec { + params := k.GetParams(ctx) + if params.TotalValue.IsZero() { + return params.InterestRate + } + + interestRateMax := params.InterestRateMax + interestRateMin := params.InterestRateMin + interestRateIncrease := params.InterestRateIncrease + interestRateDecrease := params.InterestRateDecrease + healthGainFactor := params.HealthGainFactor + prevInterestRate := params.InterestRate + + moduleAddr := authtypes.NewModuleAddress(types.ModuleName) + depositDenom := k.GetDepositDenom(ctx) + balance := k.bk.GetBalance(ctx, moduleAddr, depositDenom) + borrowed := params.TotalValue.Sub(balance.Amount) + targetInterestRate := healthGainFactor. + Mul(sdk.NewDecFromInt(borrowed)). + Quo(sdk.NewDecFromInt(params.TotalValue)) + + interestRateChange := targetInterestRate.Sub(prevInterestRate) + interestRate := prevInterestRate + if interestRateChange.GTE(interestRateDecrease.Mul(sdk.NewDec(-1))) && interestRateChange.LTE(interestRateIncrease) { + interestRate = targetInterestRate + } else if interestRateChange.GT(interestRateIncrease) { + interestRate = prevInterestRate.Add(interestRateIncrease) + } else if interestRateChange.LT(interestRateDecrease.Mul(sdk.NewDec(-1))) { + interestRate = prevInterestRate.Sub(interestRateDecrease) + } + + newInterestRate := interestRate + + if interestRate.GT(interestRateMin) && interestRate.LT(interestRateMax) { + newInterestRate = interestRate + } else if interestRate.LTE(interestRateMin) { + newInterestRate = interestRateMin + } else if interestRate.GTE(interestRateMax) { + newInterestRate = interestRateMax + } + + return newInterestRate +} +``` diff --git a/x/stablestake/spec/README.md b/x/stablestake/spec/README.md new file mode 100644 index 000000000..359e13502 --- /dev/null +++ b/x/stablestake/spec/README.md @@ -0,0 +1,29 @@ +# Stablestake Module + +## Contents + +1. **[Concepts](01_concepts.md)** +2. **[Usage](02_usage.md)** +3. **[Keeper](03_keeper.md)** +4. **[Protobuf Definitions](04_protobuf_definitions.md)** +5. **[Functions](05_functions.md)** + +## References + +Resources: + +- [Elys Network Documentation](https://docs.elys.network) +- [Cosmos SDK Documentation](https://docs.cosmos.network) +- [GitHub Repository for Elys Network](https://github.com/elys-network/elys) + +## Overview + +The `stablestake` module in the Elys Network is designed to manage stable staking functionalities by providing efficient borrowing and lending mechanisms, managing interest rates, and ensuring the stability of the staking process. This module enhances the staking experience and efficiency within the network, ensuring seamless borrowing, interest calculation, and effective debt management. + +## Key Features + +- **Borrowing and Lending Management**: Efficiently manage borrowing and lending operations. +- **Interest Rate Updates**: Dynamically update and manage interest rates. +- **Debt Management**: Handle debts and interest calculations effectively. + +For more detailed information, please refer to the individual sections listed in the contents above.