DatingDapp

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

Reentrancy in matchRewards Allows Fund Theft

Root + Impact

Description

The matchRewards function deploys a new MultiSigWallet contract and then sends ETH to it using a low-level call. The MultiSigWallet constructor could be malicious if an attacker can influence the deployment (through CREATE2 or other means), or more importantly, if the receiving contract's receive function performs a callback. While userBalances are set to 0 before the external call (following CEI pattern), the totalFees update happens before the external call, allowing potential reentrancy to manipulate fee accounting or trigger multiple matches.

function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
totalFees += matchingFees; // State change before external call
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Send ETH to the deployed multisig wallet - EXTERNAL CALL
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}

Risk

Impact:
An attacker could potentially manipulate the contract state during the external call, leading to incorrect fee accounting or triggering additional matches. In worst case, if the contract deployment can be influenced, funds could be redirected to an attacker-controlled address.

Proof of Concept

// Attack scenario if MultiSigWallet had a malicious receive function:
contract MaliciousMultiSig {
receive() external payable {
// Reenter LikeRegistry to manipulate state
LikeRegistry(msg.sender).likeUser{value: 1 ether}(someAddress);
// Or call other functions while contract is in intermediate state
}
}

Recommended Mitigation

function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Update state AFTER deployment but BEFORE external call
totalFees += matchingFees;
// Send ETH to the deployed multisig wallet
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
// Better: Add ReentrancyGuard to likeUser function
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day 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!