Summary
In the Pot::closePot
function, the manager cut is transferred to the msg.sender
, which in this case is the ContestManager
contract itself, not the owner. However, the ContestManager
contract does not have a withdraw function, effectively locking the manager's cut in the contract without a way to retrieve it.
Impact
The manager cut is irretrievably locked in the contract, and the manager (owner) cannot access these funds since there is no withdraw function in ContestManager
.
Tools Used
Manual Review.
Proof of Concept
The following variables should be added to TestMyCut.t.sol
:
address[] testPlayers = [
address(uint160(1)),
address(uint160(2)),
address(uint160(3)),
address(uint160(4)),
address(uint160(5)),
address(uint160(6)),
address(uint160(7)),
address(uint160(8)),
address(uint160(9)),
address(uint160(10))
];
uint256[] testRewards = [
2 ether,
2 ether,
2 ether,
2 ether,
2 ether,
2 ether,
2 ether,
2 ether,
2 ether,
2 ether
];
uint256 testTotalRewards = 20 ether;
Then, add the following test to the existing tests in TestMyCut.t.sol
:
function testLockedManagerCut() public mintAndApproveTokens {
vm.startPrank(user);
ContestManager testConMan = ContestManager(conMan);
address testPot = testConMan.createContest(
testPlayers,
testRewards,
IERC20(weth),
testTotalRewards
);
testConMan.fundContest(0);
vm.stopPrank();
uint256 numberOfClaimers = 5;
for (uint i; i < numberOfClaimers; i++) {
vm.prank(testPlayers[i]);
Pot(testPot).claimCut();
}
vm.warp(block.timestamp + 100 days);
console.log(
"Owner Balance before Closure: ",
weth.balanceOf(address(user))
);
uint256 ownerBalanceBeforeClosure = weth.balanceOf(address(user));
console.log(
"ContestManager Balance before Closure: ",
weth.balanceOf(address(conMan))
);
vm.prank(user);
testConMan.closeContest(testPot);
console.log(
"Owner Balance after Closure: ",
weth.balanceOf(address(user))
);
console.log(
"ContestManager Balance after Closure: ",
weth.balanceOf(address(conMan))
);
assertNotEq(ownerBalanceBeforeClosure, weth.balanceOf(address(user)));
}
Finally run the tests with:
Recommended Mitigation
Consider adding a withdraw function to ContestManager.sol
or directly transferring the funds to owner
.
For instance a function like the following one could be added to ContestManager.sol
:
function ownerWithdrawal(address token, uint256 amount) public onlyOwner {
IERC20(token).transfer(msg.sender, amount);
}