Achitecture
Last updated
Last updated
The Zearn architecture has 3 main parts:
stZETA contract
UnstZETA contract
NodeOperator contract (Coming soon)
The stZETA contract is an ERC20 token contract that carries out the following operations:
User interaction
Reward Distribution
Manage withdrawals
Manage reward fees
Delegate to validators
Mint and burn NFTs
The stZETA contract uses OpenZeppelin AccessControl to manage permissions.
A user can interact only with the Zearn contract to:
Submit ERC20 ZETA
Request withdrawals
Claim withdrawals
Call ERC20 functions
Users can easily acquire stZETA by calling the submit function within the stZETA contract and specifying the amount they wish to delegate. This action will automatically exchange their ZETA for stZETA.
The calculation of the total stZETA a user receives when delegating their ZETA tokens proceeds as follows:
sharePerUser = submittedZeta * totalShares / totalPooledZeta
The totalPooledZeta represents the sum of buffered tokens (those submitted by the user but yet to be delegated) and the total amount already delegated.
totalPooledZeta = totalBufferedZeta + totalDelegatedZeta
Initial states
UserA submit
submit —> 1000 ZETA
Gets —> 1000 stZETA
Update states
Users Shares
UserB submit
submit —> 500 ZETA
Gets —> 500 * 1000 / 1000 = 500 stZETA
Update states
Users Shares
The system was slashed —> -100 ZETA
Update states
Users Shares
UserC submit
submit —> 500 ZETA
Gets —> 500 * 1500 / 1400 = 535.71 stZETA
Update states
Users Shares
The system accumulates reward —> +200 ZETA
Update states
Users Shares
When the system experiences a reduction, the overall pooled ZETA decreases. However, it rebounds when a user contributes ZETA again or when the system receives rewards.
The stZETA contract is used to delegate tokens to validators.Our delegation process is driven by the real buffered ZETA within the stZETA contract. Once we hit the minDelegationAmount, we begin delegating to all staked operators. Each operator has a maxDelegateLimit, a value determined by the DAO. For instance, a trusted operator would have a higher limit, while a less trusted validator might have a lower one. This maxDelegateLimit allows us to distribute tokens effectively amongst operators.
The withdrawal employs the fresh validatorShare exit API. This gives us a nonce that can be utilized to associate each user request with this specific nonce. The ZETA contract keeps tabs on each validatorShare nonce, which will step up each time there's a new withdrawal request.
Request Withdrawal: In the event a user decides to withdraw their ZETA tokens from the system, a fresh ERC721 token is created and linked to this request. The owner has the flexibility to trade this token, sell it, or use it to reclaim their ZETA tokens.
The user requests a withdrawal.
Mint an NFT and map its ID with the request.
Store the request nonce of the validatorShare and validatorShare address.
Call the sellVoucher_new function
Claim tokens:
The user calls the claim token function and passes the tokenId.
Check if the msg.sender is the owner of this NFT.
Call the claim unstake tokens function on the validatorShare contract.
Transfer tokens to the user.
Burn the NFT.
ZETA tokens are gathered and housed within the stZETA contract in response to two specific events:
Whenever a user initiates a withdrawal request, the validatorShare contract dutifully transfers the respective rewards.
Scheduled job (explained below)
TOTAL_REWARDS = accumulated rewards on Zearn + accumulated rewards on all validators
We're set to allow operators a maximum stake of 10 ZETA tokens. Any accumulated rewards on all validator sides will be disregarded. Our oracle daemon will handle reward distribution. Regular checks will be conducted to ensure the amount is above a lower limit, which can be adjusted as needed. Once this is confirmed, stZETA works out the share for Node Operators and the treasury and immediately transfers tokens to them. The remaining ZETA tokens are then added to the buffer and re-delegated, which increases the totalPooledZETA value. The staking rewards are divided as follows: 5% to Zearn DAO treasury, 5% to Node Operators, and the bulk, 90%, is returned to the stZETA value via re-delegation.
Rewards for operators are allocated among all staked operators based on a specific ratio. A validator who has not been slashed in the previous period receives a ratio of 100%. However, if a validator has been slashed, their ratio drops to 80% of their total reward portion, with the remaining percentage distributed among the other validators.
When an operator is unstaked, the nodeOperator contract triggers the withdrawTotalDelegated function. This function claims all the delegated ZETA from the unstaked validator. Subsequently, an NFT token is minted and linked to this request. Later on, a scheduled task can call claimTokens2stZETA to withdraw the ZETA from the validatorShare.
When a user initiates a withdrawal, the validatorShare contract automatically handles the transfer of all accumulated rewards. The same process also applies when new vouchers are purchased. The primary concept here is to regard all tokens that have not been submitted (not buffered) through the submit function as rewards that are due for distribution.
We can manage the rewards fees using the stZETA contract.
The stZETA implements the validatorShare contract API
BuyVoucher_new
: buy shares from a validator link.
SellVouchernew
: sell an amount of shares link. Also, it has a good feature that allows us to track each sell request using a nonce.
unstakeClaimTokens_new
: claim the token by a nonce link.
getTotalStake
: get the total staked amount link.
getLiquidRewards
: get the accumulated rewards link
The UnstZETA contract, an ERC721 contract, is utilized by the stZETA contract to handle withdrawal requests.Each time a user triggers the requestWithdraw function within the stZETA contract, a new NFT is created and linked to the request.As an NFT owner, a user can:
Retrieve tokens from the withdrawal request
Transfer the NFT to another individual, who can then make a claim
Authorize another individual to make a claim
This ERC721 contract has slight modifications, allowing it to return a list of tokens owned by an address through the owner2Tokens public mapping. The same applies to obtaining the list of approved tokens through the address2Approved mapping.
Manage operatorsThe validator contract plays a key role in staking on the ZetaChain stake manager. For each operator, a new validatorProxy contract is generated when the addOperator function is called. This validatorProxy acts as the owner of the validator on the ZetaChain stakeManager. It enables the operator's owner to interact with the stakeManager API to:
Stake a validator using stakeFor.
Unstake a validator using unstake.
Top up Heimdall fees for a validator using topUpForFee.
Check the total staked by a validator using validatorStake.
Restake amount using restake.
Access validator share contract using getValidatorContract.
Update signer pubkey using updateSIgner.
Withdraw Heimdall fees using claimFee.
Change commission using updateCommisionRate.
Withdraw rewards using withdrawRewards.
Add an operator
An Operator expresses interest in participating in the UnstZETA protocol.
The DAO conducts a vote to consider the inclusion of the new operator. If the vote for inclusion is successful, the Node Operator becomes active:
A fresh validator contract is established.
The status is set to NotStaked.
A default max delegation value is assigned, indicating the system will delegate this amount (at most) whenever the delegate function is triggered.
Each operator owner is able to engage with their operator using the reward address to perform the following actions:
stake
join
unstake
topUpHeimdallFees
unjail
restake
update signer pub key
unstake claim
claim fee
The operator calls the stake function, including the amount of ZETA and heimdallFees.
The operator is switched to staked status and becomes ready to accept delegation.
Allows already staked validators to join the UnstZETA protocol.
They have first to approve the NFT token to the specific validatorProxy contract.
Call this function to join the system.
During the unstaking process of an operator, the NodeOperator contract interacts with the stZETA contract. Subsequently, the stZETA contract engages with the validatorShare contract to withdraw the total delegated ZETA tokens. After a specified withdrawal delay, the Zearn contract is eligible to claim these tokens. This final step of claiming tokens can be automated using a cron job.
The final step in the process is the removal of an operator. This occurs after the operator has unstaked and claimed their staked tokens. The DAO has the ability to invoke this function, which results in the operator's removal and deletion of the validatorProxy contract.
A validator can replenish the Heimdall fees that are consumed by the Heimdall and Bor nodes during the validation of new blocks.
Allows a validator to switch his status from locked to active.
Allows a validator to stake more ZETA, by default this feature is disabled.
Allows the operator to update the Signer Public key used by the heimdall.
Allows the operator owner to claim his staked ZETA tokens.
Allows the operator owner to claim his heimdall fees.
Update the operator's commission rate.
Define the minimum and maximum amounts, along with Heimdall fees, that an operator can apply towards staking or topping up Heimdall fees.
The Validator Factory is leveraged to roll out new ValidatorProxy instances, which are subsequently added to a validator array.
permission:
OPERATOR
description:
Creates a new ValidatorProxy and appends it to array of validators
permission:
OPERATOR
description:
Removes a ValidatorProxy determined by its address from the array of validator
totalShares | 0 |
---|---|
totalShares | 1000 |
---|---|
User | stakeRatio = userShares / totalShares | userZeta = stakeRatio * totalPooledZeta |
---|---|---|
totalShares | 1500 |
---|---|
User | stakeRatio = userShares / totalShares | userZeta = stakeRatio * totalPooledZeta |
---|---|---|
totalShares | 1500 |
---|---|
User | stakeRatio = userShares / totalShares | userZeta = stakeRatio * totalPooledZeta |
---|---|---|
totalShares | 2035.71 |
---|---|
User | stakeRatio = userShares / totalShares | userZeta = stakeRatio * totalPooledZeta |
---|---|---|
totalShares | 2035.71 |
---|---|
User | stakeRatio = userShares / totalShares | userZeta = stakeRatio * totalPooledZeta |
---|---|---|
totalPooledZeta
0
totalPooledZeta
1000
1
1 = 1000 / 1000
1000 = 1 * 1000
totalPooledZeta
1500
1
0.66 = 1000 / 1500
1000 = 0.66 * 1500
2
0.33 = 500 / 1500
500 = 0.33 * 1500
totalPooledZeta
1500 - 100 = 1400
1
0.66 = 1000 / 1500
933.33 = 0.66 * 1400
2
0.33 = 500 / 1500
466.66 = 0.33 * 1400
totalPooledZeta
1900
1
0.4912 = 1000 / 2035.71
933.33 = 0.4912 * 1900
2
0.2456 = 500 / 2035.71
466.66 = 0.2456 * 1900
3
0.2631 = 535.71 / 2035.71
500 = 0.2631 * 1900
totalPooledZeta
1900 + 200 = 2100
1
0.4912 = 1000 / 2035.71
1031.52 = 0.4912 * 2100
2
0.2456 = 500 / 2035.71
515.76 = 0.2456 * 2100
3
0.2631 = 535.71 / 2035.71
552.62 = 0.2631 * 2100