diff --git a/x/estaking/spec/01_concepts.md b/x/estaking/spec/01_concepts.md new file mode 100644 index 000000000..dcc7494f7 --- /dev/null +++ b/x/estaking/spec/01_concepts.md @@ -0,0 +1,7 @@ + + +# Concepts + +The `estaking` module in the Elys Network extends basic staking capabilities, providing additional functionalities such as managing and distributing rewards from multiple validators, updating staking parameters, and handling Eden and EdenB token mechanics in relation to staking rewards. This module aims to enhance the staking experience and efficiency within the network. diff --git a/x/estaking/spec/02_usage.md b/x/estaking/spec/02_usage.md new file mode 100644 index 000000000..b9bd1005a --- /dev/null +++ b/x/estaking/spec/02_usage.md @@ -0,0 +1,67 @@ + + +# Usage + +## Commands + +### Querying Commitments and Balances + +```bash +TREASURY=$(elysd keys show -a treasury --keyring-backend=test) + +elysd query commitment show-commitments $TREASURY +elysd query bank balances $TREASURY +``` + +### Querying Staking and Validators + +```bash +elysd query estaking params +elysd query staking validators +``` + +### Delegating Tokens + +```bash +VALIDATOR=elysvaloper12tzylat4udvjj56uuhu3vj2n4vgp7cf9pwcqcs +elysd tx staking delegate $VALIDATOR 1000000000000uelys --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +``` + +### Querying and Withdrawing Rewards + +```bash +elysd query distribution rewards $TREASURY $VALIDATOR +elysd query distribution rewards $TREASURY $EDEN_VAL +elysd query distribution rewards $TREASURY $EDENB_VAL + +elysd tx distribution withdraw-rewards $VALIDATOR --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +elysd tx distribution withdraw-rewards $EDEN_VAL --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +elysd tx distribution withdraw-rewards $EDENB_VAL --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +``` + +### Committing Claimed Rewards + +```bash +elysd tx commitment commit-claimed-rewards 503544 ueden --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +elysd tx commitment commit-claimed-rewards 1678547 uedenb --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +``` + +### Delegating + +```bash +elysd tx staking delegate $VALIDATOR 1000uelys --fees=10000uusdc --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000 +``` + +### Querying eStaking Rewards + +```bash +elysd query estaking rewards $TREASURY +``` + +### Withdrawing All Rewards + +```bash +elysd tx estaking withdraw-all-rewards --from=validator --chain-id=elystestnet-1 +``` diff --git a/x/estaking/spec/03_keeper.md b/x/estaking/spec/03_keeper.md new file mode 100644 index 000000000..105825c9d --- /dev/null +++ b/x/estaking/spec/03_keeper.md @@ -0,0 +1,115 @@ + + +# Keeper + +## Rewards Distribution + +The `estaking` module's keeper handles rewards distribution and updates staking parameters. It ensures that rewards are properly calculated, distributed, and that necessary adjustments to staking parameters are made regularly. + +### EndBlocker + +The `EndBlocker` function is invoked at the end of each block. It is responsible for processing the distribution of rewards and burning EdenB tokens if there has been a reduction in Elys staking. + +```go +func (k Keeper) EndBlocker(ctx sdk.Context) { + k.ProcessRewardsDistribution(ctx) + k.BurnEdenBIfElysStakingReduced(ctx) +} +``` + +### Taking Delegation Snapshot + +The `TakeDelegationSnapshot` function captures the current state of a delegator's staked amount. This snapshot includes calculating the total delegation amount and storing it as an `ElysStaked` object. + +```go +func (k Keeper) TakeDelegationSnapshot(ctx sdk.Context, addr string) { + delAmount := k.CalcDelegationAmount(ctx, addr) + elysStaked := types.ElysStaked{ + Address: addr, + Amount: delAmount, + } + k.SetElysStaked(ctx, elysStaked) +} +``` + +### Burning EdenB Tokens + +The `BurnEdenBIfElysStakingReduced` function burns EdenB tokens if the Elys staking has decreased. It checks for addresses where staking changes have occurred and performs necessary actions to burn tokens and update snapshots. + +```go +func (k Keeper) BurnEdenBIfElysStakingReduced(ctx sdk.Context) { + addrs := k.GetAllElysStakeChange(ctx) + for _, delAddr := range addrs { + k.BurnEdenBFromElysUnstaking(ctx, delAddr) + k.TakeDelegationSnapshot(ctx, delAddr.String()) + k.RemoveElysStakeChange(ctx, delAddr) + } +} +``` + +### Processing Rewards Distribution + +The `ProcessRewardsDistribution` function is responsible for distributing rewards to stakers. It updates incentive parameters and calculates the rewards to be distributed based on collected fees and staking conditions. + +```go +func (k Keeper) ProcessRewardsDistribution(ctx sdk.Context) { + k.ProcessUpdateIncentiveParams(ctx) + err := k.UpdateStakersRewards(ctx) + if err != nil { + ctx.Logger().Error("Failed to update staker rewards unclaimed", "error", err) + } +} +``` + +### Updating Stakers Rewards + +The `UpdateStakersRewards` function updates the rewards for stakers. It calculates the total rewards based on the collected fees and staking conditions, and then mints the appropriate amount of reward tokens. + +```go +func (k Keeper) UpdateStakersRewards(ctx sdk.Context) error { + baseCurrency, found := k.assetProfileKeeper.GetUsdcDenom(ctx) + if !found { + return errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency) + } + + feeCollectorAddr := authtypes.NewModuleAddress(authtypes.FeeCollectorName) + totalFeesCollected := k.commKeeper.GetAllBalances(ctx, feeCollectorAddr) + gasFeeCollectedDec := sdk.NewDecCoinsFromCoins(totalFeesCollected...) + dexRevenueStakersAmount := gasFeeCollectedDec.AmountOf(baseCurrency) + + params := k.GetParams(ctx) + stakeIncentive := params.StakeIncentives + totalBlocksPerYear := k.parameterKeeper.GetParams(ctx).TotalBlocksPerYear + + edenAmountPerYear := sdk.ZeroInt() + if stakeIncentive != nil && stakeIncentive.EdenAmountPerYear.IsPositive() { + edenAmountPerYear = stakeIncentive.EdenAmountPerYear + } + stakersEdenAmount := edenAmountPerYear.Quo(sdk.NewInt(totalBlocksPerYear)) + + totalElysEdenEdenBStake := k.TotalBondedTokens(ctx) + + stakersMaxEdenAmount := params.MaxEdenRewardAprStakers. + MulInt(totalElysEdenEdenBStake). + QuoInt64(totalBlocksPerYear) + + stakersEdenAmount = sdk.MinInt(stakersEdenAmount, stakersMaxEdenAmount.TruncateInt()) + + stakersEdenBAmount := sdk.NewDecFromInt(totalElysEdenEdenBStake). + Mul(params.EdenBoostApr). + QuoInt64(totalBlocksPerYear). + RoundInt() + + params.DexRewardsStakers.NumBlocks = sdk.OneInt() + params.DexRewardsStakers.Amount = dexRevenueStakersAmount + k.SetParams(ctx, params) + + coins := sdk.NewCoins( + sdk.NewCoin(ptypes.Eden, stakersEdenAmount), + sdk.NewCoin(ptypes.EdenB, stakersEdenBAmount), + ) + return k.commKeeper.MintCoins(ctx, authtypes.FeeCollectorName, coins.Sort()) +} +``` diff --git a/x/estaking/spec/04_protobuf_definitions.md b/x/estaking/spec/04_protobuf_definitions.md new file mode 100644 index 000000000..7e94123ed --- /dev/null +++ b/x/estaking/spec/04_protobuf_definitions.md @@ -0,0 +1,252 @@ + + +# Types, Messages, Queries, and States + +## Types + +### ElysStaked + +The `ElysStaked` message tracks the amount of Elys staked by a delegator. This is crucial for managing EdenBoost tokens, which need to be burned when an Elys unstake event occurs. + +```proto +message ElysStaked { + string address = 1; + string amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} +``` + +### IncentiveInfo + +The `IncentiveInfo` message contains details about the staking incentives, including the reward amount per year, the starting block of the distribution, and the total number of blocks per year. + +```proto +message IncentiveInfo { + string eden_amount_per_year = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string distribution_start_block = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string total_blocks_per_year = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string blocks_distributed = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} +``` + +### DexRewardsTracker + +The `DexRewardsTracker` message tracks rewards for stakers and liquidity providers. The amounts are denominated in USDC. + +```proto +message DexRewardsTracker { + string num_blocks = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + string amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} +``` + +### Params + +The `Params` message defines the parameters for the `estaking` module, including staking incentives, maximum reward APR for stakers, EdenBoost APR, and tracking dex rewards. + +```proto +message Params { + option (gogoproto.goproto_stringer) = false; + + IncentiveInfo stake_incentives = 1; + string eden_commit_val = 2; + string edenb_commit_val = 3; + string max_eden_reward_apr_stakers = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string eden_boost_apr = 6 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + DexRewardsTracker dex_rewards_stakers = 7 [ + (gogoproto.nullable) = false + ]; +} +``` + +### GenesisState + +The `GenesisState` message defines the initial state of the `estaking` module at genesis. + +```proto +message GenesisState { + Params params = 1 [(gogoproto.nullable) = false]; + repeated ElysStaked staking_snapshots = 2 [ (gogoproto.nullable) = false ]; +} +``` + +## Messages + +### Msg Service + +The `Msg` service defines the transactions available in the `estaking` module. + +```proto +service Msg { + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); + rpc WithdrawReward(MsgWithdrawReward) returns (MsgWithdrawRewardResponse); + rpc WithdrawElysStakingRewards(MsgWithdrawElysStakingRewards) returns (MsgWithdrawElysStakingRewardsResponse); + rpc WithdrawAllRewards(MsgWithdrawAllRewards) returns (MsgWithdrawAllRewardsResponse); +} +``` + +#### MsgUpdateParams + +This message updates the parameters of the `estaking` module. + +```proto +message MsgUpdateParams { + string authority = 1; + Params params = 2 [ (gogoproto.nullable) = false ]; +} + +message MsgUpdateParamsResponse {} +``` + +#### MsgWithdrawReward + +This message allows a delegator to withdraw rewards from a single validator. + +```proto +message MsgWithdrawReward { + string delegator_address = 1; + string validator_address = 2; +} + +message MsgWithdrawRewardResponse { + repeated cosmos.base.v1beta1.Coin amount = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} +``` + +#### MsgWithdrawElysStakingRewards + +This message allows a delegator to withdraw rewards from all validators. + +```proto +message MsgWithdrawElysStakingRewards { + string delegator_address = 1; +} + +message MsgWithdrawElysStakingRewardsResponse { + repeated cosmos.base.v1beta1.Coin amount = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} +``` + +#### MsgWithdrawAllRewards + +This message allows a delegator to withdraw rewards from all validators and Eden/EdenB commitments. + +```proto +message MsgWithdrawAllRewards { + string delegator_address = 1; +} + +message MsgWithdrawAllRewardsResponse { + repeated cosmos.base.v1beta1.Coin amount = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} +``` + +## Queries + +### Query Service + +The `Query` service defines the gRPC querier service for the `estaking` module. + +```proto +service Query { + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/elys-network/elys/estaking/params"; + } + rpc Rewards(QueryRewardsRequest) returns (QueryRewardsResponse) { + option (google.api.http).get = "/elys-network/elys/estaking/rewards/{address}"; + } +} +``` + +#### QueryParamsRequest + +This message requests the parameters of the `estaking` module. + +```proto +message QueryParamsRequest {} +``` + +#### QueryParamsResponse + +This message responds with the parameters of the `estaking` module. + +```proto +message QueryParamsResponse { + Params params = 1 [(gogoproto.nullable) = false]; +} +``` + +#### QueryRewardsRequest + +This message requests the total rewards accrued by a delegation. + +```proto +message QueryRewardsRequest { + string address = 1; +} +``` + +#### QueryRewardsResponse + +This message responds with the rewards accrued by a delegator. + +```proto +message QueryRewardsResponse { + repeated DelegationDelegatorReward rewards = 1 [(gogoproto.nullable) = false]; + repeated cosmos.base.v1beta1.Coin total = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} +``` + +#### DelegationDelegatorReward + +This message defines the rewards for a delegator from a specific validator. + +```proto +message DelegationDelegatorReward { + string validator_address = 1; + repeated cosmos.base.v1beta1.Coin reward = 2 [ + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.nullable) = false + ]; +} +``` diff --git a/x/estaking/spec/05_functions.md b/x/estaking/spec/05_functions.md new file mode 100644 index 000000000..b9aba982a --- /dev/null +++ b/x/estaking/spec/05_functions.md @@ -0,0 +1,111 @@ + + +# Functions + +## EndBlocker + +The `EndBlocker` function is called at the end of each block to perform necessary updates and maintenance for the `estaking` module. It processes rewards distribution and handles the burning of EdenB tokens if the Elys staking has reduced. + +```go +func (k Keeper) EndBlocker(ctx sdk.Context) { + k.ProcessRewardsDistribution(ctx) + k.BurnEdenBIfElysStakingReduced(ctx) +} +``` + +### ProcessRewardsDistribution + +The `ProcessRewardsDistribution` function is responsible for distributing rewards to stakers. It updates the incentive parameters and staker rewards based on the collected fees and staking conditions. + +```go +func (k Keeper) ProcessRewardsDistribution(ctx sdk.Context) { + k.ProcessUpdateIncentiveParams(ctx) + err := k.UpdateStakersRewards(ctx) + if err != nil { + ctx.Logger().Error("Failed to update staker rewards unclaimed", "error", err) + } +} +``` + +### BurnEdenBIfElysStakingReduced + +The `BurnEdenBIfElysStakingReduced` function burns EdenB tokens if the Elys staking has reduced. It checks for addresses where staking has changed and takes appropriate action to burn tokens and update staking snapshots. + +```go +func (k Keeper) BurnEdenBIfElysStakingReduced(ctx sdk.Context) { + addrs := k.GetAllElysStakeChange(ctx) + for _, delAddr := range addrs { + k.BurnEdenBFromElysUnstaking(ctx, delAddr) + k.TakeDelegationSnapshot(ctx, delAddr.String()) + k.RemoveElysStakeChange(ctx, delAddr) + } +} +``` + +## TakeDelegationSnapshot + +The `TakeDelegationSnapshot` function captures the current state of a delegator's staked amount. It calculates the delegation amount and records it. + +```go +func (k Keeper) TakeDelegationSnapshot(ctx sdk.Context, addr string) { + delAmount := k.CalcDelegationAmount(ctx, addr) + elysStaked := types.ElysStaked{ + Address: addr, + Amount: delAmount, + } + k.SetElysStaked(ctx, elysStaked) +} +``` + +## UpdateStakersRewards + +The `UpdateStakersRewards` function updates the rewards for stakers. It calculates the total rewards based on the collected fees and staking conditions, and then mints the appropriate amount of reward tokens. + +```go +func (k Keeper) UpdateStakersRewards(ctx sdk.Context) error { + baseCurrency, found := k.assetProfileKeeper.GetUsdcDenom(ctx) + if !found { + return errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency) + } + + feeCollectorAddr := authtypes.NewModuleAddress(authtypes.FeeCollectorName) + totalFeesCollected := k.commKeeper.GetAllBalances(ctx, feeCollectorAddr) + gasFeeCollectedDec := sdk.NewDecCoinsFromCoins(totalFeesCollected...) + dexRevenueStakersAmount := gasFeeCollectedDec.AmountOf(baseCurrency) + + params := k.GetParams(ctx) + stakeIncentive := params.StakeIncentives + totalBlocksPerYear := k.parameterKeeper.GetParams(ctx).TotalBlocksPerYear + + edenAmountPerYear := sdk.ZeroInt() + if stakeIncentive != nil && stakeIncentive.EdenAmountPerYear.IsPositive() { + edenAmountPerYear = stakeIncentive.EdenAmountPerYear + } + stakersEdenAmount := edenAmountPerYear.Quo(sdk.NewInt(totalBlocksPerYear)) + + totalElysEdenEdenBStake := k.TotalBondedTokens(ctx) + + stakersMaxEdenAmount := params.MaxEdenRewardAprStakers. + MulInt(totalElysEdenEdenBStake). + QuoInt64(totalBlocksPerYear) + + stakersEdenAmount = sdk.MinInt(stakersEdenAmount, stakersMaxEdenAmount.TruncateInt()) + + stakersEdenBAmount := sdk.NewDecFromInt(totalElysEdenEdenBStake). + Mul(params.EdenBoostApr). + QuoInt64(totalBlocksPerYear). + RoundInt() + + params.DexRewardsStakers.NumBlocks = sdk.OneInt() + params.DexRewardsStakers.Amount = dexRevenueStakersAmount + k.SetParams(ctx, params) + + coins := sdk.NewCoins( + sdk.NewCoin(ptypes.Eden, stakersEdenAmount), + sdk.NewCoin(ptypes.EdenB, stakersEdenBAmount), + ) + return k.commKeeper.MintCoins(ctx, authtypes.FeeCollectorName, coins.Sort()) +} +``` diff --git a/x/estaking/spec/README.md b/x/estaking/spec/README.md new file mode 100644 index 000000000..2082fc598 --- /dev/null +++ b/x/estaking/spec/README.md @@ -0,0 +1,29 @@ +# Elys eStaking 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 `estaking` module in the Elys Network is designed to extend basic staking functionalities by providing advanced management of staking rewards, updating staking parameters, and handling Eden and EdenB token mechanics. This module enhances the staking experience and efficiency within the network, ensuring seamless reward distribution and effective staking management. + +## Key Features + +- **Advanced Reward Management**: Manage and distribute rewards from multiple validators efficiently. +- **Staking Parameter Updates**: Update and manage staking parameters dynamically. +- **Token Mechanics**: Handle Eden and EdenB tokens in relation to staking rewards effectively. + +For more detailed information, please refer to the individual sections listed in the contents above.