Platform: Code4rena
Start Date: 18/10/2022
Pot Size: $50,000 USDC
Total HM: 13
Participants: 67
Period: 5 days
Judge: Picodes
Total Solo HM: 7
Id: 172
League: ETH
Rank: 49/67
Findings: 1
Award: $37.88
π Selected for report: 0
π Solo Findings: 0
π Selected for report: berndartmueller
Also found by: 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, Aymen0909, BClabs, Diana, Jeiwan, Lambda, LeoS, RaoulSchaffranek, RaymondFam, RedOneN, ReyAdmirado, Rolezn, SaharAP, Trust, V_B, __141345__, a12jmx, bharg4v, brgltd, carlitox477, ch0bu, chaduke, cloudjunky, cryptostellar5, cryptphi, csanuragjain, d3e4, delfin454000, erictee, fatherOfBlocks, hansfriese, ignacio, joestakey, karanctf, ladboy233, lukris02, mcwildy, minhtrng, peanuts, ret2basic, seyni, slowmoses, svskaushik, tnevler, yixxas
37.8829 USDC - $37.88
https://github.com/jbx-protocol/juice-nft-rewards/blob/main/contracts/JB721TieredGovernance.sol#L177
The _totalTierCheckpoints does not get updated if user has delegated its votes to address(0). This becomes a problem for Governance since for a tier, the total votes would become more than existing vote. In case if a significant portion of vote has been delegated to address 0, then the voting system will get impacted
function setTierDelegate(address _delegatee, uint256 _tierId) public virtual override { _delegateTier(msg.sender, _delegatee, _tierId); }
function _delegateTier( address _account, address _delegatee, uint256 _tierId ) internal virtual { // Get the current delegatee address _oldDelegate = _tierDelegation[_account][_tierId]; // Store the new delegatee _tierDelegation[_account][_tierId] = _delegatee; emit DelegateChanged(_account, _oldDelegate, _delegatee); // Move the votes. _moveTierDelegateVotes( _oldDelegate, _delegatee, _tierId, _getTierVotingUnits(_account, _tierId) ); }
Observe there is no check to verify that new _delegatee is not address 0
Now _moveTierDelegateVotes function is called which updates the _delegateTierCheckpoints for this user on the mentioned tier
function _moveTierDelegateVotes( address _from, address _to, uint256 _tierId, uint256 _amount ) internal { // Nothing to do if moving to the same account, or no amount is being moved. if (_from == _to || _amount == 0) return; // If not moving from the zero address, update the checkpoints to subtract the amount. if (_from != address(0)) { (uint256 _oldValue, uint256 _newValue) = _delegateTierCheckpoints[_from][_tierId].push( _subtract, _amount ); emit TierDelegateVotesChanged(_from, _oldValue, _newValue, _tierId, msg.sender); } // If not moving to the zero address, update the checkpoints to add the amount. if (_to != address(0)) { (uint256 _oldValue, uint256 _newValue) = _delegateTierCheckpoints[_to][_tierId].push( _add, _amount ); emit TierDelegateVotesChanged(_to, _tierId, _oldValue, _newValue, msg.sender); } }
Since _to is address 0 (user is delegating to 0 address) so _delegateTierCheckpoints[_to][_tierId] will not be updated and only _delegateTierCheckpoints[_from][_tierId] will be updated which is correct
The problem here is that now this tier has in a way burned all the voting units for User A which means this tier has now lesser number of voting units. This should have been updated in _totalTierCheckpoints (which tracks total voting units in tier) but it is not done within this flow
This means after this transaction, total voting units in this checkpoint will be including the non existing votes (from user A delegation to 0 address)
So lets say in tier id1
total votes : 1000 user A voting units: 600 User A delegates to address 0 total votes : 1000 user A voting units: 0 (since _delegateTierCheckpoints[_from][_tierId].push( _subtract, _amount );) Governance voting starts Only 1000-600=400 votes can be placed If governance required atleast 50% consensus from total votes or required 50% participation of overall votes then this would never reach
Do not allow User to delegate to 0 address if they have some voting units. If required then kindly update _totalTierCheckpoints before delegating to 0 address
#0 - trust1995
2022-10-23T22:55:54Z
I believe it to be a non issue. The reasoning is that any voter who delegates to 0 can be considered a malicious voter. If the voter is malicious, it can just as well use the majority to do whatever it wishes, instead of crippling the voting. If the voter is not malicious, then sending to the 0 address is a user mistake, which should be checked but can't be more than LOW severity as it depends on big user error.
#1 - csanuragjain
2022-10-24T05:18:04Z
I believe that this issue is highlighting an accounting issue which if not fixed will always show higher number of total votes per tier than actually present in system. This will become a problem for a voting consensus which is derived based on total votes per tier
I do understand your point that user can use those votes directly instead of crippling the system. Although user error can happen, which could lead to this issue. Previously issue requiring pre-requisite conditions have been marked Medium but would leave this for the judge's decision :)
#2 - drgorillamd
2022-10-24T11:41:41Z
Duplicate #228 Mis-tagged this one, transferring to 0 is not an issue per se, but the total weight has an accounting issue
#3 - mejango
2022-10-24T17:28:36Z
@drgorillamd down to prevent delegation to 0 address.
#4 - Picodes
2022-11-04T09:57:09Z
The warden showed how delegation to address(0)
is not handled correctly as:
Mitigation could be to prevent delegation to address(0)
or to correct the accounting
However it's very unlikely that this impact the system globally as highlighted by @trust1995, and at the user level we're down to a case of a finding conditional to a user mistake
#5 - c4-judge
2022-11-04T09:57:21Z
Picodes marked the issue as change severity
#6 - c4-judge
2022-11-04T09:57:35Z
Picodes marked the issue as satisfactory
#7 - c4-judge
2022-11-18T18:14:21Z
Picodes changed the severity to QA (Quality Assurance)
#8 - Picodes
2022-11-22T09:32:15Z
@csanuragjain apologizes for the back and forth in my decisions. When reviewing this with a senior judge, I decided to downgrade this to Low as the error can be corrected easily if it is one, and if the voter is malicious this is like delegating to an EOA that will never vote
#9 - c4-judge
2022-12-03T18:57:05Z
Picodes marked the issue as grade-b