TempleGold

TempleDAO
Foundry
25,000 USDC
View results
Submission Details
Severity: high
Invalid

Voting Powers can be doubled by self-delegation

Summary

Token owners can double their voting power only once through self-delegation in the TempleGoldStaking contract.

Vulnerability Details

In the TempleGoldStaking contract, token owners can delegate their voting power by calling the delegate function, which makes a call to the internal _delegate function:

function delegate(address delegatee) external override {
return _delegate(msg.sender, delegatee);
}

In the call to the internal _delegate function, with both delegator and delegatee being the token owner's address:

function _delegate(address delegator, address delegatee) internal {
address currentDelegate = delegates[delegator];
uint256 delegatorBalance = _balances[delegator];
delegates[delegator] = delegatee;
emit DelegateChanged(delegator, currentDelegate, delegatee);
_moveDelegates(currentDelegate, delegatee, delegatorBalance);
}

currentDelegate will be address(0) due to the first delegation of the token owner and the default value of the delegates mapping. Then the _moveDelegates function is called to transfer voting power. The _moveDelegates function is invoked with the following parameters: srcRep (currentDelegate) = address(0), dstRep=delegatee and amount = delegatorBalance.

function _moveDelegates(
address srcRep,
address dstRep,
uint256 amount
) internal {
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) {//@audit: Skipped because srcRep = address(0)
}
if (dstRep != address(0)) {//executed
uint256 dstRepNum = numCheckpoints[dstRep];
uint256 dstRepOld = dstRepNum > 0 ? _checkpoints[dstRep][dstRepNum - 1].votes : 0;
uint256 dstRepNew = dstRepOld + amount;
_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
}
}
}

The first if condition will return true, but the second if will be skipped due to srcRep being address(0). The third if block will be executed, and the voting power of dstRep (which is the token owner's address) will be increased. As a result, the token owner’s voting power has been increased by an amount equal to their total number of tokens, without a corresponding decrease.

It is important to note that the exploit only works once per address. If a token owner subsequently delegates to themselves after their initial self-delegation, currentDelegate will be set to a non-default value in the delegates mapping, and the delegation logic will work correctly.

Impact

Malicious token owners gain more voting power than they deserve. They could secretly accumulate multiple tokens and then exploit this vulnerability to double their voting power. This could also undermine the DAO’s mission and lead to a loss of trust in the protocol

Tools Used

Manual Review, VS Code

Recommendations

  1. Make delegate Function Public:

function delegate(address delegatee) public view returns (address) {
address currentDelegate = delegates[delegatee];
return currentDelegate == address(0) ? delegatee : currentDelegate;
}

Then, use this function inside the internal _delegate function instead of accessing the delegates mapping directly:

function _delegate(address delegator, address delegatee) internal {
address PrevDelegate = delegate(delegator);
uint256 delegatorBalance = _balances[delegator];
delegates[delegator] = delegatee;
emit DelegateChanged(delegator, PrevDelegate, delegatee);
_moveDelegates(PrevDelegate, delegatee, delegatorBalance);
}

or as a second option,

  1. Adjust for Initial Delegation from address(0):

Modify the _moveDelegates function to adjust for the initial delegation from address(0):

function _moveDelegates(
address srcRep,
address dstRep,
uint256 amount
) internal {
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) {
// Existing logic
}
if (dstRep != address(0)) {
uint256 dstRepNum = numCheckpoints[dstRep];
uint256 dstRepOld = dstRepNum > 0 ? _checkpoints[dstRep][dstRepNum - 1].votes : 0;
uint256 dstRepNew = dstRepOld + amount;
_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
----> // Adjust for the initial delegation from address(0)
if (srcRep == address(0) && delegates[dstRep] == address(0)) {
_writeCheckpoint(dstRep, dstRepNum, dstRepNew, dstRepOld);
}
}
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

oxwhite Submitter
12 months ago
inallhonesty Lead Judge
11 months ago
inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.