// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/// @notice Returns success without delivering tokens. Used to prove that
/// balance-delta accounting, rather than an ERC-20 return value, is binding.
contract NoDeliveryMockERC20 {
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    function mint(address to, uint256 amount) external {
        balanceOf[to] += amount;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        return true;
    }

    function transfer(address, uint256) external pure returns (bool) {
        return true;
    }

    function transferFrom(address, address, uint256) external pure returns (bool) {
        return true;
    }
}

/// @notice Receives inputs exactly, but taxes every outgoing transfer. This
/// distinguishes exact receipt from exact claimant delivery.
contract OutboundFeeMockERC20 {
    uint256 public constant FEE = 1;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    error InsufficientBalance();
    error InsufficientAllowance();

    function mint(address to, uint256 amount) external {
        balanceOf[to] += amount;
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        return true;
    }

    function transfer(address to, uint256 amount) external returns (bool) {
        _transfer(msg.sender, to, amount, true);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        uint256 permitted = allowance[from][msg.sender];
        if (permitted != type(uint256).max) {
            if (permitted < amount) revert InsufficientAllowance();
            allowance[from][msg.sender] = permitted - amount;
        }
        _transfer(from, to, amount, false);
        return true;
    }

    function _transfer(address from, address to, uint256 amount, bool chargeFee) private {
        if (balanceOf[from] < amount) revert InsufficientBalance();
        balanceOf[from] -= amount;
        balanceOf[to] += chargeFee ? amount - FEE : amount;
    }
}

/// @notice Sends native currency by self-destructing in the same transaction.
contract ForceNativeDonation {
    constructor() payable { }

    function force(address payable to) external {
        selfdestruct(to);
    }
}
