Summary
The matchRewards()
function does not verify if the MultiSig contract was properly deployed before modifying states and sending funds. If the deployment fails, the funds are lost.
Vulnerability Details
In LikeRegistry.sol
, the matchRewards
function:
Sets the user balances to zero before verifying the deployment
Does not check if the MultiSig contract has been successfully deployed
Lacks a rollback mechanism in case of deployment failure
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;
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
Impact
Permanent Loss of Funds : If the deployment fails, the balances are already zero and the funds remain locked in the contract
No Recovery Mechanism : No way to recover the funds in case of failure
Easily Exploitable : The deployment can fail for several legitimate reasons (invalid bytecode, out of gas, etc.)
Severity: HIGH - Permanent loss of funds with high probability of occurrence
Tools Used
Proof of Concept
function testFailedDeployment() public {
vm.deal(alice, 2 ether);
vm.deal(bob, 2 ether);
vm.prank(alice);
likeRegistry.likeUser{value: 1 ether}(bob);
address predictedAddress = computeCreateAddress(
address(likeRegistry),
vm.getNonce(address(likeRegistry))
);
vm.etch(predictedAddress, hex"FF");
vm.prank(bob);
vm.expectRevert();
likeRegistry.likeUser{value: 1 ether}(alice);
assertEq(likeRegistry.userBalances(alice), 0);
assertEq(likeRegistry.userBalances(bob), 0);
assertEq(address(likeRegistry).balance, 2 ether);
uint256 size;
assembly {
size := extcodesize(predictedAddress)
}
assertEq(size, 1, "Contract should have invalid bytecode size");
}
Recommendations
Verify the deployment of the contract before modifying states:
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 = new MultiSigWallet(from, to);
uint256 size;
assembly {
size := extcodesize(address(multiSigWallet))
}
require(size > 0, "Contract deployment failed");
try MultiSigWallet(address(multiSigWallet)).owner1() returns (address owner1) {
require(owner1 == from, "Invalid MultiSig deployment");
} catch {
revert("Invalid MultiSig deployment");
}
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
userBalances[from] = 0;
userBalances[to] = 0;
totalFees += matchingFees;
}
Implement a secure deployment pattern with a factory:
contract MultiSigFactory {
event MultiSigDeployed(address indexed multiSig, address indexed owner1, address indexed owner2);
function deployMultiSig(address owner1, address owner2) external returns (address) {
MultiSigWallet multiSig = new MultiSigWallet(owner1, owner2);
require(address(multiSig).code.length > 0, "Deployment failed");
require(multiSig.owner1() == owner1, "Invalid owner1");
require(multiSig.owner2() == owner2, "Invalid owner2");
emit MultiSigDeployed(address(multiSig), owner1, owner2);
return address(multiSig);
}
}
Use a minimal proxy deployment pattern to reduce gas costs and failure risks:
contract MultiSigFactory {
address public immutable implementation;
constructor() {
implementation = address(new MultiSigWallet(address(0), address(0)));
}
function deployMultiSig(address owner1, address owner2) external returns (address) {
address proxy = Clones.clone(implementation);
MultiSigWallet(proxy).initialize(owner1, owner2);
require(MultiSigWallet(proxy).owner1() == owner1, "Invalid initialization");
return proxy;
}
}