all files / contracts/test/ TestERC20.sol

98.02% Statements 99/101
89.13% Branches 41/46
95.83% Functions 23/24
98.95% Lines 94/95
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271                                                                                  2097× 2097× 2097× 2097× 2097×       40× 40×       30× 30×       7061× 7061×   7045× 7045×   7037× 7037×   7029× 7029×   7021× 7016×   7016×       1318× 1318×   1318× 1318×   1316× 1316×       604×   604× 604×   602× 602×       2008× 2006× 25× 24×     2005× 2005×   2005× 2005×   2005× 2005×     2005×         2005×   2005× 1401× 1401×     1400× 1400×   1398× 1398×                   11× 11×           11×                       11× 11×                                                   1020× 1020×                     25×       49328× 762× 67×     49256×                 989× 989×                               3318×   3314×            
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;
import "hardhat/console.sol";
 
/**
    @notice Token behaviours can be set by calling configure()
    name                                    params
    balance-of/consume-all-gas                                              Consume all gas on balanceOf
    balance-of/set-amount                   uint amount                     Always return set amount on balanceOf
    balance-of/revert                                                       Revert on balanceOf
    balance-of/panic                                                        Panic on balanceOf
    approve/return-void                                                     Return nothing instead of bool
    approve/revert                                                          Revert on approve
    transfer/return-void                                                    Return nothing instead of bool
    transfer-from/return-void                                               Return nothing instead of bool
    transfer/deflationary                   uint deflate                    Make the transfer and transferFrom decrease recipient amount by deflate
    transfer/inflationary                   uint inflate                    Make the transfer and transferFrom increase recipient amount by inflate
    transfer/underflow                                                      Transfer increases sender balance by transfer amount
    transfer/revert                                                         Revert on transfer
    transfer-from/revert                                                    Revert on transferFrom
    transfer-from/call                      uint address, bytes calldata    Makes an external call on transferFrom
    name/return-bytes32                                                     Returns bytes32 instead of string
    symbol/return-bytes32                                                   Returns bytes32 instead of string
    permit/allowed                                                          Switch permit type to DAI-like 'allowed'
*/
 
