DatingDapp

First Flight #33
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Invalid

Denial of Service Risk Due to Failed MultiSig Deployment

Summary

The matchRewards() function's failure to handle MultiSig deployment errors can lead to a Denial of Service (DoS) condition, making the system unusable for affected users.

Vulnerability Details

In LikeRegistry.sol, when the MultiSig deployment fails in matchRewards:

  1. User balances are set to zero and cannot be recovered

  2. The matching process is permanently blocked for these users

  3. Users cannot interact with the system anymore as their funds are locked

function matchRewards(address from, address to) internal {
// Balances are set to zero before deployment
userBalances[from] = 0;
userBalances[to] = 0;
// If this deployment fails, the function reverts
// but the balances are already set to zero
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Users are now in an inconsistent state
// and cannot use the system anymore
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}

Impact

  1. System Unusable : Affected users cannot use the system anymore as their balances are permanently set to zero

  2. Locked Funds : ETH remains locked in the contract with no way to recover it

  3. Cascading Effect : Other users trying to match with affected users will also be impacted

Severity: HIGH - Complete denial of service for affected users with permanent fund loss

Tools Used

  • Foundry for testing

  • Manual code review

  • PoC demonstrating the DOS vulnerability

Proof of Concept

function testDenialOfServiceOnDeploymentFailure() public {
console.log("Starting DOS test");
// Setup initial state
vm.deal(alice, 2 ether);
vm.deal(bob, 2 ether);
vm.deal(address(this), 100 ether);
// Alice likes Bob
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
console.log("Alice liked Bob");
// Force the next deployment to consume all gas
address predictedAddress = computeCreateAddress(
address(likeRegistry),
vm.getNonce(address(likeRegistry))
);
// We deploy a contract that consumes all gas during creation
bytes memory gasConsumingCode = hex"5b600080fd"; // Code that loops indefinitely
vm.etch(predictedAddress, gasConsumingCode);
console.log("Etched gas consuming code at:", predictedAddress);
// Bob likes Alice, triggering matchRewards which will fail
vm.prank(bob);
vm.expectRevert();
likeRegistry.likeUser{value: 1 ether}(alice);
// The balances are stuck at 0
assertEq(
likeRegistry.userBalances(alice),
0,
"Alice balance should be 0"
);
assertEq(likeRegistry.userBalances(bob), 0, "Bob balance should be 0");
// The ETH is locked in the contract (only the first like because the second one reverts)
assertEq(
address(likeRegistry).balance,
1 ether,
"ETH should be locked"
);
// Now Alice cannot use the system anymore
vm.prank(alice);
vm.expectRevert();
likeRegistry.likeUser{value: 1 ether}(address(3));
console.log(
"DOS confirmed: System is unusable after deployment failure"
);
}

Recommendations

  1. Implement a try-catch pattern for deployment:

function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
// Use try-catch for deployment
MultiSigWallet multiSigWallet;
try new MultiSigWallet(from, to) returns (MultiSigWallet wallet) {
multiSigWallet = wallet;
} catch {
revert("MultiSig deployment failed");
}
// Only update states after successful deployment
userBalances[from] = 0;
userBalances[to] = 0;
totalFees += matchingFees;
// Transfer ETH
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
  1. Use a factory pattern with proper error handling:

contract MultiSigFactory {
function deployMultiSig(address owner1, address owner2) external returns (address) {
try new MultiSigWallet(owner1, owner2) returns (MultiSigWallet wallet) {
require(address(wallet).code.length > 0, "Deployment failed");
return address(wallet);
} catch Error(string memory reason) {
revert(string.concat("Deployment failed: ", reason));
} catch {
revert("Deployment failed with unknown error");
}
}
}
  1. Implement a recovery mechanism:

contract LikeRegistry {
mapping(address => bool) public failedMatches;
function recoverFailedMatch(address user) external {
require(failedMatches[user], "No failed match to recover");
require(userBalances[user] == 0, "Invalid state");
// Reset user state
delete failedMatches[user];
userBalances[user] = 1 ether; // Original amount
emit MatchRecovered(user);
}
function matchRewards(address from, address to) internal {
try new MultiSigWallet(from, to) returns (MultiSigWallet wallet) {
// ... normal flow ...
} catch {
failedMatches[from] = true;
failedMatches[to] = true;
revert("Match failed - recovery possible");
}
}
}
Updates

Appeal created

n0kto Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

invalid_deployent_multisig_could_fail

No valid reason for the deployment to fail.

Support

FAQs

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