Hello! MixBytes is here
The team of three security auditors checked the VotingAdapter contract on the 4246b8d
commit. During our checks we havenāt found anything suspicious or vulnerable in the contract. We started our review from the b83b982
commit and shared several improvements with the Lido DAO Operations Workstream contributors. Part of the improvements were implemented, and we rechecked the contract.
Deployed VotingAdapter contract: 0x4b2AB543FA389Ca8528656282bF0011257071BED
The proposed Voting contract has successfully passed two security audits on the commit 50d9802.
Deployed contract: Voting (Implementation): 0xf165148978Fa3cE74d76043f833463c340CFB704
You can see full reports here:
Super important proposal.
We appreciate this proposal as being the additional steps to decentralization for LidoDAO.
With the recent activity around 51% attacks for governance, while itās extremely difficult to do so for Lido, could Lido consider a mitigation mechanism like MakerDAOās hat system which imposes an implicit dynamic quorum to keep the quorum feasible for delegates while out of reach for malicious actors? Additionally, as quorum has become highly challenging, is it possible to create a ranked delegation mechanism, where voting power changes hands depending on how active a delegate is? I.e., Delegates A, B, and C are selected for 10,000 votes, from Party D, and depending on the activity of these delegates their share of the delegation from Party D shifts?
Thereās a number of safeguards in Lido DAOās onchain governance mechanics:
- Objection phase of the vote, providing a time window for tokenholders to prevent the malicious motion from getting voted on;
-Delegation mechanics allowing for the tokenholder to vote over the delegate (basically one can overpower the delegated vote).
Combined those provide first-layer defence against malicious governance motions.
The next step for Lido DAOās governance is Dual Governance proposal: Lido dual governance explainer (research distillation)
On a separate note, I canāt say I like āchains of delegationā much: such kind of designs are aimed to preserve VP being delegated / activated, but push for less control from tokenholder. Mechanics with dynamic quorum requirements seem to be a pretty complicated solution, so Iām not sure if those should be the first choice for upgrades.
Iām happy to finally announce that the voting for enabling on-chain delegation for Lido DAO is now live!
Please, participate here: Lido DAO Voting UI
The main phase lasts until August 15, 2024, at 14:00 UTC.
Be sure to vote!
We are excited to introduce our guide on how to verify on-chain voting #177 items! This note is designed to help you navigate the process with ease. If you have any questions or need further clarification, please donāt hesitate to reach out.
Gm from Aragon! Back in April, we decided to perform an extensive review of the LIP-21: Simple On-Chain Delegation and some other alternatives recently proposed in the Lido forum to provide feedback and a recommended course of action. You can find this extensive review below (some of the provided code examples may have had minor changes from April until now):
Authors: Jordan (Sr. Smart Contract Developer) Giorgi (Sr. Smart Contract Developer), CapitulationCapital (ex-Ecosystem Lead), Anthony Leuts (CEO) Aragon X
Who is Aragon X: Aragon X is the newest generation of builders from the Aragon Project who have recently launched Aragon OSx, a new generation modular DAO framework, and the Aragon App interface.
Summary
Lido recently introduced LIP-21: Simple On-chain Delegation which closed its snapshot round with near unanimous support of 70M LDO and is now available for an onchain vote . To quote said snapshot proposal:
āThe Simple On-chain Delegation allows LDO token holders to delegate their voting power to other addresses and delegates to participate in on-chain voting on behalf of their delegated voters. Alongside that, itās proposed to add TRP (Token Rewards Plan) participants the ability to delegate their LDO rewards.
This proposal is intended to address the security and operational hurdles arising from the lack of delegation for on-chain voting.ā
Details of the LIP
Contracts Involved
LIP-21 (referred to from here as āthe LIPā) represents a set of changes to the Voting.sol
contract and the VotingAdapter.vy
contract, where:
Voting.sol
is a Solidity 0.4.24 Upgradeable Proxy that is built on Aragon OS. Its primary purpose is storing and retrieving Vote data on-chain based on the associated LDO tokenās balance at a given block snapshot.VotingAdapter.vy
is a Vyper contract that allows tokens yet to be vested to vote, by serving as a communication layer between the vesting contracts and theVoting.sol
contract. It can be updated by pointing a factory contract controlling the deployment of other vesting contracts to a new VotingAdapter implementation.
Additionally there is the LDO token, which is a non-upgradeable Minime ERC20 token which includes balance histories:
All MiniMe Tokens maintain a history of the balance changes that occur during each block. Two calls are introduced to read the totalSupply and the balance of any address at any block in the past.
function totalSupplyAt(uint _blockNumber) constant returns(uint) function balanceOfAt(address _holder, uint _blockNumber) constant returns (uint)
Implementation
The LIP specification is pretty comprehensive and well documented, we see no reason to rewrite it here, but instead as a summary of key takeaways:
- Delegation is tracked via a pair of mappings added to the
Voting.sol
contract. The pair of mappings is used to ensure it is both quick to check the delegate for a given voter and easy to loop over the list of voters for that same delegate. - Users can call
setDelegate
which will update the delegation mappings with their chosen delegate. - Delegates can vote on behalf of voters by calling
attemptVoteForMultiple
, this in turn reaches out to:_isDelegateFor
to confirm inVoting.sol
if the user is delegated_hasVotingPower
to fetch the token balance of the voter from the LDO token at the block snapshot_vote
is then called on behalf of the voter, a new argument has been added to track if the userisDelegated
and thenVoterState
is stored asDelegateYea
orDelegateNay
in place ofYea
orNay
- Voting adapter adds methods to set and reset delegates, which simply proxies the calls to the
Voting.sol
contract - Changes to the interface in
Voting.sol
andVotingAdapter.vy
can be summarised below:
Voting.sol
/// ----- new state ------
struct DelegatedAddressList {
address[] addresses;
}
// delegate -> [delegated voter address]
mapping(address => DelegatedAddressList) private delegatedVoters;
struct Delegate {
address delegate;
uint96 voterIndex;
}
// voter -> delegate
mapping(address => Delegate) private delegates;
enum VoterState { Absent, Yea, Nay, DelegateYea, DelegateNay }
/// ----- new transactions -----
function setDelegate(address _delegate) public;
function resetDelegate() public;
function attemptVoteForMultiple(uint256 _voteId, bool _supports, address[] _voters) external;
function attemptVoteFor(uint256 _voteId,bool _supports,address _voter) external
/// ----- new views -----
function getDelegatedVoters(
address _delegate,
uint256 _offset,
uint256 _limit
) external view returns (address[] memory, uint256[] memory);
function getDelegatedVotersAtVote(
address _delegate,
uint256 _offset,
uint256 _limit,
uint256 _voteId
) external view returns (address[] memory, uint256[] memory);
function getVotersStateAtVote(
uint256 _voteId,
address[] calldata _voters
) external view returns (VoterState[] memory voterStatesList);
/// ----- new events -----
event SetDelegate(address indexed voter,address indexed delegate);
event ResetDelegate(address indexed voter,address indexed delegate);
event CastVoteAsDelegate(
uint256 indexed voteId,
address indexed delegate,
address indexed voter,
bool supports,
uint256 stake
)
VotingAdapter.vy
def setDelegate(_delegate: address): nonpayable
def resetDelegate(): nonpayable
Alternative Implementations
Alongside the LIP, we reviewed a couple of other options. The first was by Tally in a research grant and the second (mentioned in the grant post as well) was 1Hiveās TAO Voting built on Aragon OS.
Tao Voting
The Tao voting implementation shares many similarities with the Lido solution in its implementation, with the major differences being that a representativeManager
contract is not defined separately and delegation is stored inside Voting.sol
. Additionally, the Tao voting solution would require users wrapping the LDO tokens before votes could be held and we understand that this introduces additional complexity which may negatively impact delegation.
OnTokenTransfer
hook
Tally proposed a solution whereby the non-upgradeable LDO contract can be placed behind an adapter contract that conforms (with some modifications) to the ERC20Votes standard - allowing it to be used with standardised frontends like Tally and the new Aragon App.
As a summary:
- Onchain delegations are updated as part of the standard token transfers, this is done via a hook inside the LDO tokenās
doTransfer
function:
function doTransfer(address _from, address _to, uint _amount) internal returns(bool) {
// ... skipped for brevity
// Alerts the token controller of the transfer
if (isContract(controller)) {
// Adding the ` == true` makes the linter shut up so...
require(ITokenController(controller).onTransfer(_from, _to, _amount) == true);
}
- The
TokenController
is both upgradeable (TokenManager.sol
) and can be updated viachangeController
, the Tally design would add a separate contract to track on-chain delegations, which would be reached out to via the upgradedITokenController
contract.
Tally donāt specify the exact manner in which delegation will be implemented, other than it will likely track the OpenZeppelin implementation used in ERC20Votes
, but mention that other areas of the Aragon governance contract could be replaced due to Aragon OSās flexible permissions system (something we have made sure to keep and expand upon with OSx), which would allow closer conformity to the OpenZeppelin Governor standard.
Comparing
When reviewing the alternative solutions, one concern with Tao Voting (which would also apply in part to the LIP as it shares many similarities) would be related to its handling of large numbers of addresses:
*Aragon TAO Voting unfortunately hits the same constraints as the Lido DAO when seeking an implementation of delegation for the token. That is: there is no native way to track delegated voting power onchainā¦
ā¦The implementation requires that the user pass in at vote time an array containing all of the voters that they are voting on behalf of. This is non-scalable, as the smart contract function loops through each individual delegate to calculate their voting power at voting time.
For a delegate such as Linda Xie 3 in Optimism, this would require passing in a list of over 80,000 delegates and executing the loop 80,000 times.*
Our impression from discussions with the Lido team are that they are aware of this issue and have accepted the tradeoff of higher costs for large delegates versus lower costs for the majority of voters and delegates. We should also note that, even the LIP snapshot vote states:
A proper full-fledged delegation mechanism involves maintaining an accounting to track actual delegated voting power at any moment accurately. Implementing this is a complex and time-consuming project.
It should also be noted that maintaining up-to-date delegation records inside doTransfer
will increase gas costs for all users, not just delegators. On the other hand, as Voting.sol
is separate from the LDO token (outside of fetching snapshot balances), adding the delegation functionality has no impact on the LDO transfer gas costs.
Specifically with the LDO token, one already has balance histories stored inside the contract. Adding a second contract to track historical delegations (as in the Tally solution) therefore has the gas costs of:
- Tracking the historical token balances in the LDO token (Minime)
- Tracking the delegation checkpoints as per
ERC20Votes
The tradeoff here is that the costs for the delegates is increased much more, as voting power at a point in time must be computed on the fly. Whereas the Tally solution would greatly increase gas costs for the LDO token on all transactions moving forward both inside and (crucially) outside of governance. While a full delegation solution is being worked on, we agree with the current proposal of the LIP being a fair compromise that doesnāt significantly increase gas costs for LDO users.
UI Implications
While UI details are not explicitly provided inside the LIP or associated material, we did note that the LIP does not conform to a common interface standard, and as such will require a custom UI to be implemented. Given Lidoās prominence in the industry and the importance of the LDO token, along with the sheer value at stake in the Lido protocol, such a solution must also be sufficiently decentralised to avoid exposing the voting process to censorship or manipulation by a central party.
This should not be challenging for the Lido team to build and Aragon has offered use of its new open design system and any UX support to Lido, both of which could help generate a general standard on voting UIās in the industry.
Closing Comments
In all, reviewing the solutions presented to Lido, we came to the following conclusions:
- The LIP is an elegant solution to achieve the basics of delegation, in a timely manner, to solve Lidoās present problem of low participation and votes failing to reach quorum.
- Given the importance and usage of LDO in the ecosystem we think optimising for everyday users and tokenholders at this stage over a smaller number of delegates, is a sensible tradeoff while a longer term strategy is envisaged.
- While adopting common voting standards may be desirable, the limitations of the LDO token, as it relates to upgradeability, make this a more complex problem that would need further thought and is a common trade-off in achieving safety versus flexibility.
- The Lido front-end should be decentralized, resistant to capture & censorship, tamper-proof, and not within reach of any 3rd party or nation state.
In conclusion, we appreciate the Lido teamās thought into devising a practical solution in a timely manner, and reiterate our initial commitment to offering any support or guidance, should the team have need of it. The Lido development team is world-class and has once again proven their creativity in finding excellent solutions to challenging problems, and under various constraints. We applaud their forward thinking approach and look forward to continue to working with them in the future and are happy that the community is signalling approval of LIP-21 and it will most likely pass onchain as well.
The omnibus vote #177, which included on-chain delegation, failed due to quorum. Iām seeing another on-chain vote scheduled for 8/20 - 8/22 on the calendar for re-voting, but would there be a change to the Public Delegate program schedule given there would be another vote required? Not being able to reach quorum regularly poses a problem for the DAO to push things forward with momentum, and hopefully the on-chain delegation will help solve it!
@notjamiedimon
I hope the vote will be relaunched today, and quorum will be reached this time. Letās see. Anyway, all updates on the Delegate Program will be posted on the Delegates Program proposal, and the discourd governance thread.
If the rerun is successful, no changes to the delegate program timeline will be needed.
Hello-hello!
Iām excited to share that on-chain vote #178 has been completed and enacted. The āVotingā and ātrp_voting_adapterā contracts have been upgraded!
Want to congrats everyone who participated in developing!
Happy to see the on-chain delegation live!
And this was our largest vote by the number of addressesāWOW!
DAO Ops Workstream thinks this deserves a celebration, if you voted you can mint your commemorative POAP here: Lido DAO Vote #178 Passed