DatingDapp

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

Reentrancy Vulnerability in matchRewards() Allows Fund Drainage

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

  • The LikeRegistry contract implements a dating/matching platform with ETH-based likes and multisig wallet rewards.

  • Explain the specific issue or problem in one or more sentences

  • The matchRewards() function violates the Checks-Effects-Interactions pattern by making an external call to the newly deployed MultiSigWallet before all state changes are finalized

// Root cause in the codebase with @> marks to highlight the relevant section
function matchRewards(address from, address to) internal {
userBalances[from] = 0; // State change
userBalances[to] = 0; // State change
// External interactions BEFORE all effects complete
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
}

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • If MultiSigWallet's constructor or receive function calls back into LikeRegistry, contract state is inconsistent

  • Reason 2

Impact:

  • Impact 1

  • Deploy a malicious MultiSigWallet that reenters likeUser()

  • Drain all ETH from the contract through recursive matching

  • Impact 2

Proof of Concept

// Malicious MultiSigWallet
contract MaliciousMultiSig {
LikeRegistry public registry;
address public accomplice;
constructor(address _from, address _to) payable {
registry = LikeRegistry(msg.sender);
accomplice = _to;
// Reenter during construction
if (address(registry).balance > 0) {
registry.likeUser{value: 1 ether}(accomplice);
}
}
receive() external payable {
// Can also reenter here
if (address(registry).balance > 1 ether) {
registry.likeUser{value: 1 ether}(accomplice);
}
}
}
// Attack scenario:
// 1. Attacker and accomplice both create profiles
// 2. Both call likeUser() with 1 ETH each (2 ETH in contract)
// 3. When matched, MaliciousMultiSig reenters and drains funds

Recommended Mitigation

- remove this codeimport "@openzeppelin/contracts/security/ReentrancyGuard.sol";
+ add this code
function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
// EFFECTS - Complete ALL state changes first
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
totalFees += matchingFees; // Update BEFORE external calls
// INTERACTIONS - External calls last
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
contract LikeRegistry is Ownable, ReentrancyGuard {
function likeUser(address liked) external payable nonReentrant {
// ... existing code
}
}
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!