owned this note
owned this note
Published
Linked with GitHub
# Liquid Stake - Solana Technical Specification
## Table of Contents
* [Introduction](Tenderize.me-Technical-Specification.md#introduction)
* [Users](Tenderize.me-Technical-Specification.md#users)
* [Mechanics](Tenderize.me-Technical-Specification.md#mechanics)
* [Smart Contract Design](Tenderize.me-Technical-Specification.md#smart-contract-design)
* [Data Types](Tenderize.me-Technical-Specification.md#data-types)
* [Actions](Tenderize.me-Technical-Specification.md#actions)
* [Parameters](Tenderize.me-Technical-Specification.md#parameters)
## Introduction
Tenderize.me is a liquid staking yield aggregator. The protocol provides LPs value accruing staking derivative token, which represents their staking positions and accumulated rewards.
Main focus of the protocol is to make staking more efficient and easy for regular non-technical defi users.
This document is a technical specification of the protocol. For a more general overview of the protocol see the [whitepaper](hygfyttyr) for the original proposal
This specification will describe protocol actions in terms of smart contract function calls that trigger state transitions within the system.
### Program / Smart contract
The entry point of blockchain interaction. Can be found by address PUBKEY in devnet, testnet and mainnet clusters. It is possible to deploy the program to the localnet for testing purposes, but you need to change source code to reflect address change
### The program state
Solana smart contracts are stateless, that's why state is stored in separate accounts. State set is determined by root state account. All other state accounts, reserve and stake accounts has PDA addresses dependent on the root state account address. Root state account is initialized by initialize command and must be provided to the all on-chain commands. Other state accounts are used for storing validator records and credit records in a fixed size accounts. Accounts address also depends on chunk index.
### Reserve
#### Goal
The reserve serves as a buffer to gather deposits and pay out withdrawals to avoid superfluous staking and unstaking.
#### Mechanism
Since staking in solana network happens on checkpoint bases (balances for staking are checked at the beggining of each epoch), there is no point in sending funds to validators sooner than before the end of the epoch.
Thus to pool funds for staking makes for much more efficient mechanism, where LPs requesting withdrawals can be paid out from the LPs providing deposit and only the Net deposits(deposits - withdrawals) are staked at the end of the epoch. Therefore there is less "warming up" and "cooling down" which makes the machinsm more efficient in terms of yield.
#### Implementation
Reserve is program that is controled by system accounts dependent on root state address. It is not possible to manipulate it (exept for sending SOLs directly to reserve) without running program commands.
### Staking
#### Goal
To stake funds on users behalf while maintaining decentralisation, minimizing trust and maximizing yield.
#### Mechanism
Staker(staking obejct in solana contract) receives funds from reserve at the end of each epoch. It distributes the funds between n validators proportionally and requests staking. It also checks accounts that had previously been warming up if they are active, and merges active accounts with Validator's main account. This is done for all the validaotors.
#### Implementation
The root state account points to chunked list of validator records in separate set of fixed size accounts with addresses dependent on root state account address and index in the list of chunks. Every validator record contains validator vote info, summary information for validator and points to stake account set. Stake account address is derived from root state account address, validator vote address and stake index. We need to keep multiple stake account for one validator because it is impossible to restake SOLs when stake account is in use. For solving this problem we made it possibile to create multiple accounts for warming up and cooling down. We also merge fully delegated accounts and destroy fully undelegated accounts after all funds are withdrawn.
### Derivative token
`tenderToken`.
Its main function is to liquify LPs' staking position and accrue staking rewards. Since `tenderToken` represents fractional ownership of funds of specific token(usually ERC20) under management it has an implicit share price.
The price of a `tenderToken` is calculated as the total amount of deposited `tokens` under management including rewards, divided by the total supply of equivalent `tenderTokens`.
$price_{tender}=\frac{outstanding_{token}}{supply_{tender}}$
To achieve the desired functionality, two main principles must be kept at all times.
* Deposits and Withdrawals do not affect $price_{tender}$ further refered to as `sharePrice` in the document.
* `sharePrice` must increment right at the moment staking rewards are assigned.
Additionally `tenderToken` serves as a tokenized representation of LP's staking reward. Hence can be freely traded and used in other protocols.
#### Implementation
In solana blockchain tokens are controlled by program "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA". Token type is determined by token mint account address. It must be set in initialization command (must have 0 supply and correct mint authorithy to be accepted by initialize command) and can not be changed during lifetime of program state. It is not possible to close root state account while token suply, reserve and stakes are not empty
## Smart Contract user roles
### Owner
The main manager of program state. Implemented as authority key. Can add and remove validators, delegate and undelegate (maybe after splitting) stakes, change fees and close instances. For normal functioning of the system must run delegate/undelegate at the end of each epoch. When reserve balance is too big relative to the total staked funds it is prohibited to run undelegate and when sum of credits is too big relative to the total staked it is prohibited to run delegation (the range is set in initialize command and can not be changed). If there is no owner activity for a long time period and reserve minus sum of credits are out of range at the same time, everyone will receive access to the delegate and undelegate functionality to maintain the system without owner involvement (link to admin interface will appear in the frontend). Implemented in the off-chain admin daemon running on some server.
### Updater
Everyone may run update process at the start of epoch for the price recalculation and prize gathering. It is recommended to merge all mergeable stakes before running update because merging will optimize future update transaction count (and lower fees). Implemented in the off-chain admin daemon but in case of troubles with server anyone may become a updater for maintaining the system (the first deposit/withdraw command in epoch will offer to run merge + update for using a more recent token price)
### Credit payer
Everyone may run payCredit command to transfer SOLs from reserve to creditors. This action is rewarded from credit fees. Reward intended to cover transaction fee but if you add payCredit instructions in existing transaction you will receive pure profit. Must be called after update on start of epoch and after deposits as soon as possible. Implemented in the off-chain admin daemon but may be run in every deposit transaction if configured in frontend settings
## User Interactions
## Commands
- `Initialize`: initializes previousle created for the program account to be a new instance of the program state with empty validator and credit lists. Sets owner authority, list chunk sizes, stake pool and credit fees, associated token mint and owner's fee account. Requires reserve PDA to be empty, mint to have 0 supply and to have corresponding PDA account as mint authority.
- `Deposit`: transfers SOLs from user account into reserve and mint tSOLs to the user's and owner's accounts according to the fee settings
- `Withdraw`: burns delegated tSOLs from user account and return corresponding amount of SOLs from reserve
- `Close`: inverse of initialize. Requires that there are no validators and credits, empty reserve and mint supply. Additionally destroys the state account, returns rent SOLs to the owner and changes mint authority. Needed owner's signature
- `AddValidator`: appends validator to the list. May create the new validator list chunk from the provided SOL source. Needed owner's signature
- `RemoveValidator`: removes validator from the list. May return rent from destroying validator chunk. Needed owner's signature
- `UpdateStakeBalance`: updates price of the tSOL in the beginning of next epoch. Need to be called repeatedly for all registered stakes of all validators in the list in right order. Additionally moves all prizes from stake accounts to reserve for the end epoch redelegation
- `DelegateReserve`: transfers SOLs from reserve to the stake account warming up from current epoch or creates the new one with calculated PDA address. Need to be run with meaningful arguments (possible multiple times) at the end of each epoch. Needed owner's signature
- `MergeStakes`: merges 2 fully delegated stakes of the same validator. Run it for the main stake (first fully acctivated) and any another fully activated stakes for optimizing UpdateStakeBalance transaction sizes before running UpdateStakeBalance chain
- `SplitStake`: splits stake for the future undelegation. Needed owner's signature
- `Unstake`: starts cooling down of stake. Needed owner's signature. Undelegated SOLs will be moved to reserve during the UpdateStakeBalance chains. Needed owner's signature
- `Credit`: delayed withdraw. Record withdraw request to the list for the future execution. Gather credit fee, part of which will be returned during record processing. User doesn't lose control over tSOL tokens and may cancel delegation in any time.
- `PayCredit`: runs delayed withdraw for the next credit record. Running this command will be rewarded from the credit fee according to the initialization settings. Run it after any user deposit and UpdateStakeBalance chain completion
## User Interactions
![image](https://github.com/Tenderize/Tenderize-on-solana/blob/main/docs/Tenderize.me-schemaF.png)
### Deposit
Alice deposits solana token into the system and gets back tenderSolana
1. Solana Token is transferred to the reserve account
2. Based on current shareprice of tenderSolana the appropriate amount of tenderSols are minted and transferred to Alice (part of minted tokens will be sent to the program owner as fee)
3. If configured in frontend settings, Alice will run `payCreditors` commands in the same transaction and may receive some reward for doing this
4. The original token is kept as a reserve till the end of epoch when `delageteReserve` will be called or the next `payCreditors` call (probably issued by Alice in the same transaction as deposit), which will pay out LPs who wanted to withdraw their stake in previously, but weren’t able to because of reserve state (owed[])
**Things to observe:**
No worming up + cooling down period.
Staking rewards are received for longer than if LPs just used staking pools, since there are no staking rewards during worming up + cooling down period.
### Withdrawal
Bob withdraws his SOL token by providing tSOL
* IF enough funds in reserve
* IF YES `Withdraw`
* 1. tSOLs are burned
* 2. SOLs is taken from reserve and send back to Bob
* IF NOT
* We will notify user that there are not enough funds in the reserve, in such a case user can decide to come later or to “Get in line to Unstake”, in such case command `credit` is called which does following:
* 1. All command parameters are remembered in the credit list
* 2. When reserve will be filled by another user deposits or in result of gathering staking prizes in `update` call at the start of the next epoch somebody runs `payCreditors` commands and process part of credit list records. If processed part of credit list includes Bob's record, Bob receives SOLs from reserve in result of processing his record.
* 3. Once funds are sent to Bob, an appropriate amount of tSOLs from the Bob's tSOL account delegated to the special address (remembered as part of `credit` parameters) is burnt.
**Things to observe:**
Bob receives his initial deposit + accrued staking rewards since tenderSolana token has accrued in value in the time between his deposit and withdrawal.
If he has to wait for his deposit, he is receiving staking rewards for all this time.
If it happens that there is a time where change in deposits in negative for couple of continues epoch, it may happen that LPs would have to wait for a long time to get their Solana tokens back. In such a case owner will unstake necessary amount to pay out LPs
### Swapping with Pool
We utilise Serum AMM liquidity pool to allow everyone to be able to exit his position without needing to interact with the program. It is useful when reserve has not enough funds to pay immediately.
## State Update
- `Initialize`: the creation of state
- `AddValidator`: managing validator list
- Epoch start:
1. Series of commands `MergeStakes` must be run for optimizing `Update` transaction size. These are public functions usuall performed by admin but could be run by anyone.
2. Admin or some one else must run series of `Update` commands for recalculating token price and gathering staking rewards.
3. Admin or some one else must run series of `PayCreditor` commands for utilizing staking rewards as SOL source for paying creditors.
- After few deposits:
1. Admin or some one else must run series of `PayCreditor` commands for for paying creditors from filled reserve
- Before epoch end:
1. Admin run series of `PayCreditor` commands if needed
2. If reserve balance is greater than 10% of total amount of SOLs in the system owner (or some one else if owner is not present for the long time period) must run series of `DelegateReserve` commands and make reserve balance about 10% of total
3. Else if total sum of credits is greater than 10% of total owner (or some one else if owner is not present for the long time period) must run series of `Unstake` commands (possible after splitting stakes by running `SplitStake`) for filling reserve in the next epochs
4. In `DelegateReserve` scenario depositing functionality is turning off till next epoch `Update` series of commands (few seconds) for preventing depositing after delegation using the old token price
- `Close`: destrucion of state