Aptos Foundation releases core delegation pool functionality through the release of AIP-6 developed by the team at Bware Labs
Bware Labs Team
About Aptos
As a L1 blockchain aiming to be one of the safest and most scalable blockchains in the Web3 space. Aptos’ main principles revolve around scalability, safety, reliability and upgradability. Developed by former Meta engineers, Aptos relies on the capabilities of Move, a Rust-based programming language designed to provide higher security and flexibility for developers.
Motivation
The security and decentralization of L1 protocols is generally tied to the number of validators participating in the network. The more validators are able to join, the higher the blockchain’s degree of decentralization.
A delegation pool is an extension of the existing staking protocol on Aptos. Its main function is to enable multiple parties to contribute to the minimum stake required by delegating stake to a trusted validator. The validator can then enter the active set and start validating blocks once the delegation pool reaches the minimum stake required to run a validator.
This achieves two goals that further decentralize the network:
- Token holders can participate in helping secure the network by delegating a minimum of 11 APT to a validator without having to run a validator themselves.
- Node operators can spin up a delegation pool and have the public stake to it in order to reach the minimum stake required to join the validator set. This helps the community bootstrap validator nodes.
The following section of this article dives into the technical details of the delegation pool architecture, providing a detailed explanation of the implementation together with all the challenges and solutions that came up and were resolved on the way.
The Bware Labs team that developed this functionality had to bring their A game and work closely with the Aptos Dev team to be able to bring about the next phase of decentralization within the Aptos ecosystem while giving token holders the option to participate in the protocol via delegations.
Architecture
Within the core protocol, there is only one account, the stake owner, which can add stake to the validator. Essentially, the stake owner should be abstracted to an entity capable of collecting stake from delegators and adding it on their behalf to the native stake pool attached to the validator.
How it works
When initializing a delegation pool, a resource account is created along with an empty stake pool it owns from that point on.This account will manage the stake of the underlying pool on behalf of the delegators by forwarding their stake-management operations to it (add, unlock, reactivate, withdraw) while the resource account cannot be directly accessed nor externally owned.
A delegation pool ensures fairness for its delegators (no one has any real advantage over others), complete freedom over their own stake as well as robust security and separation of ownership over the total stake.
Achieving these properties requires an accounting logic to track down how much a delegator individually deposited and additionally earned from the validator’s rewards.
The core data structure used within the delegation pool by its accounting logic is represented by a shares-based pool. More exactly, a delegator buys shares into the delegation pool for its stake and becomes a shareholder. The pool accumulates additional stake from staking rewards and appreciates the value (price) of all existing shares.
A delegator redeems its shares later and receives its initial stake back plus any rewards earned from each successive price appreciation since joining the pool.Hence, internally, delegators own shares rather than absolute stake amounts.
In reality, a validator’s stake is fragmented into the following states: pending-active, active, pending-inactive and inactive.
These states have distinct properties dictated by the core staking protocol:
- only active and pending-inactive stakes earn rewards
- only active stake can be scheduled for unlocking (moved to pending-inactive state)
- only pending-inactive stake can be restored to active state (reactivated)
- only inactive stake can be withdrawn
The delegation pool adheres to these rules in order to guarantee the consistency of operations and any deviation may lead to scenarios such as:
- routing rewards to stake that did not earn them
- delegators withdrawing stake unlocked by others but themselves
- unlocking less stake on the stake pool than requested on the delegation one, causing downtime on withdrawals
Consequently, stakes in different states should be separated, and specifically, the shares owning these stakes.
The initial design choice is to store active and pending-active stake on the same shares pool and inactive and pending-inactive stake on a different one.
The stake-management operations were implemented as:
- adding stake is buying shares into the active pool
- unlocking stake is redeeming stake from the active pool and using it to buy shares into the pending-inactive one
- reactivating stake is redeeming stake from the pending-inactive pool and using it to buy shares into the active one
- withdrawing stake is redeeming stake from the pending-inactive pool and transferring it to delegator’s account
When the current lockup cycle is ended on the stake pool, the entire pending-inactive stake is inactivated and does not earn rewards anymore. However, during this newly started lockup, delegators can unlock stake which would intertwine with the inactive one. As shares within a pool appreciate uniformly, the inactive stake would continue to earn rewards from the ones produced by the pending-inactive one and impact its overall APY.
To address this inconsistency, another type of shares pool has been introduced, one storing inactive stake exclusively. When starting a new lockup cycle, a fresh pending-inactive shares pool is created to host future pending-inactive stake that would be unlocked within the current cycle.
Because the shares pool data structure is not easily enumerable, it would be inefficient (and costly) to merge the inactive and former pending-inactive pools. Instead, the delegation pool will leave the former pending-inactive pool as-is and consider it inactive from now on.
The delegation pool will contain multiple inactive shares pools of the past lockup cycles and one pending-inactive pool of the ongoing one.
In order to trace where a delegator has stake, the lockup cycles observed by the delegation pool are enumerated and the inactive pools are indexed by them.
The design goes further and enforces that a delegator can have stake only in one lockup cycle at all times. When unlocking stake, any existing inactive one will be automatically withdrawn to the delegator’s account. This saves the delegator from programmatically finding all past cycles where they had unlocked stake.
We’ve discussed about separating the stake states and assumed that the delegation pool would know when a lockup cycle ended on the stake pool and act accordingly. We’ve also omitted how rewards are actually routed to the corresponding internal pools.
To ensure stake-management operations are executed against the most up-to-date state of the delegation pool, a synchronization process is triggered before the actual operation.
When synchronizing the delegation pool to its attached stake pool:
- stake recorded on the delegation pool is compared against the stake reported by the stake pool:
- in case of active and pending-active stakes, any additional amount observed represents the rewards produced in the meantime by the active stake, which got distributed only to the stake pool
- in case of pending-inactive stake, any additional amount observed represents the rewards produced in the meantime by the pending-inactive stake
- the commission fee is extracted from the new rewards and used to buy active and pending-inactive shares for the operator, while the remaining amount is added on top of the internal pools as rewards.
- if any new inactive stake is detected, then the lockup cycle has ended on the stake pool and inactivated the entire pending-inactive stake. The delegation pool will create a new shares pool as described above, update its known inactive stake and observed lockup cycle.
Challenges
flash-loan attacks
As active and pending-active stakes live within the same shares pool, the latter would unfairly benefit from rewards produced by the former when the current staking epoch ends.
If someone borrows a large amount of stake and delegates it in the last epoch of the lockup cycle, they get rewarded almost all epoch rewards of the pool and then can unlock, withdraw and return the borrowed stake.
Solution:
To mitigate this issue, a carefully designed ratio of the added stake is charged and then refunded along with the actual active rewards when this epoch ends.
This fee acts as a placeholder for the rewards the pending-active stake would have earned if being active.
extracted-fee = (amount — extracted-fee) * reward-rate% * (1 — operator-commission%)
share-price manipulation
When converting between internal shares and external stake there are truncation errors resulting from integer division.
A delegator loses at most 1 share on a buy operation and at most 1 octa on a redeem. The resulting loss ends up on the shares pool and artificially increases its share price.
An attacker could take advantage of this and continuously increase the share price on an internal pool. Now, 1 share is worth a lot, delegators are expected to lose that much when buying shares, while the resulting loss becomes rewards for existing shares (including the ones of the attacker).
Solution:
In order to make it computationally infeasible to orchestrate this attack, a minimum stake is enforced on all internal pools as well as a scaling factor setting the initial price on the pool.
The loss becomes less dominant and far more operations are required to raise the price to the same values as before.
A detailed analysis can be found here.
operator commission
The operator is rewarded for running a validator node by means of a commission fee percentage set at delegation pool’s creation.
The former distribution contract, responsible for splitting validator rewards between the stake owner and operator, has some limitations:
- the operator had to explicitly request its commission rewards to be calculated
- the stake owner cannot cancel the unlocking of any stake as this would reactivate any unlocking commission rewards too.
- the operator does not receive its share out of the pending-inactive rewards
Solution:
The current design empowers the operator to access the same API as regular delegators and addresses the shortcomings presented above.
In terms of internal balances, the operator is treated as any other delegator. The only distinction is that when new rewards are captured, a part of them is used to buy shares for the operator as dictated by the commission fee. This commission fee currently cannot be changed.
Benefits of the implementation
As stated throughout the article, the benefits of this implementation apply to the entire ecosystem by creating the premise for even further decentralization through the elimination of capital barriers for new validators which will now have the option to participate in the network without having to supply the entire required stake.
On the other hand, token holders will be able to support the protocol by delegating their assets and earning rewards.
Last but not least, the Aptos blockchain will benefit from a higher security while giving all its supporters the chance to be a part of their amazing journey ahead.