When the collector calls 'collectFee()', they should receive the fees due to the 'buySnow()" function received when the users are buying snow.
But, it is actually taking the whole balance of the contrat, to transfer it to the collector, instead of determinating a separated amount between the fees and the actual balace of the contrat.
This occurs whenever the collector will call the collectFee() function.
To demonstrate this vulnerability, place the following test file into the test/TestSnow.t.sol file
function test_collectFee_drainsEverything() public {
vm.prank(ashley);
snow.buySnow{value: 2 ether}(2);
console.log("Alice buys 2 Snow (ETH)");
console.log(" Contrat ETH balance :", address(snow).balance / 1e18, "ETH");
vm.prank(victory);
snow.buySnow{value: 3 ether}(3);
console.log("Bob buys 3 Snow (ETH)");
console.log(" Contrat ETH balance :", address(snow).balance / 1e18, "ETH");
vm.prank(jerry);
snow.buySnow(2);
console.log("Charlie buys 2 Snow (WETH)");
console.log(" Contrat WETH balance :", weth.balanceOf(address(snow)) / 1e18, "WETH");
vm.prank(makeAddr("stranger"));
vm.deal(makeAddr("stranger"), 1 ether);
(bool sent,) = address(snow).call{value: 1 ether}("");
assertTrue(sent);
console.log("Stranger sent 1 ETH directly");
uint256 contractEthBefore = address(snow).balance;
uint256 contractWethBefore = weth.balanceOf(address(snow));
uint256 collectorEthBefore = address(collector).balance;
uint256 collectorWethBefore = weth.balanceOf(collector);
console.log("\n=== BEFORE collectFee ===");
console.log(" Contrat ETH :", contractEthBefore / 1e18, "ETH");
console.log(" Contrat WETH :", contractWethBefore / 1e18, "WETH");
console.log(" Collector ETH :", collectorEthBefore / 1e18, "ETH");
console.log(" Collector WETH:", collectorWethBefore / 1e18, "WETH");
vm.prank(collector);
snow.collectFee();
uint256 contractEthAfter = address(snow).balance;
uint256 contractWethAfter = weth.balanceOf(address(snow));
uint256 collectorEthAfter = address(collector).balance;
uint256 collectorWethAfter = weth.balanceOf(collector);
console.log("\n=== AFTER collectFee ===");
console.log(" Contrat ETH :", contractEthAfter / 1e18, "ETH (attendu: 0)");
console.log(" Contrat WETH :", contractWethAfter / 1e18, "WETH (attendu: 0)");
console.log(" Collector ETH :", collectorEthAfter / 1e18, "ETH");
console.log(" Collector WETH:", collectorWethAfter / 1e18, "WETH");
assertEq(contractEthAfter, 0, "Contract should have 0 ETH");
assertEq(contractWethAfter, 0, "Contract should have 0 WETH");
assertEq(
collectorEthAfter,
collectorEthBefore + contractEthBefore,
"Collector a recu tout l ETH (incl. stranger)"
);
assertEq(
collectorWethAfter,
collectorWethBefore + contractWethBefore,
"Collector received all the WETH"
);
console.log("\n[POC] collectFee() has drained 100% of contract's funds.");
console.log("[POC] Stranger's ETH also got collected.");
}
As a mitigation, create a specified amount of fees when collecting them, as exemple : feesCollected (variable) that get incremented whenever a fee is collected (ex: in buySnow).