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
:
User balances are set to zero and cannot be recovered
The matching process is permanently blocked for these users
Users cannot interact with the system anymore as their funds are locked
function matchRewards(address from, address to) internal {
userBalances[from] = 0;
userBalances[to] = 0;
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
Impact
System Unusable : Affected users cannot use the system anymore as their balances are permanently set to zero
Locked Funds : ETH remains locked in the contract with no way to recover it
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
Proof of Concept
function testDenialOfServiceOnDeploymentFailure() public {
console.log("Starting DOS test");
vm.deal(alice, 2 ether);
vm.deal(bob, 2 ether);
vm.deal(address(this), 100 ether);
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
console.log("Alice liked Bob");
address predictedAddress = computeCreateAddress(
address(likeRegistry),
vm.getNonce(address(likeRegistry))
);
bytes memory gasConsumingCode = hex"5b600080fd";
vm.etch(predictedAddress, gasConsumingCode);
console.log("Etched gas consuming code at:", predictedAddress);
vm.prank(bob);
vm.expectRevert();
likeRegistry.likeUser{value: 1 ether}(alice);
assertEq(
likeRegistry.userBalances(alice),
0,
"Alice balance should be 0"
);
assertEq(likeRegistry.userBalances(bob), 0, "Bob balance should be 0");
assertEq(
address(likeRegistry).balance,
1 ether,
"ETH should be locked"
);
vm.prank(alice);
vm.expectRevert();
likeRegistry.likeUser{value: 1 ether}(address(3));
console.log(
"DOS confirmed: System is unusable after deployment failure"
);
}
Recommendations
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;
MultiSigWallet multiSigWallet;
try new MultiSigWallet(from, to) returns (MultiSigWallet wallet) {
multiSigWallet = wallet;
} catch {
revert("MultiSig deployment failed");
}
userBalances[from] = 0;
userBalances[to] = 0;
totalFees += matchingFees;
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
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");
}
}
}
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");
delete failedMatches[user];
userBalances[user] = 1 ether;
emit MatchRecovered(user);
}
function matchRewards(address from, address to) internal {
try new MultiSigWallet(from, to) returns (MultiSigWallet wallet) {
} catch {
failedMatches[from] = true;
failedMatches[to] = true;
revert("Match failed - recovery possible");
}
}
}