Platform: Code4rena
Start Date: 22/08/2022
Pot Size: $50,000 USDC
Total HM: 4
Participants: 160
Period: 5 days
Judge: gzeon
Total Solo HM: 2
Id: 155
League: ETH
Rank: 10/160
Findings: 1
Award: $1,683.29
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: cccz
Also found by: IEatBabyCarrots, KIntern_NA, Lambda, berndartmueller, bin2chen, csanuragjain, jayphbee, zzzitron
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L112-L115 https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L126-L144
User can delegate to delegatee
by calling delegeate and if the parameter delegatee
is address(0), it will be replaced with the msg.sender
.
function delegate(address delegatee) public { if (delegatee == address(0)) delegatee = msg.sender; return _delegate(msg.sender, delegatee); }
User can also delegate to delegatee
by calling delegateBySig, but this method doesn't take care of delegatee
like delegate
does.
This is to say it doesn't have if (delegatee == address(0)) delegatee = msg.sender;
check.
function delegateBySig( address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s ) public { bytes32 domainSeparator = keccak256( abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), getChainId(), address(this)) ); bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry)); bytes32 digest = keccak256(abi.encodePacked('\x19\x01', domainSeparator, structHash)); address signatory = ecrecover(digest, v, r, s); require(signatory != address(0), 'ERC721Checkpointable::delegateBySig: invalid signature'); require(nonce == nonces[signatory]++, 'ERC721Checkpointable::delegateBySig: invalid nonce'); require(block.timestamp <= expiry, 'ERC721Checkpointable::delegateBySig: signature expired'); return _delegate(signatory, delegatee); }
This leads to inconsistent modification of checkpoints
and numCheckpoints
when calling delegate
and delegateBySig
respectively if user accidently pass address(0) as delegatee
parameter.
When delegate
called with delegatee = address(0), the calling sequence like below:
function delegate(address delegatee) public { if (delegatee == address(0)) delegatee = msg.sender; return _delegate(msg.sender, delegatee); } function _delegate(address delegator, address delegatee) internal { /// @notice differs from `_delegate()` in `Comp.sol` to use `delegates` override method to simulate auto-delegation address currentDelegate = delegates(delegator); _delegates[delegator] = delegatee; emit DelegateChanged(delegator, currentDelegate, delegatee); uint96 amount = votesToDelegate(delegator); _moveDelegates(currentDelegate, delegatee, amount); } function _moveDelegates( address srcRep, address dstRep, uint96 amount ) internal { if (srcRep != dstRep && amount > 0) { if (srcRep != address(0)) { uint32 srcRepNum = numCheckpoints[srcRep]; uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; uint96 srcRepNew = sub96(srcRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount underflows'); _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); } if (dstRep != address(0)) { uint32 dstRepNum = numCheckpoints[dstRep]; uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; uint96 dstRepNew = add96(dstRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount overflows'); _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); } } }
We can see that delegate
calls _delegate
internally with delegator == delegatee
since delegatee
is address(0). And _delegate
calls _moveDelegate
internally with srcRep == dstRep
since currentDelegate
and delegatee
are equal. _moveDelegate
does nothing at this siuation.
Comparing to user calling delegateBySig
with delegatee
being address(0) , the _moveDelegate
method will execute
if (srcRep != address(0)) { uint32 srcRepNum = numCheckpoints[srcRep]; uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; uint96 srcRepNew = sub96(srcRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount underflows'); _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); }
and finnly modify checkpoints
and numCheckpoints
in _writeCheckpoint
method.
Add this line of code as delegate
does in delegateBySig
.
if (delegatee == address(0)) delegatee = msg.sender;
#0 - davidbrai
2022-08-29T15:48:27Z
Duplicate of #157