Slow Lipstick Hare
Medium
Permanent Locking of Excess ETH in NounsDAO StreamEscrow Contract that might have been sent by mistake.
Severity: Medium
Vulnerability Context
Contract: StreamEscrow
Root Cause: No callable function to rescue any amount of Excess ETH trapped in the contract.
Impact:
Excess ETH sent directly to the contract will be permanently locked with no mechanism for withdrawal.
Proof of Concept:
- The contract does not implement a "rescueETH()" function as it does a "rescueToken" function, so we this implies it is expected that Tokens or the Native Token might be sent to the contract by accident.
See as rescueToken() is already implemented: https://github.com/sherlock-audit/2024-11-nounsdao/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/StreamEscrow.sol#L292
-
Stream calculations are precise and do not account for excess ETH
-
The contract is non-upgradeable
-
No administrative function exists to withdraw accidentally sent ETH
Historical Precedent:
A similar vulnerability was identified in the NounsDAO Stream contract two years ago:
sherlock-audit/2022-11-nounsdao-judging#14
- The previous Stream contract also lacked a native token rescue mechanism
- This finding was confirmed and classified as a medium severity issue
Technical Details:
- Streams are calculated with exact ETH amounts per tick
- "sendETHToTreasury()" only sends calculated stream amounts
- No function allows withdrawal of unaccounted ETH balance
- Contract is non-upgradeable, making the issue permanently unfixable
Potential Loss:
Any ETH sent directly to the contract outside the stream mechanism will be irretrievably lost, potentially leading to permanent fund lockup.
Recommendation:
Implement a "rescueETH()" function similar to "rescueToken()", controlled by the DAO, to allow recovery of mistakenly sent funds.
function rescueETH(address to, uint256 amount) external onlyDAO {
(bool sent, ) = to.call{value: amount}("");
require(sent, "ETH transfer failed");
}
Risk Mitigation:
- Add a function to rescue native tokens
- Ensure the function is protected by the "onlyDAO" modifier
- Implement proper transfer checks