contract TestERC20 {
    address owner;
    string _name;
    string _symbol;
    uint8 public decimals;
    uint256 public totalSupply;
    bool secureMode;
 
    mapping(address => uint256) public balances;
    mapping(address => mapping(address => uint256)) public allowance;
 
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);
 
    constructor(string memory name_, string memory symbol_, uint8 decimals_, bool secureMode_) {
        owner = msg.sender;
        _name = name_;
        _symbol = symbol_;
        decimals = decimals_;
        secureMode = secureMode_;
    }
 
    function name() public view returns (string memory n) {
        (bool isSet,) = behaviour("name/return-bytes32");
        if (!isSet) return _name;
        doReturn(false, bytes32(abi.encodePacked(_name)));
    }
 
    function symbol() public view returns (string memory s) {
        (bool isSet,) = behaviour("symbol/return-bytes32");
        if (!isSet) return _symbol;
        doReturn(false, bytes32(abi.encodePacked(_symbol)));
    }
 
    function balanceOf(address account) public view returns (uint) {
        (bool isSet, bytes memory data) = behaviour("balance-of/set-amount");
        if(isSet) return abi.decode(data, (uint));
 
        (isSet,) = behaviour("balance-of/consume-all-gas");
        if(isSet) consumeAllGas();
 
        (isSet,) = behaviour("balance-of/revert");
        if(isSet) revert("revert behaviour");
 
        (isSet,) = behaviour("balance-of/panic");
        if(isSet) assert(false);
 
        (isSet,) = behaviour("balance-of/max-value"); 
        Iif(isSet) return type(uint).max;
        
        return balances[account];
    }
 
    function approve(address spender, uint256 amount) external {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
 
        (bool isSet,) = behaviour("approve/revert");
        if(isSet) revert("revert behaviour");
 
        (isSet,) = behaviour("approve/return-void");
        doReturn(isSet, bytes32(uint(1)));
    }
 
    function transfer(address recipient, uint256 amount) external {
        transferFrom(msg.sender, recipient, amount);
 
        (bool isSet,) = behaviour("transfer/revert");
        if(isSet) revert("revert behaviour");
 
        (isSet,) = behaviour("transfer/return-void");
        doReturn(isSet, bytes32(uint(1)));
    }
 
    function transferFrom(address from, address recipient, uint256 amount) public {
        require(balances[from] >= amount, "ERC20: transfer amount exceeds balance");
        if (from != msg.sender && allowance[from][msg.sender] != type(uint256).max) {
            require(allowance[from][msg.sender] >= amount, "ERC20: transfer amount exceeds allowance");
            allowance[from][msg.sender] -= amount;
        }
 
        (bool isSet, bytes memory data) = behaviour("transfer/deflationary");
        uint deflate = isSet ? abi.decode(data, (uint)) : 0;
 
        (isSet, data) = behaviour("transfer/inflationary");
        uint inflate = isSet ? abi.decode(data, (uint)) : 0;
 
        (isSet,) = behaviour("transfer/underflow");
        if(isSet) {
            balances[from] += amount * 2;
        }
 
        unchecked {
            balances[from] -= amount;
            balances[recipient] += amount - deflate + inflate;
        }
 
        emit Transfer(from, recipient, amount);
 
        if(msg.sig == this.transferFrom.selector) {
            (isSet, data) = behaviour("transfer-from/call");
            if(isSet) {
                (address _address, bytes memory _calldata) = abi.decode(data, (address, bytes));
                (bool success, bytes memory ret) = _address.call(_calldata);
                Eif(!success) revert(string(ret));
            }
 
            (isSet,) = behaviour("transfer-from/revert");
            if(isSet) revert("revert behaviour");
 
            (isSet,) = behaviour("transfer-from/return-void");
            doReturn(isSet, bytes32(uint(1)));
        }
    }
 
    bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
 
    mapping(address => uint) public nonces;
    string _version = "1"; // ERC20Permit.sol hardcodes its version to "1" by passing it into EIP712 constructor
 
    function _getChainId() private view returns (uint256 chainId) {
        this; 
        assembly {
            chainId := chainid()
        }
    }
 
    function DOMAIN_SEPARATOR() public view returns (bytes32) {
        return keccak256(
            abi.encode(
                DOMAIN_TYPEHASH,
                keccak256(bytes(_name)),
                keccak256(bytes(_version)),
                _getChainId(),
                address(this)
            )
        );
    }
 
    function PERMIT_TYPEHASH() public view returns (bytes32) {
        (bool isSet,) = behaviour("permit/allowed");
        return isSet
            ? keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)")
            : keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    }
 
    // EIP2612
    function permit(address holder, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH(), holder, spender, value, nonces[holder]++, deadline));
        applyPermit(structHash, holder, spender, value, deadline, v, r, s);
    }
 
    // allowed type
    function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external {
        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH(), holder, spender, nonce, expiry, allowed));
        uint value = allowed ? type(uint).max : 0;
 
        nonces[holder]++;
        applyPermit(structHash, holder, spender, value, expiry, v, r, s);
    }
 
    // packed type
    function permit(address holder, address spender, uint value, uint deadline, bytes calldata signature) external {
        bytes32 r = bytes32(signature[0 : 32]);
        bytes32 s = bytes32(signature[32 : 64]);
        uint8 v = uint8(uint(bytes32(signature[64 : 65]) >> 248));
        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH(), holder, spender, value, nonces[holder]++, deadline));
        applyPermit(structHash, holder, spender, value, deadline, v, r, s);
    }
 
 
    function applyPermit(bytes32 structHash, address holder, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) internal {
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), structHash));
        address signatory = ecrecover(digest, v, r, s);
        Erequire(signatory != address(0), "permit: invalid signature");
        require(signatory == holder, "permit: unauthorized");
        Erequire(block.timestamp <= deadline, "permit: signature expired");
 
        allowance[holder][spender] = value;
 
        emit Approval(holder, spender, value);
    }
    // Custom testing method
 
    modifier secured() {
        Erequire(!secureMode || msg.sender == owner, "TestERC20: secure mode enabled");
        _;
    }
 
    struct Config {
        string name;
        bytes data;
    }
 
    Config[] config;
 
    function configure(string calldata name_, bytes calldata data_) external secured {
        config.push(Config(name_, data_));
    }
 
    function behaviour(string memory name_) public view returns(bool, bytes memory) {
        for (uint i = 0; i < config.length; ++i) {
            if (keccak256(abi.encode(config[i].name)) == keccak256(abi.encode(name_))) {
                return (true, config[i].data);
            }
        }
        return (false, "");
    }
 
 
    function changeOwner(address newOwner) external secured {
        owner = newOwner;
    }
 
    function mint(address who, uint amount) external secured {
        balances[who] += amount;
        emit Transfer(address(0), who, amount);
    }
 
    function setBalance(address who, uint newBalance) external secured {
        balances[who] = newBalance;
    }
 
    function changeDecimals(uint8 decimals_) external secured {
        decimals = decimals_;
    }
 
    function callSelfDestruct() external secured {
        selfdestruct(payable(address(0)));
    }
 
    function consumeAllGas() internal pure {
        for (; true;) {}
    }
 
    function doReturn(bool returnVoid, bytes32 data) internal pure {
        if (returnVoid) return;
 
        assembly {
            mstore(mload(0x40), data)
            return(mload(0x40), 0x20)
        }
    }
}