DatingDapp

AI First Flight #6
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: low
Invalid

[M-02] Missing Reentrancy Guard on `matchRewards` — Latent Exploitability

[M-02] Missing Reentrancy Guard on matchRewards — Latent Exploitability

Scope

  • LikeRegistry.sol

Description

matchRewards() performs an external low-level call multiSigWallet.call{value: rewards}() without a nonReentrant modifier on likeUser(). While userBalances is zeroed before the call (partial CEI), there is no reentrancy guard on the entry function. Currently latent because rewards = 0 (H-01), but becomes exploitable if H-01 is fixed without adding reentrancy protection.

MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
@> (bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
// No nonReentrant modifier on likeUser()

Risk

Likelihood: Low — Requires H-01 to be fixed first. The MultiSig wallet's receive() is benign.

Impact: High — If triggered, could drain user balances via repeated matchRewards calls.

Severity: Medium

  • SWC: SWC-107 (Reentrancy)

  • CWE: CWE-841

  • Evidence Grade: B (pattern confirmed, economic impact latent)

Proof of Concept

Assume H-01 is fixed and userBalances is properly tracked. Alice and Bob each deposit 1 ETH. When Bob likes Alice (creating a mutual match), matchRewards() executes. It zeroes userBalances for both users, then performs multiSigWallet.call{value: rewards}(""). If the MultiSig wallet's receive() were replaced with a malicious contract that re-enters likeUser() (via a third account that also likes one of the matched users), the attacker could re-trigger matchRewards() before the first call completes. Currently dormant because rewards = 0 due to H-01.

// Structural pattern confirmation (no runtime PoC — requires H-01 fix):
// 1. likeUser() has NO nonReentrant modifier
// 2. matchRewards() is internal, called from likeUser()
// 3. matchRewards() performs external call: multiSigWallet.call{value: rewards}("")
// 4. State reads (userBalances) happen before the external call
// but matches[].push() happens in likeUser() BEFORE matchRewards()

Recommended Mitigation

OpenZeppelin's ReentrancyGuard adds a mutex lock that prevents any external call from re-entering the protected function. Applying nonReentrant to likeUser() ensures that even if matchRewards() triggers a callback (via the MultiSig wallet's receive() or a malicious contract), the re-entrant likeUser() call will revert. This closes the reentrancy window regardless of how H-01 is fixed in the future.

+import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
-contract LikeRegistry is Ownable {
+contract LikeRegistry is Ownable, ReentrancyGuard {
-function likeUser(address liked) external payable {
+function likeUser(address liked) external payable nonReentrant {
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!