The features we want:
- Ability for any token holder to delegate their voting power to another address. Someone who is delegated some voting power can delegate those further to another delegate.
- Holders and delegates have the ability to cast their own vote and override whatever their final delegate voted (can also vote before the delegate, and then the delegate won’t be voting on their behalf).
Implementation approach
When a delegation happens, the total voting power the delegator has (staked tokens + amount delegated to them) is recursively added to the delegate balanced of all the accounts above in the delegation chain. Thanks to this, it is really fast to compute the total voting power of an account, which makes the cost of delegates casting a vote constant regardless of how many holders have delegated their voting power to them.
This approach requires storing quite a fair amount of data when a delegation happens, alternative implementations could store less data on delegation and rely on doing more complex reads for calculating the total delegated amount. This would open the door to potential griefing attacks to popular delegates, making their vote casting transactions more costly.
Challenges
The main unsolved challenges that we have encountered while prototyping on-chain liquid democracy are:
1. Undelegation cost is proportional to delegation chain depth
Because the delegated voting power is added to all the delegates above in the delegation chain, undelegating requires recursively subtracting the previously delegated voting power from all delegates above. Therefore the cost of undelegating grows linearly with the depth of the delegation chain above one’s delegate.
This can be used by delegates to disincentivize undelegations by making them very expensive. By artificially delegating their voting power to other accounts they control, delegates can create a very deep delegation chain that is expensive to undelegate from.
If there are no bounds to the depth of delegation chains, delegates could make it impossible (gas limit attack) for someone to undelegate from them, which could even make tokens stuck forever. This issue can be solved by setting an upper limit (MAX_CHAIN_DEPTH
) to how deep delegation chains can be.
2. Properly easuring delegation chain depth requires a lot of storage
As shown in 1, keeping track of delegation chain depth is required to avoid major griefing attacks. A naive approach is just storing at every step of the chain the current depth (every time a delegation is made, it is set to the new chain depth is if it is higher than the previous one). This however presents some challenges when an undelegation happens, as it isn’t possible to know if there were actually more chains of that depth or what the new depth after undelegating is.
Two (bad) solutions to this are:
- Just keep the historic maximum chain depth for a delegate and not modify on undelegations.
- Keep track of the sizes of all delegation chains below a delegate, so whenever an undelegation happens the current delegation chain depth can be calculated. This would require a ridiculous amount of storage.
3. Delegators can block holders and delegates from delegating
A delegation chain can be created below a certain delegate or holder by delegating a chain of depth MAX_CHAIN_DEPTH - currentDepth
. This would prevent them from delegating further because their delegation chain would be at the maximum.
A solution would be to allow delegates to choose a custom maximum delegation chain depth that their delegators can have if they in order to delegate to them, so they couldn’t be pushed to the chain depth limit by someone delegating to them. A sane default could be MAX_CHAIN_DEPTH / 2
and popular delegates could increase it if needed (if someone will never delegate, it can safely be set to the upper bound). Once increased, it couldn’t be lowered unless forcing all an undelegation of all the deeper delegation chains.
4. The cost of overriding a vote is proportional to delegation chain depth
If anyone in the chain can override the vote of their delegate (In chain A -> B -> C
, B could override C’s vote for voting power A + B
and A could override the vote too), the cost for overriding a vote is proportionally to the chain depth above.
The griefing attack explained in challenge 1 can be used by delegates to disincentivize vote overriding, making them expensive by creating artificially deep delegation chains.
Conclusion
The impact of these challenges could be reduced by choosing better data structures than this initial naive approach. Implementing complex data structures in the EVM is usually suboptimal given the high costs of storage.
We are actively trying to workaround them and try to have an implementation that diminishes the potential griefing attack vectors. Implementing LD on-chain may end up being too expensive and offloading the calculation of delegation chains and tallying to an off-chain computation oracle may could be the way to go.