#37285 [SC-Critical] Incorrect Delegation State After Slashing in LockedGold Contract
Submitted on Dec 1st 2024 at 16:37:37 UTC by @jovi for Audit Comp | Celo
Report ID: #37285
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/celo-org/celo-monorepo/blob/release/core-contracts/12/packages/protocol/contracts/governance/LockedGold.sol
Impacts:
Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results
Description
Brief/Intro
The LockedGold
contract fails to properly update the delegated voting power when an account is slashed. Specifically, the contract neglects to call key functions like _updateDelegatedAmount
and revokeFromDelegatedWhenUnlocking
for the slashed account. This leads to an inconsistent state where delegatees retain voting power based on the pre-slashing balance, causing discrepancies in governance integrity.
Vulnerability Details
Context
The LockedGold
contract is responsible for managing locked CELO and delegation of governance votes. To ensure the accuracy of voting power, it employs two key functions:
_updateDelegatedAmount
:Purpose: Recalculates the CELO delegated to each delegatee based on the current total locked balance of the delegator.
Usage: Invoked after any operation that modifies the total locked gold (e.g.,
lock
orrelock
).Importance: Ensures that delegated voting power is proportionally updated to reflect the delegator’s current locked gold.
revokeFromDelegatedWhenUnlocking
:Purpose: Adjusts delegation amounts when gold is being unlocked, ensuring that delegatees do not retain voting power over funds no longer locked.
Usage: Invoked during the unlocking process before the balance is reduced.
Importance: Prevents delegatees from holding inflated voting power after funds are unlocked.
These functions are systematically invoked during standard balance-altering operations such as:
lock()
: Updates delegated amounts after locking additional CELO.unlock()
: Revokes delegations for the unlocked amount.relock()
: Recalculates delegations after relocking previously unlocked gold.
However, the slash()
function fails to invoke these critical updates.
Vulnerability
When an account is slashed using the slash()
function, the contract reduces the account’s locked balance but does not update the associated delegated amounts. This creates a discrepancy between the account's actual locked balance and the delegatees' voting power.
Key issues:
Delegation Discrepancy: The slashed account’s delegatees retain voting power based on the pre-slashed balance.
Reporter Delegation State: The slasher’s account does not properly reflect delegation power from the reward.
Incorrect Temporary State: While the
updateDelegatedAmount()
function is publicly available for permissionless updates, the temporary inconsistency can be exploited.
The following snippet illustrates the issue:
This discrepancy creates an inconsistent state in the delegation system, where the actual locked gold doesn't match the delegated amounts temporarily.
Even though there is the updateDelegatedAmount
public function that allows permissionless updates of delegated amounts, allowing this explicit transition to a wrong state during slashes may be utilized by exploits to falsely display states that have more voting power than they should.
Impact Details
Governance Integrity: Delegatees retain incorrect voting power after slashes, undermining the fairness and accuracy of governance decisions.
Economic Imbalance:
Slashed accounts effectively retain an unfair delegation advantage.
Slasher delegatees may not gain immediate delegation power, leading to delayed or inaccurate representation.
Proof of Concept
Proof of Concept
The following test demonstrates the vulnerability. It creates a delegator account, locks CELO, delegates voting power, and then slashes the account. The test shows that the delegatee retains voting power based on the pre-slashed balance.
Test Code
To demonstrate this vulnerability paste the following modified code snippet at the LockedGold.t.sol
test file:
Run the tests with the following command:
Was this helpful?