Incident Disclosure 2022-02-24
When a transaction is finalized past the voting deadline, the votes become reset to their default state. This leads to votes by vlSEX holders for gauges to receive SOLID emissions to not become registered, whilst Solidex' own gauges receive far too many votes. This issue can be reproduced at each new round of voting, once per week.
Both Solidex and Solidly codebases are immutable and non-upgradeable. This leads to there being no feasible fix possible at this point. Due to the nature of the issue, the ability to mitigate this issue and prevent it from occurring, while possible, has limited reliability and effectiveness will vary. The Solidex team is researching potential deterrents and ways to be as effective as possible with mitigation attempts.
Solidex Contract Deployments
Solidex contributors deployed an initial partner deposit contract on February 10 2022 followed by the remainder of the protocol contracts on February 22. Some of these contracts remain unverified in order to safeguard the protocol's head start and to avoid its code from being copied by competitors who have not yet been able to launch. Solidex has shared its codebase with third party projects for peer review and is at the time of this writing undergoing a security audit, with a second audit scheduled to begin on March 14.
SOLID emissions and voting
Solidly's first round of gauge emissions began at 00:00:00 UTC on February 24, which is also the deadline for voting on the allocation of SOLID emissions to gauges.
Solidly emissions run for 7 days, a new round of voting begins at the beginning of every Thursday at 00:00:00 UTC.
Solidex voting on SOLID emissions
Solidex voting resets at the same time, every Thursday at 00:00:00 UTC each week.
During each period, vlSEX holders can vote once on the SOLID gauges of their choice to receive emissions, and this is then submitted to Solidly through an unguarded function,
SolidexVoter.submitVotes(), that anyone can call.
Solidly voting can be updated constantly, so each time the permissionless
submitVotes() is called, Solidex updates its voting based on the most recent vlSEX vote allocation for the period.
Crucially, the protocol by default allocates a hard coded 10% of its total voting power to its two token pools, SEX/WFTM and SOLIDsex/SOLID, shared equally.
With minutes left for the voting deadline to expire,
submitVotes() was called, and the transaction finalized 6 seconds after the passing of the voting period, at 00:00:06 UTC.
Details of the issue
The design of Solidex understood the Solidly voting to take effect at the strike of 00:00:00 UTC on Thursday of each week, which is imprecise. In reality, voting takes effect individually for each gauge upon the first action in a pool, after the passing of this weekly deadline.
Thus, there is a window at the beginning of the week where there has been no voting, but last week's votes have not yet taken effect.
The issue became apparent when the transaction in question was finalized just after the passing of the deadline (causing the state of Solidex voting to reset to the default), but before there had been any actions on the Solidly gauges in question.
As a result, all existing vlSEX votes were reset, and the Solidex default vote state was submitted to Solidly, resulting in the only votes being for the hardcoded SEX/WFTM and SOLIDsex/SOLID pools. Since there were no other votes, these pools received all the voting power from Solidex.
It will be possible to reproduce this issue at the strike of midnight UTC every Thursday.
Whenever this issue occurs, SEX/WFTM and SOLIDsex/SOLID gauges receive 50% each of Solidex voting power for that entire period, whilst vlSEX votes for other gauges do not get submitted to Solidly. As was what happened during this incident.
Details of fix
The deployed smart contracts of both Solidly and Solidex are immutable and non-upgradeable. As such, there is no known way to apply a fix at this time.
Details of possible mitigation
The issue is prevented as long as
submitVotes() is not called after 00:00:00 UTC and before there is activity on the pools of the gauges that have been voted for.
A possible method of prevention, is thus to ensure that
submitVotes() is called just before 00:00:00 UTC, and that pools that have received votes are interacted with immediately at the strike of 00:00:00 UTC, therefore eliminating the window of possibility.
To succeed in broadcasting transactions with such precision relies on there not being any strong incentives to compete and race to succeed to disrupt Solidex's voters, by having the protocol's entire voting power be directed at its own two gauges for that week.
submitVotes() is unguarded, it is not possible to reliably and consistently prevent a motivated attacker from such griefing, at best it can be made a challenge and costly to succeed, to deter attackers from trying.
The Solidex team has immediately begun researching potential deterrents and ways to be as effective as possible in this mitigation.
Timeline of events
2022-02-24 00:00:00 UTC: Solidex voting state is reset and Solidly enters into a new voting period
submitVotes() is called and finalized, before there has been activity on Solidly pools in the voting period
00:09:00 Solidex developers notice there are no SOLID emission on some pools and begin investigation
00:22:00 Reports received from third parties about a potential issue
01:40:00 Public announcement of the issue
05:00:00: Publication of the full disclosure
Third party disclosure
Solidex does not currently have any established bilateral disclosure agreements. Given the nature of the issue, a disclosure was made to Solidly deployer Andre Cronje prior to the initial public announcement of the issue.