Closed howlbot-integration[bot] closed 3 months ago
thereksfour marked the issue as unsatisfactory: Invalid
This issue is valid delegating votes to address zero will lead to a permanent loss of voting power, this is a similar finding. https://code4rena.com/reports/2022-08-nounsdao#h-01-erc721checkpointable-delegatebysig-allows-the-user-to-vote-to-address-0-which-causes-the-user-to-permanently-lose-his-vote-and-cannot-transfer-his-nft
This is the POC, add it to ZZstRSR.test.ts.
it.only('Delegate to user2 then user2 delegate to address 0', async function () {
// Should perform basic validations on stake
const amount1: BigNumber = bn('50e18')
await rsr.connect(addr1).approve(stRSRVotes.address, amount1)
await stRSRVotes.connect(addr1).stake(amount1)
await stRSRVotes.connect(addr1).delegate(addr2.address)
// Mine block
await advanceTime(1)
expect(await stRSRVotes.delegates(addr1.address)).to.equal(addr2.address)
// Advance block
await advanceBlocks(1)
// Check current votes
let user1votes = await stRSRVotes.getVotes(addr1.address)
console.log("user1 votes", user1votes)
// Advance time
await advanceTime(1)
// User 2 delegates to address 0
const Delegation = [
{ name: 'delegatee', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'expiry', type: 'uint256' },
]
const nonce = await stRSRVotes.nonces(addr1.address)
const expiry = MAX_UINT256
const chainId = await getChainId(hre)
const name = await [http://stRSRVotes.name](https://t.co/yo16sCmMr1)()
const verifyingContract = stRSRVotes.address
// Get data
const buildData = {
types: { Delegation },
domain: { name, version: VERSION, chainId, verifyingContract },
message: {
delegatee: addr1.address,
nonce,
expiry,
},
}
// Get data
const sig = await ZERO_ADDRESS._signTypedData(buildData.domain, buildData.types, buildData.message)
const { v, r, s } = ethers.utils.splitSignature(sig)
// Change delegate for addr1 using signature
await rsr.connect(addr2).approve(stRSRVotes.address, amount1)
await stRSRVotes.connect(addr2).delegateBySig(ZERO_ADDRESS, nonce, expiry, v, r, s)
let user2votes = await stRSRVotes.getVotes(addr2.address)
console.log("user2 votes", user2votes)
})
See here: https://github.com/code-423n4/2024-07-reserve-findings/issues/93#issuecomment-2314711014 And your POC doesn't seem to work, it reverts at ZERO_ADDRESS._signTypedData.
Lines of code
https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSRVotes.sol#L166-L183 https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSRVotes.sol#L230-L254 https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/p1/StRSRVotes.sol#L211-L218
Vulnerability details
Summary
Do Note that this vulnerability was highly inspired by this recent finding -
The
StRSRVotes
contract contains a similar vulnerability to the one described in the Nouns NFT scenario. The issue lies in the_moveVotingPower
function, which is called during token transfers and delegations.We have identified a critical vulnerability in the
StRSRP1Votes
contract. WhendelegateBySig
is called, potential delegation to the zero address can lead to permanent loss of the user's voting power.Users are no longer able to participate in the voting decisions When this function is used to delegate to address zero, the delegator wallet loses all its voting power and can no longer transfer their votes out of that wallet.
Vulnerability Details
The
_moveVotingPower
function allows delegation to address(0), which can lead to permanent loss of voting power.The issue occurs when an
StRSR
owner delegates their voting power to an EOA. If the delegate, whether intentionally or unintentionally, uses thedelegateBySig
function to delegate to the zero address, one unit of voting power is permanently burned. This results in the associated losing its voting power and becoming non-transferrable. The transfer attempt will revert due to an underflow error when the contract attempts to reduce the sender's already zero voting power. When delegating toaddress(0)
, voting power is subtracted from the source but not added to any destination, effectively burning it.The
_afterTokenTransfer
function calls_moveVotingPower
, potentially causing issues during transfers if a user's voting power has been compromised. ThestakeAndDelegate
function allows users to stake and delegate in one transaction, potentially exacerbating the issue if misused.StRSR
token holders delegating to EOA accounts.StRSR
tokens can become non-transferrable with lost voting power.Impact:
Voting Power Loss: The affected user loses its voting power permanently, reducing its utility in governance processes.
Proof of Concept:
Setup: Alice owns
StRSR
1. Bob ownsStRSR
2.Delegation: Alice delegates her voting power to Bob (Alice votes: 0; Bob votes: 2).
Faulty Delegation: Bob uses
delegateBySig
to delegate his voting power to the zero address (0x0000...). One vote is permanently burned (Alice votes: 0; Bob votes: 1).Transfer: Bob transfers
StRSR
2 to a new wallet, Bobby (Alice votes: 0; Bob votes: 0; Bobby votes: 1).Zombie Noun: Alice's
StRSR
1 becomes a zombie: it has no voting power and cannot be transferred out of Alice's wallet due to an underflow error.Root Cause: The root cause lies in the StRSRP1Votes contract's _moveDelegates function, which handles vote delegation and transfer operations. The function fails to account for the scenario where voting power is delegated to the zero address. Specifically, when delegateBySig is used to delegate to the zero address, the function deducts voting power from the sender but does not reassign it to any recipient, leading to an irrecoverable loss of voting power. Additionally, this causes an underflow error during subsequent transfer attempts.
Recommended Mitigation:
The
StRSRP1Votes
contract should be refactored to include checks that prevent delegation to the address (0x0000...).Reserve owners should delegate their voting power only to smart contract accounts, such as those created with Gnosis Safe, to prevent this vulnerability from being exploited.
Assessed type
call/delegatecall