├── .gas-snapshot ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ └── tests.yml ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── audits └── v6-Fixed-Point-Solutions.pdf ├── foundry.toml ├── package-lock.json ├── package.json └── src ├── auth ├── Auth.sol ├── Owned.sol └── authorities │ ├── MultiRolesAuthority.sol │ └── RolesAuthority.sol ├── test ├── Auth.t.sol ├── Bytes32AddressLib.t.sol ├── CREATE3.t.sol ├── DSTestPlus.t.sol ├── ERC1155.t.sol ├── ERC20.t.sol ├── ERC4626.t.sol ├── ERC6909.t.sol ├── ERC721.t.sol ├── FixedPointMathLib.t.sol ├── LibString.t.sol ├── MerkleProofLib.t.sol ├── MultiRolesAuthority.t.sol ├── Owned.t.sol ├── ReentrancyGuard.t.sol ├── RolesAuthority.t.sol ├── SSTORE2.t.sol ├── SafeCastLib.t.sol ├── SafeTransferLib.t.sol ├── SignedWadMath.t.sol ├── WETH.t.sol └── utils │ ├── DSInvariantTest.sol │ ├── DSTestPlus.sol │ ├── Hevm.sol │ ├── mocks │ ├── MockAuthChild.sol │ ├── MockAuthority.sol │ ├── MockERC1155.sol │ ├── MockERC20.sol │ ├── MockERC4626.sol │ ├── MockERC6909.sol │ ├── MockERC721.sol │ └── MockOwned.sol │ └── weird-tokens │ ├── MissingReturnToken.sol │ ├── ReturnsFalseToken.sol │ ├── ReturnsGarbageToken.sol │ ├── ReturnsTooLittleToken.sol │ ├── ReturnsTooMuchToken.sol │ ├── ReturnsTwoToken.sol │ └── RevertingToken.sol ├── tokens ├── ERC1155.sol ├── ERC20.sol ├── ERC4626.sol ├── ERC6909.sol ├── ERC721.sol └── WETH.sol └── utils ├── Bytes32AddressLib.sol ├── CREATE3.sol ├── FixedPointMathLib.sol ├── LibString.sol ├── MerkleProofLib.sol ├── ReentrancyGuard.sol ├── SSTORE2.sol ├── SafeCastLib.sol ├── SafeTransferLib.sol └── SignedWadMath.sol /.gitattributes: -------------------------------------------------------------------------------- 1 | .gas-snapshot linguist-language=Julia -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Describe the changes made in your pull request here. 4 | 5 | ## Checklist 6 | 7 | Ensure you completed **all of the steps** below before submitting your pull request: 8 | 9 | - [ ] Ran `forge snapshot`? 10 | - [ ] Ran `npm run lint`? 11 | - [ ] Ran `forge test`? 12 | 13 | _Pull requests with an incomplete checklist will be thrown out._ 14 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Install Foundry 12 | uses: onbjerg/foundry-toolchain@v1 13 | with: 14 | version: nightly 15 | 16 | - name: Install dependencies 17 | run: forge install 18 | 19 | - name: Check contract sizes 20 | run: forge build --sizes 21 | 22 | - name: Check gas snapshots 23 | run: forge snapshot --check 24 | 25 | - name: Run tests 26 | run: forge test 27 | env: 28 | # Only fuzz intensely if we're running this action on a push to main or for a PR going into main: 29 | FOUNDRY_PROFILE: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && 'intense' }} 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /cache 2 | /node_modules 3 | /out -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 100, 4 | 5 | "overrides": [ 6 | { 7 | "files": "*.sol", 8 | "options": { 9 | "tabWidth": 4, 10 | "printWidth": 120 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.packageDefaultDependenciesContractsDirectory": "src", 3 | "solidity.packageDefaultDependenciesDirectory": "lib", 4 | "solidity.compileUsingRemoteVersion": "v0.8.15", 5 | "search.exclude": { "lib": true }, 6 | "files.associations": { 7 | ".gas-snapshot": "julia" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solmate 2 | 3 | **Modern**, **opinionated**, and **gas optimized** building blocks for **smart contract development**. 4 | 5 | ## Contracts 6 | 7 | ```ml 8 | auth 9 | ├─ Owned — "Simple single owner authorization" 10 | ├─ Auth — "Flexible and updatable auth pattern" 11 | ├─ authorities 12 | │ ├─ RolesAuthority — "Role based Authority that supports up to 256 roles" 13 | │ ├─ MultiRolesAuthority — "Flexible and target agnostic role based Authority" 14 | tokens 15 | ├─ WETH — "Minimalist and modern Wrapped Ether implementation" 16 | ├─ ERC20 — "Modern and gas efficient ERC20 + EIP-2612 implementation" 17 | ├─ ERC721 — "Modern, minimalist, and gas efficient ERC721 implementation" 18 | ├─ ERC1155 — "Minimalist and gas efficient standard ERC1155 implementation" 19 | ├─ ERC4626 — "Minimal ERC4626 tokenized Vault implementation" 20 | ├─ ERC6909 — "Minimalist and gas efficient standard ERC6909 implementation" 21 | utils 22 | ├─ SSTORE2 — "Library for cheaper reads and writes to persistent storage" 23 | ├─ CREATE3 — "Deploy to deterministic addresses without an initcode factor" 24 | ├─ LibString — "Library for creating string representations of uint values" 25 | ├─ SafeCastLib — "Safe unsigned integer casting lib that reverts on overflow" 26 | ├─ SignedWadMath — "Signed integer 18 decimal fixed point arithmetic library" 27 | ├─ MerkleProofLib — "Efficient merkle tree inclusion proof verification library" 28 | ├─ ReentrancyGuard — "Gas optimized reentrancy protection for smart contracts" 29 | ├─ FixedPointMathLib — "Arithmetic library with operations for fixed-point numbers" 30 | ├─ Bytes32AddressLib — "Library for converting between addresses and bytes32 values" 31 | ├─ SafeTransferLib — "Safe ERC20/ETH transfer lib that handles missing return values" 32 | ``` 33 | 34 | ## Safety 35 | 36 | This is **experimental software** and is provided on an "as is" and "as available" basis. 37 | 38 | While each [major release has been audited](audits), these contracts are **not designed with user safety** in mind: 39 | 40 | - There are implicit invariants these contracts expect to hold. 41 | - **You can easily shoot yourself in the foot if you're not careful.** 42 | - You should thoroughly read each contract you plan to use top to bottom. 43 | 44 | We **do not give any warranties** and **will not be liable for any loss** incurred through any use of this codebase. 45 | 46 | ## Installation 47 | 48 | To install with [**Foundry**](https://github.com/gakonst/foundry): 49 | 50 | ```sh 51 | forge install transmissions11/solmate 52 | ``` 53 | 54 | To install with [**Hardhat**](https://github.com/nomiclabs/hardhat) or [**Truffle**](https://github.com/trufflesuite/truffle): 55 | 56 | ```sh 57 | npm install solmate 58 | ``` 59 | 60 | ## Acknowledgements 61 | 62 | These contracts were inspired by or directly modified from many sources, primarily: 63 | 64 | - [Gnosis](https://github.com/gnosis/gp-v2-contracts) 65 | - [Uniswap](https://github.com/Uniswap/uniswap-lib) 66 | - [Dappsys](https://github.com/dapphub/dappsys) 67 | - [Dappsys V2](https://github.com/dapp-org/dappsys-v2) 68 | - [0xSequence](https://github.com/0xSequence) 69 | - [OpenZeppelin](https://github.com/OpenZeppelin/openzeppelin-contracts) 70 | -------------------------------------------------------------------------------- /audits/v6-Fixed-Point-Solutions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transmissions11/solmate/c93f7716c9909175d45f6ef80a34a650e2d24e56/audits/v6-Fixed-Point-Solutions.pdf -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | solc = "0.8.15" 3 | bytecode_hash = "none" 4 | optimizer_runs = 1000000 5 | 6 | [profile.intense.fuzz] 7 | runs = 10000 8 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solmate", 3 | "version": "6.8.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@solidity-parser/parser": { 8 | "version": "0.13.2", 9 | "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.13.2.tgz", 10 | "integrity": "sha512-RwHnpRnfrnD2MSPveYoPh8nhofEvX7fgjHk1Oq+NNvCcLx4r1js91CO9o+F/F3fBzOCyvm8kKRTriFICX/odWw==", 11 | "dev": true, 12 | "requires": { 13 | "antlr4ts": "^0.5.0-alpha.4" 14 | } 15 | }, 16 | "ansi-regex": { 17 | "version": "5.0.0", 18 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 19 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 20 | "dev": true 21 | }, 22 | "antlr4ts": { 23 | "version": "0.5.0-alpha.4", 24 | "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", 25 | "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", 26 | "dev": true 27 | }, 28 | "emoji-regex": { 29 | "version": "9.2.2", 30 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 31 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 32 | "dev": true 33 | }, 34 | "escape-string-regexp": { 35 | "version": "4.0.0", 36 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 37 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 38 | "dev": true 39 | }, 40 | "is-fullwidth-code-point": { 41 | "version": "3.0.0", 42 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 43 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 44 | "dev": true 45 | }, 46 | "lru-cache": { 47 | "version": "6.0.0", 48 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 49 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 50 | "dev": true, 51 | "requires": { 52 | "yallist": "^4.0.0" 53 | } 54 | }, 55 | "prettier": { 56 | "version": "2.3.2", 57 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", 58 | "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", 59 | "dev": true 60 | }, 61 | "prettier-plugin-solidity": { 62 | "version": "1.0.0-beta.16", 63 | "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.16.tgz", 64 | "integrity": "sha512-xVBcnoWpe52dNnCCbqPHC9ZrTWXcNfldf852ZD0DBcHDqVMwjHTAPEdfBVy6FczbFpVa8bmxQil+G5XkEz5WHA==", 65 | "dev": true, 66 | "requires": { 67 | "@solidity-parser/parser": "^0.13.2", 68 | "emoji-regex": "^9.2.2", 69 | "escape-string-regexp": "^4.0.0", 70 | "semver": "^7.3.5", 71 | "solidity-comments-extractor": "^0.0.7", 72 | "string-width": "^4.2.2" 73 | } 74 | }, 75 | "semver": { 76 | "version": "7.3.5", 77 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 78 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 79 | "dev": true, 80 | "requires": { 81 | "lru-cache": "^6.0.0" 82 | } 83 | }, 84 | "solidity-comments-extractor": { 85 | "version": "0.0.7", 86 | "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", 87 | "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", 88 | "dev": true 89 | }, 90 | "string-width": { 91 | "version": "4.2.2", 92 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", 93 | "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", 94 | "dev": true, 95 | "requires": { 96 | "emoji-regex": "^8.0.0", 97 | "is-fullwidth-code-point": "^3.0.0", 98 | "strip-ansi": "^6.0.0" 99 | }, 100 | "dependencies": { 101 | "emoji-regex": { 102 | "version": "8.0.0", 103 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 104 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 105 | "dev": true 106 | } 107 | } 108 | }, 109 | "strip-ansi": { 110 | "version": "6.0.0", 111 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 112 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 113 | "dev": true, 114 | "requires": { 115 | "ansi-regex": "^5.0.0" 116 | } 117 | }, 118 | "yallist": { 119 | "version": "4.0.0", 120 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 121 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 122 | "dev": true 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solmate", 3 | "license": "AGPL-3.0-only", 4 | "version": "6.8.0", 5 | "description": "Modern, opinionated and gas optimized building blocks for smart contract development.", 6 | "files": [ 7 | "src/**/*.sol" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/transmissions11/solmate.git" 12 | }, 13 | "devDependencies": { 14 | "prettier": "^2.3.1", 15 | "prettier-plugin-solidity": "^1.0.0-beta.13" 16 | }, 17 | "scripts": { 18 | "lint": "prettier --write **.sol" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/auth/Auth.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Provides a flexible and updatable auth pattern which is completely separate from application logic. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol) 6 | /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) 7 | abstract contract Auth { 8 | event OwnershipTransferred(address indexed user, address indexed newOwner); 9 | 10 | event AuthorityUpdated(address indexed user, Authority indexed newAuthority); 11 | 12 | address public owner; 13 | 14 | Authority public authority; 15 | 16 | constructor(address _owner, Authority _authority) { 17 | owner = _owner; 18 | authority = _authority; 19 | 20 | emit OwnershipTransferred(msg.sender, _owner); 21 | emit AuthorityUpdated(msg.sender, _authority); 22 | } 23 | 24 | modifier requiresAuth() virtual { 25 | require(isAuthorized(msg.sender, msg.sig), "UNAUTHORIZED"); 26 | 27 | _; 28 | } 29 | 30 | function isAuthorized(address user, bytes4 functionSig) internal view virtual returns (bool) { 31 | Authority auth = authority; // Memoizing authority saves us a warm SLOAD, around 100 gas. 32 | 33 | // Checking if the caller is the owner only after calling the authority saves gas in most cases, but be 34 | // aware that this makes protected functions uncallable even to the owner if the authority is out of order. 35 | return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) || user == owner; 36 | } 37 | 38 | function setAuthority(Authority newAuthority) public virtual { 39 | // We check if the caller is the owner first because we want to ensure they can 40 | // always swap out the authority even if it's reverting or using up a lot of gas. 41 | require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig)); 42 | 43 | authority = newAuthority; 44 | 45 | emit AuthorityUpdated(msg.sender, newAuthority); 46 | } 47 | 48 | function transferOwnership(address newOwner) public virtual requiresAuth { 49 | owner = newOwner; 50 | 51 | emit OwnershipTransferred(msg.sender, newOwner); 52 | } 53 | } 54 | 55 | /// @notice A generic interface for a contract which provides authorization data to an Auth instance. 56 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Auth.sol) 57 | /// @author Modified from Dappsys (https://github.com/dapphub/ds-auth/blob/master/src/auth.sol) 58 | interface Authority { 59 | function canCall( 60 | address user, 61 | address target, 62 | bytes4 functionSig 63 | ) external view returns (bool); 64 | } 65 | -------------------------------------------------------------------------------- /src/auth/Owned.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Simple single owner authorization mixin. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) 6 | abstract contract Owned { 7 | /*////////////////////////////////////////////////////////////// 8 | EVENTS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | event OwnershipTransferred(address indexed user, address indexed newOwner); 12 | 13 | /*////////////////////////////////////////////////////////////// 14 | OWNERSHIP STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | address public owner; 18 | 19 | modifier onlyOwner() virtual { 20 | require(msg.sender == owner, "UNAUTHORIZED"); 21 | 22 | _; 23 | } 24 | 25 | /*////////////////////////////////////////////////////////////// 26 | CONSTRUCTOR 27 | //////////////////////////////////////////////////////////////*/ 28 | 29 | constructor(address _owner) { 30 | owner = _owner; 31 | 32 | emit OwnershipTransferred(address(0), _owner); 33 | } 34 | 35 | /*////////////////////////////////////////////////////////////// 36 | OWNERSHIP LOGIC 37 | //////////////////////////////////////////////////////////////*/ 38 | 39 | function transferOwnership(address newOwner) public virtual onlyOwner { 40 | owner = newOwner; 41 | 42 | emit OwnershipTransferred(msg.sender, newOwner); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/auth/authorities/MultiRolesAuthority.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {Auth, Authority} from "../Auth.sol"; 5 | 6 | /// @notice Flexible and target agnostic role based Authority that supports up to 256 roles. 7 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/MultiRolesAuthority.sol) 8 | contract MultiRolesAuthority is Auth, Authority { 9 | /*////////////////////////////////////////////////////////////// 10 | EVENTS 11 | //////////////////////////////////////////////////////////////*/ 12 | 13 | event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled); 14 | 15 | event PublicCapabilityUpdated(bytes4 indexed functionSig, bool enabled); 16 | 17 | event RoleCapabilityUpdated(uint8 indexed role, bytes4 indexed functionSig, bool enabled); 18 | 19 | event TargetCustomAuthorityUpdated(address indexed target, Authority indexed authority); 20 | 21 | /*////////////////////////////////////////////////////////////// 22 | CONSTRUCTOR 23 | //////////////////////////////////////////////////////////////*/ 24 | 25 | constructor(address _owner, Authority _authority) Auth(_owner, _authority) {} 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | CUSTOM TARGET AUTHORITY STORAGE 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | mapping(address => Authority) public getTargetCustomAuthority; 32 | 33 | /*////////////////////////////////////////////////////////////// 34 | ROLE/USER STORAGE 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | mapping(address => bytes32) public getUserRoles; 38 | 39 | mapping(bytes4 => bool) public isCapabilityPublic; 40 | 41 | mapping(bytes4 => bytes32) public getRolesWithCapability; 42 | 43 | function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) { 44 | return (uint256(getUserRoles[user]) >> role) & 1 != 0; 45 | } 46 | 47 | function doesRoleHaveCapability(uint8 role, bytes4 functionSig) public view virtual returns (bool) { 48 | return (uint256(getRolesWithCapability[functionSig]) >> role) & 1 != 0; 49 | } 50 | 51 | /*////////////////////////////////////////////////////////////// 52 | AUTHORIZATION LOGIC 53 | //////////////////////////////////////////////////////////////*/ 54 | 55 | function canCall( 56 | address user, 57 | address target, 58 | bytes4 functionSig 59 | ) public view virtual override returns (bool) { 60 | Authority customAuthority = getTargetCustomAuthority[target]; 61 | 62 | if (address(customAuthority) != address(0)) return customAuthority.canCall(user, target, functionSig); 63 | 64 | return 65 | isCapabilityPublic[functionSig] || bytes32(0) != getUserRoles[user] & getRolesWithCapability[functionSig]; 66 | } 67 | 68 | /*/////////////////////////////////////////////////////////////// 69 | CUSTOM TARGET AUTHORITY CONFIGURATION LOGIC 70 | //////////////////////////////////////////////////////////////*/ 71 | 72 | function setTargetCustomAuthority(address target, Authority customAuthority) public virtual requiresAuth { 73 | getTargetCustomAuthority[target] = customAuthority; 74 | 75 | emit TargetCustomAuthorityUpdated(target, customAuthority); 76 | } 77 | 78 | /*////////////////////////////////////////////////////////////// 79 | PUBLIC CAPABILITY CONFIGURATION LOGIC 80 | //////////////////////////////////////////////////////////////*/ 81 | 82 | function setPublicCapability(bytes4 functionSig, bool enabled) public virtual requiresAuth { 83 | isCapabilityPublic[functionSig] = enabled; 84 | 85 | emit PublicCapabilityUpdated(functionSig, enabled); 86 | } 87 | 88 | /*////////////////////////////////////////////////////////////// 89 | USER ROLE ASSIGNMENT LOGIC 90 | //////////////////////////////////////////////////////////////*/ 91 | 92 | function setUserRole( 93 | address user, 94 | uint8 role, 95 | bool enabled 96 | ) public virtual requiresAuth { 97 | if (enabled) { 98 | getUserRoles[user] |= bytes32(1 << role); 99 | } else { 100 | getUserRoles[user] &= ~bytes32(1 << role); 101 | } 102 | 103 | emit UserRoleUpdated(user, role, enabled); 104 | } 105 | 106 | /*////////////////////////////////////////////////////////////// 107 | ROLE CAPABILITY CONFIGURATION LOGIC 108 | //////////////////////////////////////////////////////////////*/ 109 | 110 | function setRoleCapability( 111 | uint8 role, 112 | bytes4 functionSig, 113 | bool enabled 114 | ) public virtual requiresAuth { 115 | if (enabled) { 116 | getRolesWithCapability[functionSig] |= bytes32(1 << role); 117 | } else { 118 | getRolesWithCapability[functionSig] &= ~bytes32(1 << role); 119 | } 120 | 121 | emit RoleCapabilityUpdated(role, functionSig, enabled); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/auth/authorities/RolesAuthority.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {Auth, Authority} from "../Auth.sol"; 5 | 6 | /// @notice Role based Authority that supports up to 256 roles. 7 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/authorities/RolesAuthority.sol) 8 | /// @author Modified from Dappsys (https://github.com/dapphub/ds-roles/blob/master/src/roles.sol) 9 | contract RolesAuthority is Auth, Authority { 10 | /*////////////////////////////////////////////////////////////// 11 | EVENTS 12 | //////////////////////////////////////////////////////////////*/ 13 | 14 | event UserRoleUpdated(address indexed user, uint8 indexed role, bool enabled); 15 | 16 | event PublicCapabilityUpdated(address indexed target, bytes4 indexed functionSig, bool enabled); 17 | 18 | event RoleCapabilityUpdated(uint8 indexed role, address indexed target, bytes4 indexed functionSig, bool enabled); 19 | 20 | /*////////////////////////////////////////////////////////////// 21 | CONSTRUCTOR 22 | //////////////////////////////////////////////////////////////*/ 23 | 24 | constructor(address _owner, Authority _authority) Auth(_owner, _authority) {} 25 | 26 | /*////////////////////////////////////////////////////////////// 27 | ROLE/USER STORAGE 28 | //////////////////////////////////////////////////////////////*/ 29 | 30 | mapping(address => bytes32) public getUserRoles; 31 | 32 | mapping(address => mapping(bytes4 => bool)) public isCapabilityPublic; 33 | 34 | mapping(address => mapping(bytes4 => bytes32)) public getRolesWithCapability; 35 | 36 | function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) { 37 | return (uint256(getUserRoles[user]) >> role) & 1 != 0; 38 | } 39 | 40 | function doesRoleHaveCapability( 41 | uint8 role, 42 | address target, 43 | bytes4 functionSig 44 | ) public view virtual returns (bool) { 45 | return (uint256(getRolesWithCapability[target][functionSig]) >> role) & 1 != 0; 46 | } 47 | 48 | /*////////////////////////////////////////////////////////////// 49 | AUTHORIZATION LOGIC 50 | //////////////////////////////////////////////////////////////*/ 51 | 52 | function canCall( 53 | address user, 54 | address target, 55 | bytes4 functionSig 56 | ) public view virtual override returns (bool) { 57 | return 58 | isCapabilityPublic[target][functionSig] || 59 | bytes32(0) != getUserRoles[user] & getRolesWithCapability[target][functionSig]; 60 | } 61 | 62 | /*////////////////////////////////////////////////////////////// 63 | ROLE CAPABILITY CONFIGURATION LOGIC 64 | //////////////////////////////////////////////////////////////*/ 65 | 66 | function setPublicCapability( 67 | address target, 68 | bytes4 functionSig, 69 | bool enabled 70 | ) public virtual requiresAuth { 71 | isCapabilityPublic[target][functionSig] = enabled; 72 | 73 | emit PublicCapabilityUpdated(target, functionSig, enabled); 74 | } 75 | 76 | function setRoleCapability( 77 | uint8 role, 78 | address target, 79 | bytes4 functionSig, 80 | bool enabled 81 | ) public virtual requiresAuth { 82 | if (enabled) { 83 | getRolesWithCapability[target][functionSig] |= bytes32(1 << role); 84 | } else { 85 | getRolesWithCapability[target][functionSig] &= ~bytes32(1 << role); 86 | } 87 | 88 | emit RoleCapabilityUpdated(role, target, functionSig, enabled); 89 | } 90 | 91 | /*////////////////////////////////////////////////////////////// 92 | USER ROLE ASSIGNMENT LOGIC 93 | //////////////////////////////////////////////////////////////*/ 94 | 95 | function setUserRole( 96 | address user, 97 | uint8 role, 98 | bool enabled 99 | ) public virtual requiresAuth { 100 | if (enabled) { 101 | getUserRoles[user] |= bytes32(1 << role); 102 | } else { 103 | getUserRoles[user] &= ~bytes32(1 << role); 104 | } 105 | 106 | emit UserRoleUpdated(user, role, enabled); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/Auth.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | import {MockAuthChild} from "./utils/mocks/MockAuthChild.sol"; 6 | import {MockAuthority} from "./utils/mocks/MockAuthority.sol"; 7 | 8 | import {Authority} from "../auth/Auth.sol"; 9 | 10 | contract OutOfOrderAuthority is Authority { 11 | function canCall( 12 | address, 13 | address, 14 | bytes4 15 | ) public pure override returns (bool) { 16 | revert("OUT_OF_ORDER"); 17 | } 18 | } 19 | 20 | contract AuthTest is DSTestPlus { 21 | MockAuthChild mockAuthChild; 22 | 23 | function setUp() public { 24 | mockAuthChild = new MockAuthChild(); 25 | } 26 | 27 | function testTransferOwnershipAsOwner() public { 28 | mockAuthChild.transferOwnership(address(0xBEEF)); 29 | assertEq(mockAuthChild.owner(), address(0xBEEF)); 30 | } 31 | 32 | function testSetAuthorityAsOwner() public { 33 | mockAuthChild.setAuthority(Authority(address(0xBEEF))); 34 | assertEq(address(mockAuthChild.authority()), address(0xBEEF)); 35 | } 36 | 37 | function testCallFunctionAsOwner() public { 38 | mockAuthChild.updateFlag(); 39 | } 40 | 41 | function testTransferOwnershipWithPermissiveAuthority() public { 42 | mockAuthChild.setAuthority(new MockAuthority(true)); 43 | mockAuthChild.transferOwnership(address(0)); 44 | mockAuthChild.transferOwnership(address(this)); 45 | } 46 | 47 | function testSetAuthorityWithPermissiveAuthority() public { 48 | mockAuthChild.setAuthority(new MockAuthority(true)); 49 | mockAuthChild.transferOwnership(address(0)); 50 | mockAuthChild.setAuthority(Authority(address(0xBEEF))); 51 | } 52 | 53 | function testCallFunctionWithPermissiveAuthority() public { 54 | mockAuthChild.setAuthority(new MockAuthority(true)); 55 | mockAuthChild.transferOwnership(address(0)); 56 | mockAuthChild.updateFlag(); 57 | } 58 | 59 | function testSetAuthorityAsOwnerWithOutOfOrderAuthority() public { 60 | mockAuthChild.setAuthority(new OutOfOrderAuthority()); 61 | mockAuthChild.setAuthority(new MockAuthority(true)); 62 | } 63 | 64 | function testFailTransferOwnershipAsNonOwner() public { 65 | mockAuthChild.transferOwnership(address(0)); 66 | mockAuthChild.transferOwnership(address(0xBEEF)); 67 | } 68 | 69 | function testFailSetAuthorityAsNonOwner() public { 70 | mockAuthChild.transferOwnership(address(0)); 71 | mockAuthChild.setAuthority(Authority(address(0xBEEF))); 72 | } 73 | 74 | function testFailCallFunctionAsNonOwner() public { 75 | mockAuthChild.transferOwnership(address(0)); 76 | mockAuthChild.updateFlag(); 77 | } 78 | 79 | function testFailTransferOwnershipWithRestrictiveAuthority() public { 80 | mockAuthChild.setAuthority(new MockAuthority(false)); 81 | mockAuthChild.transferOwnership(address(0)); 82 | mockAuthChild.transferOwnership(address(this)); 83 | } 84 | 85 | function testFailSetAuthorityWithRestrictiveAuthority() public { 86 | mockAuthChild.setAuthority(new MockAuthority(false)); 87 | mockAuthChild.transferOwnership(address(0)); 88 | mockAuthChild.setAuthority(Authority(address(0xBEEF))); 89 | } 90 | 91 | function testFailCallFunctionWithRestrictiveAuthority() public { 92 | mockAuthChild.setAuthority(new MockAuthority(false)); 93 | mockAuthChild.transferOwnership(address(0)); 94 | mockAuthChild.updateFlag(); 95 | } 96 | 97 | function testFailTransferOwnershipAsOwnerWithOutOfOrderAuthority() public { 98 | mockAuthChild.setAuthority(new OutOfOrderAuthority()); 99 | mockAuthChild.transferOwnership(address(0)); 100 | } 101 | 102 | function testFailCallFunctionAsOwnerWithOutOfOrderAuthority() public { 103 | mockAuthChild.setAuthority(new OutOfOrderAuthority()); 104 | mockAuthChild.updateFlag(); 105 | } 106 | 107 | function testTransferOwnershipAsOwner(address newOwner) public { 108 | mockAuthChild.transferOwnership(newOwner); 109 | assertEq(mockAuthChild.owner(), newOwner); 110 | } 111 | 112 | function testSetAuthorityAsOwner(Authority newAuthority) public { 113 | mockAuthChild.setAuthority(newAuthority); 114 | assertEq(address(mockAuthChild.authority()), address(newAuthority)); 115 | } 116 | 117 | function testTransferOwnershipWithPermissiveAuthority(address deadOwner, address newOwner) public { 118 | if (deadOwner == address(this)) deadOwner = address(0); 119 | 120 | mockAuthChild.setAuthority(new MockAuthority(true)); 121 | mockAuthChild.transferOwnership(deadOwner); 122 | mockAuthChild.transferOwnership(newOwner); 123 | } 124 | 125 | function testSetAuthorityWithPermissiveAuthority(address deadOwner, Authority newAuthority) public { 126 | if (deadOwner == address(this)) deadOwner = address(0); 127 | 128 | mockAuthChild.setAuthority(new MockAuthority(true)); 129 | mockAuthChild.transferOwnership(deadOwner); 130 | mockAuthChild.setAuthority(newAuthority); 131 | } 132 | 133 | function testCallFunctionWithPermissiveAuthority(address deadOwner) public { 134 | if (deadOwner == address(this)) deadOwner = address(0); 135 | 136 | mockAuthChild.setAuthority(new MockAuthority(true)); 137 | mockAuthChild.transferOwnership(deadOwner); 138 | mockAuthChild.updateFlag(); 139 | } 140 | 141 | function testFailTransferOwnershipAsNonOwner(address deadOwner, address newOwner) public { 142 | if (deadOwner == address(this)) deadOwner = address(0); 143 | 144 | mockAuthChild.transferOwnership(deadOwner); 145 | mockAuthChild.transferOwnership(newOwner); 146 | } 147 | 148 | function testFailSetAuthorityAsNonOwner(address deadOwner, Authority newAuthority) public { 149 | if (deadOwner == address(this)) deadOwner = address(0); 150 | 151 | mockAuthChild.transferOwnership(deadOwner); 152 | mockAuthChild.setAuthority(newAuthority); 153 | } 154 | 155 | function testFailCallFunctionAsNonOwner(address deadOwner) public { 156 | if (deadOwner == address(this)) deadOwner = address(0); 157 | 158 | mockAuthChild.transferOwnership(deadOwner); 159 | mockAuthChild.updateFlag(); 160 | } 161 | 162 | function testFailTransferOwnershipWithRestrictiveAuthority(address deadOwner, address newOwner) public { 163 | if (deadOwner == address(this)) deadOwner = address(0); 164 | 165 | mockAuthChild.setAuthority(new MockAuthority(false)); 166 | mockAuthChild.transferOwnership(deadOwner); 167 | mockAuthChild.transferOwnership(newOwner); 168 | } 169 | 170 | function testFailSetAuthorityWithRestrictiveAuthority(address deadOwner, Authority newAuthority) public { 171 | if (deadOwner == address(this)) deadOwner = address(0); 172 | 173 | mockAuthChild.setAuthority(new MockAuthority(false)); 174 | mockAuthChild.transferOwnership(deadOwner); 175 | mockAuthChild.setAuthority(newAuthority); 176 | } 177 | 178 | function testFailCallFunctionWithRestrictiveAuthority(address deadOwner) public { 179 | if (deadOwner == address(this)) deadOwner = address(0); 180 | 181 | mockAuthChild.setAuthority(new MockAuthority(false)); 182 | mockAuthChild.transferOwnership(deadOwner); 183 | mockAuthChild.updateFlag(); 184 | } 185 | 186 | function testFailTransferOwnershipAsOwnerWithOutOfOrderAuthority(address deadOwner) public { 187 | if (deadOwner == address(this)) deadOwner = address(0); 188 | 189 | mockAuthChild.setAuthority(new OutOfOrderAuthority()); 190 | mockAuthChild.transferOwnership(deadOwner); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/test/Bytes32AddressLib.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | 6 | import {Bytes32AddressLib} from "../utils/Bytes32AddressLib.sol"; 7 | 8 | contract Bytes32AddressLibTest is DSTestPlus { 9 | function testFillLast12Bytes() public { 10 | assertEq( 11 | Bytes32AddressLib.fillLast12Bytes(0xfEEDFaCEcaFeBEEFfEEDFACecaFEBeeFfeEdfAce), 12 | 0xfeedfacecafebeeffeedfacecafebeeffeedface000000000000000000000000 13 | ); 14 | } 15 | 16 | function testFromLast20Bytes() public { 17 | assertEq( 18 | Bytes32AddressLib.fromLast20Bytes(0xfeedfacecafebeeffeedfacecafebeeffeedfacecafebeeffeedfacecafebeef), 19 | 0xCAfeBeefFeedfAceCAFeBEEffEEDfaCecafEBeeF 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/CREATE3.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {WETH} from "../tokens/WETH.sol"; 5 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 6 | import {MockERC20} from "./utils/mocks/MockERC20.sol"; 7 | import {MockAuthChild} from "./utils/mocks/MockAuthChild.sol"; 8 | 9 | import {CREATE3} from "../utils/CREATE3.sol"; 10 | 11 | contract Factory { 12 | function deploy(bytes32 salt) public returns (address deployed) { 13 | deployed = CREATE3.deploy( 14 | salt, 15 | abi.encodePacked(type(MockERC20).creationCode, abi.encode("Mock Token", "MOCK", 18)), 16 | 0 17 | ); 18 | } 19 | } 20 | 21 | contract CREATE3Test is DSTestPlus { 22 | function testDeployERC20() public { 23 | bytes32 salt = keccak256(bytes("A salt!")); 24 | MockERC20 deployed = MockERC20( 25 | CREATE3.deploy( 26 | salt, 27 | abi.encodePacked(type(MockERC20).creationCode, abi.encode("Mock Token", "MOCK", 18)), 28 | 0 29 | ) 30 | ); 31 | 32 | assertEq(address(deployed), CREATE3.getDeployed(salt)); 33 | 34 | assertEq(deployed.name(), "Mock Token"); 35 | assertEq(deployed.symbol(), "MOCK"); 36 | assertEq(deployed.decimals(), 18); 37 | } 38 | 39 | function testPredictDeployERC20() public { 40 | bytes32 salt = keccak256(bytes("A salt!")); 41 | Factory factory = new Factory(); 42 | 43 | MockERC20 deployed = MockERC20( 44 | factory.deploy(salt) 45 | ); 46 | 47 | assertEq(address(deployed), CREATE3.getDeployed(salt, address(factory))); 48 | assertTrue(address(deployed) != CREATE3.getDeployed(salt)); 49 | 50 | assertEq(deployed.name(), "Mock Token"); 51 | assertEq(deployed.symbol(), "MOCK"); 52 | assertEq(deployed.decimals(), 18); 53 | } 54 | 55 | function testFailDoubleDeploySameBytecode() public { 56 | bytes32 salt = keccak256(bytes("Salty...")); 57 | 58 | CREATE3.deploy(salt, type(MockAuthChild).creationCode, 0); 59 | CREATE3.deploy(salt, type(MockAuthChild).creationCode, 0); 60 | } 61 | 62 | function testFailDoubleDeployDifferentBytecode() public { 63 | bytes32 salt = keccak256(bytes("and sweet!")); 64 | 65 | CREATE3.deploy(salt, type(WETH).creationCode, 0); 66 | CREATE3.deploy(salt, type(MockAuthChild).creationCode, 0); 67 | } 68 | 69 | function testDeployERC20( 70 | bytes32 salt, 71 | string calldata name, 72 | string calldata symbol, 73 | uint8 decimals 74 | ) public { 75 | MockERC20 deployed = MockERC20( 76 | CREATE3.deploy(salt, abi.encodePacked(type(MockERC20).creationCode, abi.encode(name, symbol, decimals)), 0) 77 | ); 78 | 79 | assertEq(address(deployed), CREATE3.getDeployed(salt)); 80 | 81 | assertEq(deployed.name(), name); 82 | assertEq(deployed.symbol(), symbol); 83 | assertEq(deployed.decimals(), decimals); 84 | } 85 | 86 | function testFailDoubleDeploySameBytecode(bytes32 salt, bytes calldata bytecode) public { 87 | CREATE3.deploy(salt, bytecode, 0); 88 | CREATE3.deploy(salt, bytecode, 0); 89 | } 90 | 91 | function testFailDoubleDeployDifferentBytecode( 92 | bytes32 salt, 93 | bytes calldata bytecode1, 94 | bytes calldata bytecode2 95 | ) public { 96 | CREATE3.deploy(salt, bytecode1, 0); 97 | CREATE3.deploy(salt, bytecode2, 0); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/DSTestPlus.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | 6 | contract DSTestPlusTest is DSTestPlus { 7 | function testBound() public { 8 | assertEq(bound(0, 69, 69), 69); 9 | assertEq(bound(0, 68, 69), 68); 10 | assertEq(bound(5, 0, 4), 0); 11 | assertEq(bound(9999, 1337, 6666), 6006); 12 | assertEq(bound(0, type(uint256).max - 6, type(uint256).max), type(uint256).max - 6); 13 | assertEq(bound(6, type(uint256).max - 6, type(uint256).max), type(uint256).max); 14 | } 15 | 16 | function testFailBoundMinBiggerThanMax() public { 17 | bound(5, 100, 10); 18 | } 19 | 20 | function testRelApproxEqBothZeroesPasses() public { 21 | assertRelApproxEq(0, 0, 1e18); 22 | assertRelApproxEq(0, 0, 0); 23 | } 24 | 25 | function testBound( 26 | uint256 num, 27 | uint256 min, 28 | uint256 max 29 | ) public { 30 | if (min > max) (min, max) = (max, min); 31 | 32 | uint256 bounded = bound(num, min, max); 33 | 34 | assertGe(bounded, min); 35 | assertLe(bounded, max); 36 | } 37 | 38 | function testFailBoundMinBiggerThanMax( 39 | uint256 num, 40 | uint256 min, 41 | uint256 max 42 | ) public { 43 | if (max == min) { 44 | unchecked { 45 | min++; // Overflow is handled below. 46 | } 47 | } 48 | 49 | if (max > min) (min, max) = (max, min); 50 | 51 | bound(num, min, max); 52 | } 53 | 54 | function testBrutalizeMemory() public brutalizeMemory("FEEDFACECAFEBEEFFEEDFACECAFEBEEF") { 55 | bytes32 scratchSpace1; 56 | bytes32 scratchSpace2; 57 | bytes32 freeMem1; 58 | bytes32 freeMem2; 59 | 60 | assembly { 61 | scratchSpace1 := mload(0) 62 | scratchSpace2 := mload(32) 63 | freeMem1 := mload(mload(0x40)) 64 | freeMem2 := mload(add(mload(0x40), 32)) 65 | } 66 | 67 | assertGt(uint256(freeMem1), 0); 68 | assertGt(uint256(freeMem2), 0); 69 | assertGt(uint256(scratchSpace1), 0); 70 | assertGt(uint256(scratchSpace2), 0); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/ERC6909.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | import {DSInvariantTest} from "./utils/DSInvariantTest.sol"; 6 | 7 | import {MockERC6909} from "./utils/mocks/MockERC6909.sol"; 8 | 9 | contract ERC6909Test is DSTestPlus { 10 | MockERC6909 token; 11 | 12 | mapping(address => mapping(uint256 => uint256)) public userMintAmounts; 13 | mapping(address => mapping(uint256 => uint256)) public userTransferOrBurnAmounts; 14 | 15 | function setUp() public { 16 | token = new MockERC6909(); 17 | } 18 | 19 | function testMint() public { 20 | token.mint(address(0xBEEF), 1337, 100); 21 | 22 | assertEq(token.balanceOf(address(0xBEEF), 1337), 100); 23 | } 24 | 25 | function testBurn() public { 26 | token.mint(address(0xBEEF), 1337, 100); 27 | token.burn(address(0xBEEF), 1337, 70); 28 | 29 | assertEq(token.balanceOf(address(0xBEEF), 1337), 30); 30 | } 31 | 32 | function testSetOperator() public { 33 | token.setOperator(address(0xBEEF), true); 34 | 35 | assertTrue(token.isOperator(address(this), address(0xBEEF))); 36 | } 37 | 38 | function testApprove() public { 39 | token.approve(address(0xBEEF), 1337, 100); 40 | 41 | assertEq(token.allowance(address(this), address(0xBEEF), 1337), 100); 42 | } 43 | 44 | function testTransfer() public { 45 | address sender = address(0xABCD); 46 | 47 | token.mint(sender, 1337, 100); 48 | 49 | hevm.prank(sender); 50 | token.transfer(address(0xBEEF), 1337, 70); 51 | 52 | assertEq(token.balanceOf(sender, 1337), 30); 53 | assertEq(token.balanceOf(address(0xBEEF), 1337), 70); 54 | } 55 | 56 | function testTransferFromWithApproval() public { 57 | address sender = address(0xABCD); 58 | address receiver = address(0xBEEF); 59 | 60 | token.mint(sender, 1337, 100); 61 | 62 | hevm.prank(sender); 63 | token.approve(address(this), 1337, 100); 64 | 65 | token.transferFrom(sender, receiver, 1337, 70); 66 | 67 | assertEq(token.allowance(sender, address(this), 1337), 30); 68 | assertEq(token.balanceOf(sender, 1337), 30); 69 | assertEq(token.balanceOf(receiver, 1337), 70); 70 | } 71 | 72 | function testTransferFromWithInfiniteApproval() public { 73 | address sender = address(0xABCD); 74 | address receiver = address(0xBEEF); 75 | 76 | token.mint(sender, 1337, 100); 77 | 78 | hevm.prank(sender); 79 | token.approve(address(this), 1337, type(uint256).max); 80 | 81 | token.transferFrom(sender, receiver, 1337, 70); 82 | 83 | assertEq(token.allowance(sender, address(this), 1337), type(uint256).max); 84 | assertEq(token.balanceOf(sender, 1337), 30); 85 | assertEq(token.balanceOf(receiver, 1337), 70); 86 | } 87 | 88 | function testTransferFromAsOperator() public { 89 | address sender = address(0xABCD); 90 | address receiver = address(0xBEEF); 91 | 92 | token.mint(sender, 1337, 100); 93 | 94 | hevm.prank(sender); 95 | token.setOperator(address(this), true); 96 | 97 | token.transferFrom(sender, receiver, 1337, 70); 98 | 99 | assertEq(token.balanceOf(sender, 1337), 30); 100 | assertEq(token.balanceOf(receiver, 1337), 70); 101 | } 102 | 103 | function testFailMintBalanceOverflow() public { 104 | token.mint(address(0xDEAD), 1337, type(uint256).max); 105 | token.mint(address(0xDEAD), 1337, 1); 106 | } 107 | 108 | function testFailTransferBalanceUnderflow() public { 109 | address sender = address(0xABCD); 110 | address receiver = address(0xBEEF); 111 | 112 | hevm.prank(sender); 113 | token.transferFrom(sender, receiver, 1337, 1); 114 | } 115 | 116 | function testFailTransferBalanceOverflow() public { 117 | address sender = address(0xABCD); 118 | address receiver = address(0xBEEF); 119 | 120 | token.mint(sender, 1337, type(uint256).max); 121 | 122 | hevm.prank(sender); 123 | token.transferFrom(sender, receiver, 1337, type(uint256).max); 124 | 125 | token.mint(sender, 1337, 1); 126 | 127 | hevm.prank(sender); 128 | token.transferFrom(sender, receiver, 1337, 1); 129 | } 130 | 131 | function testFailTransferFromBalanceUnderflow() public { 132 | address sender = address(0xABCD); 133 | address receiver = address(0xBEEF); 134 | 135 | hevm.prank(sender); 136 | token.transferFrom(sender, receiver, 1337, 1); 137 | } 138 | 139 | function testFailTransferFromBalanceOverflow() public { 140 | address sender = address(0xABCD); 141 | address receiver = address(0xBEEF); 142 | 143 | token.mint(sender, 1337, type(uint256).max); 144 | 145 | hevm.prank(sender); 146 | token.transferFrom(sender, receiver, 1337, type(uint256).max); 147 | 148 | token.mint(sender, 1337, 1); 149 | 150 | hevm.prank(sender); 151 | token.transferFrom(sender, receiver, 1337, 1); 152 | } 153 | 154 | function testFailTransferFromNotAuthorized() public { 155 | address sender = address(0xABCD); 156 | address receiver = address(0xBEEF); 157 | 158 | token.mint(sender, 1337, 100); 159 | 160 | token.transferFrom(sender, receiver, 1337, 100); 161 | } 162 | 163 | function testMint( 164 | address receiver, 165 | uint256 id, 166 | uint256 amount 167 | ) public { 168 | token.mint(receiver, id, amount); 169 | 170 | assertEq(token.balanceOf(receiver, id), amount); 171 | } 172 | 173 | function testBurn( 174 | address sender, 175 | uint256 id, 176 | uint256 amount 177 | ) public { 178 | token.mint(sender, id, amount); 179 | token.burn(sender, id, amount); 180 | 181 | assertEq(token.balanceOf(sender, id), 0); 182 | } 183 | 184 | function testSetOperator(address operator, bool approved) public { 185 | token.setOperator(operator, approved); 186 | 187 | assertBoolEq(token.isOperator(address(this), operator), approved); 188 | } 189 | 190 | function testApprove( 191 | address spender, 192 | uint256 id, 193 | uint256 amount 194 | ) public { 195 | token.approve(spender, id, amount); 196 | 197 | assertEq(token.allowance(address(this), spender, id), amount); 198 | } 199 | 200 | function testTransfer( 201 | address sender, 202 | address receiver, 203 | uint256 id, 204 | uint256 mintAmount, 205 | uint256 transferAmount 206 | ) public { 207 | transferAmount = bound(transferAmount, 0, mintAmount); 208 | 209 | token.mint(sender, id, mintAmount); 210 | 211 | hevm.prank(sender); 212 | token.transfer(receiver, id, transferAmount); 213 | 214 | if (sender == receiver) { 215 | assertEq(token.balanceOf(sender, id), mintAmount); 216 | } else { 217 | assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); 218 | assertEq(token.balanceOf(receiver, id), transferAmount); 219 | } 220 | } 221 | 222 | function testTransferFromWithApproval( 223 | address sender, 224 | address receiver, 225 | uint256 id, 226 | uint256 mintAmount, 227 | uint256 transferAmount 228 | ) public { 229 | transferAmount = bound(transferAmount, 0, mintAmount); 230 | 231 | token.mint(sender, id, mintAmount); 232 | 233 | hevm.prank(sender); 234 | token.approve(address(this), id, mintAmount); 235 | 236 | token.transferFrom(sender, receiver, id, transferAmount); 237 | 238 | if (mintAmount == type(uint256).max) { 239 | assertEq(token.allowance(sender, address(this), id), type(uint256).max); 240 | } else { 241 | assertEq(token.allowance(sender, address(this), id), mintAmount - transferAmount); 242 | } 243 | 244 | if (sender == receiver) { 245 | assertEq(token.balanceOf(sender, id), mintAmount); 246 | } else { 247 | assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); 248 | assertEq(token.balanceOf(receiver, id), transferAmount); 249 | } 250 | } 251 | 252 | function testTransferFromWithInfiniteApproval( 253 | address sender, 254 | address receiver, 255 | uint256 id, 256 | uint256 mintAmount, 257 | uint256 transferAmount 258 | ) public { 259 | transferAmount = bound(transferAmount, 0, mintAmount); 260 | 261 | token.mint(sender, id, mintAmount); 262 | 263 | hevm.prank(sender); 264 | token.approve(address(this), id, type(uint256).max); 265 | 266 | token.transferFrom(sender, receiver, id, transferAmount); 267 | 268 | assertEq(token.allowance(sender, address(this), id), type(uint256).max); 269 | 270 | if (sender == receiver) { 271 | assertEq(token.balanceOf(sender, id), mintAmount); 272 | } else { 273 | assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); 274 | assertEq(token.balanceOf(receiver, id), transferAmount); 275 | } 276 | } 277 | 278 | function testTransferFromAsOperator( 279 | address sender, 280 | address receiver, 281 | uint256 id, 282 | uint256 mintAmount, 283 | uint256 transferAmount 284 | ) public { 285 | transferAmount = bound(transferAmount, 0, mintAmount); 286 | 287 | token.mint(sender, id, mintAmount); 288 | 289 | hevm.prank(sender); 290 | token.setOperator(address(this), true); 291 | 292 | token.transferFrom(sender, receiver, id, transferAmount); 293 | 294 | if (sender == receiver) { 295 | assertEq(token.balanceOf(sender, id), mintAmount); 296 | } else { 297 | assertEq(token.balanceOf(sender, id), mintAmount - transferAmount); 298 | assertEq(token.balanceOf(receiver, id), transferAmount); 299 | } 300 | } 301 | 302 | function testFailTransferBalanceUnderflow( 303 | address sender, 304 | address receiver, 305 | uint256 id, 306 | uint256 amount 307 | ) public { 308 | amount = bound(amount, 1, type(uint256).max); 309 | 310 | hevm.prank(sender); 311 | token.transfer(receiver, id, amount); 312 | } 313 | 314 | function testFailTransferBalanceOverflow( 315 | address sender, 316 | address receiver, 317 | uint256 id, 318 | uint256 amount 319 | ) public { 320 | amount = bound(amount, 1, type(uint256).max); 321 | uint256 overflowAmount = type(uint256).max - amount + 1; 322 | 323 | token.mint(sender, id, amount); 324 | 325 | hevm.prank(sender); 326 | token.transfer(receiver, id, amount); 327 | 328 | token.mint(sender, id, overflowAmount); 329 | 330 | hevm.prank(sender); 331 | token.transfer(receiver, id, overflowAmount); 332 | } 333 | 334 | function testFailTransferFromBalanceUnderflow( 335 | address sender, 336 | address receiver, 337 | uint256 id, 338 | uint256 amount 339 | ) public { 340 | amount = bound(amount, 1, type(uint256).max); 341 | 342 | hevm.prank(sender); 343 | token.transferFrom(sender, receiver, id, amount); 344 | } 345 | 346 | function testFailTransferFromBalanceOverflow( 347 | address sender, 348 | address receiver, 349 | uint256 id, 350 | uint256 amount 351 | ) public { 352 | amount = bound(amount, 1, type(uint256).max); 353 | uint256 overflowAmount = type(uint256).max - amount + 1; 354 | 355 | token.mint(sender, id, amount); 356 | 357 | hevm.prank(sender); 358 | token.transferFrom(sender, receiver, id, amount); 359 | 360 | token.mint(sender, id, overflowAmount); 361 | 362 | hevm.prank(sender); 363 | token.transferFrom(sender, receiver, id, overflowAmount); 364 | } 365 | 366 | function testFailTransferFromNotAuthorized( 367 | address sender, 368 | address receiver, 369 | uint256 id, 370 | uint256 amount 371 | ) public { 372 | amount = bound(amount, 1, type(uint256).max); 373 | 374 | token.mint(sender, id, amount); 375 | 376 | token.transferFrom(sender, receiver, id, amount); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/test/FixedPointMathLib.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | 6 | import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol"; 7 | 8 | contract FixedPointMathLibTest is DSTestPlus { 9 | function testMulWadDown() public { 10 | assertEq(FixedPointMathLib.mulWadDown(2.5e18, 0.5e18), 1.25e18); 11 | assertEq(FixedPointMathLib.mulWadDown(3e18, 1e18), 3e18); 12 | assertEq(FixedPointMathLib.mulWadDown(369, 271), 0); 13 | } 14 | 15 | function testMulWadDownEdgeCases() public { 16 | assertEq(FixedPointMathLib.mulWadDown(0, 1e18), 0); 17 | assertEq(FixedPointMathLib.mulWadDown(1e18, 0), 0); 18 | assertEq(FixedPointMathLib.mulWadDown(0, 0), 0); 19 | } 20 | 21 | function testMulWadUp() public { 22 | assertEq(FixedPointMathLib.mulWadUp(2.5e18, 0.5e18), 1.25e18); 23 | assertEq(FixedPointMathLib.mulWadUp(3e18, 1e18), 3e18); 24 | assertEq(FixedPointMathLib.mulWadUp(369, 271), 1); 25 | } 26 | 27 | function testMulWadUpEdgeCases() public { 28 | assertEq(FixedPointMathLib.mulWadUp(0, 1e18), 0); 29 | assertEq(FixedPointMathLib.mulWadUp(1e18, 0), 0); 30 | assertEq(FixedPointMathLib.mulWadUp(0, 0), 0); 31 | } 32 | 33 | function testDivWadDown() public { 34 | assertEq(FixedPointMathLib.divWadDown(1.25e18, 0.5e18), 2.5e18); 35 | assertEq(FixedPointMathLib.divWadDown(3e18, 1e18), 3e18); 36 | assertEq(FixedPointMathLib.divWadDown(2, 100000000000000e18), 0); 37 | } 38 | 39 | function testDivWadDownEdgeCases() public { 40 | assertEq(FixedPointMathLib.divWadDown(0, 1e18), 0); 41 | } 42 | 43 | function testFailDivWadDownZeroDenominator() public pure { 44 | FixedPointMathLib.divWadDown(1e18, 0); 45 | } 46 | 47 | function testDivWadUp() public { 48 | assertEq(FixedPointMathLib.divWadUp(1.25e18, 0.5e18), 2.5e18); 49 | assertEq(FixedPointMathLib.divWadUp(3e18, 1e18), 3e18); 50 | assertEq(FixedPointMathLib.divWadUp(2, 100000000000000e18), 1); 51 | } 52 | 53 | function testDivWadUpEdgeCases() public { 54 | assertEq(FixedPointMathLib.divWadUp(0, 1e18), 0); 55 | } 56 | 57 | function testFailDivWadUpZeroDenominator() public pure { 58 | FixedPointMathLib.divWadUp(1e18, 0); 59 | } 60 | 61 | function testMulDivDown() public { 62 | assertEq(FixedPointMathLib.mulDivDown(2.5e27, 0.5e27, 1e27), 1.25e27); 63 | assertEq(FixedPointMathLib.mulDivDown(2.5e18, 0.5e18, 1e18), 1.25e18); 64 | assertEq(FixedPointMathLib.mulDivDown(2.5e8, 0.5e8, 1e8), 1.25e8); 65 | assertEq(FixedPointMathLib.mulDivDown(369, 271, 1e2), 999); 66 | 67 | assertEq(FixedPointMathLib.mulDivDown(1e27, 1e27, 2e27), 0.5e27); 68 | assertEq(FixedPointMathLib.mulDivDown(1e18, 1e18, 2e18), 0.5e18); 69 | assertEq(FixedPointMathLib.mulDivDown(1e8, 1e8, 2e8), 0.5e8); 70 | 71 | assertEq(FixedPointMathLib.mulDivDown(2e27, 3e27, 2e27), 3e27); 72 | assertEq(FixedPointMathLib.mulDivDown(3e18, 2e18, 3e18), 2e18); 73 | assertEq(FixedPointMathLib.mulDivDown(2e8, 3e8, 2e8), 3e8); 74 | } 75 | 76 | function testMulDivDownEdgeCases() public { 77 | assertEq(FixedPointMathLib.mulDivDown(0, 1e18, 1e18), 0); 78 | assertEq(FixedPointMathLib.mulDivDown(1e18, 0, 1e18), 0); 79 | assertEq(FixedPointMathLib.mulDivDown(0, 0, 1e18), 0); 80 | } 81 | 82 | function testFailMulDivDownZeroDenominator() public pure { 83 | FixedPointMathLib.mulDivDown(1e18, 1e18, 0); 84 | } 85 | 86 | function testMulDivUp() public { 87 | assertEq(FixedPointMathLib.mulDivUp(2.5e27, 0.5e27, 1e27), 1.25e27); 88 | assertEq(FixedPointMathLib.mulDivUp(2.5e18, 0.5e18, 1e18), 1.25e18); 89 | assertEq(FixedPointMathLib.mulDivUp(2.5e8, 0.5e8, 1e8), 1.25e8); 90 | assertEq(FixedPointMathLib.mulDivUp(369, 271, 1e2), 1000); 91 | 92 | assertEq(FixedPointMathLib.mulDivUp(1e27, 1e27, 2e27), 0.5e27); 93 | assertEq(FixedPointMathLib.mulDivUp(1e18, 1e18, 2e18), 0.5e18); 94 | assertEq(FixedPointMathLib.mulDivUp(1e8, 1e8, 2e8), 0.5e8); 95 | 96 | assertEq(FixedPointMathLib.mulDivUp(2e27, 3e27, 2e27), 3e27); 97 | assertEq(FixedPointMathLib.mulDivUp(3e18, 2e18, 3e18), 2e18); 98 | assertEq(FixedPointMathLib.mulDivUp(2e8, 3e8, 2e8), 3e8); 99 | } 100 | 101 | function testMulDivUpEdgeCases() public { 102 | assertEq(FixedPointMathLib.mulDivUp(0, 1e18, 1e18), 0); 103 | assertEq(FixedPointMathLib.mulDivUp(1e18, 0, 1e18), 0); 104 | assertEq(FixedPointMathLib.mulDivUp(0, 0, 1e18), 0); 105 | } 106 | 107 | function testFailMulDivUpZeroDenominator() public pure { 108 | FixedPointMathLib.mulDivUp(1e18, 1e18, 0); 109 | } 110 | 111 | function testRPow() public { 112 | assertEq(FixedPointMathLib.rpow(2e27, 2, 1e27), 4e27); 113 | assertEq(FixedPointMathLib.rpow(2e18, 2, 1e18), 4e18); 114 | assertEq(FixedPointMathLib.rpow(2e8, 2, 1e8), 4e8); 115 | assertEq(FixedPointMathLib.rpow(8, 3, 1), 512); 116 | } 117 | 118 | function testSqrt() public { 119 | assertEq(FixedPointMathLib.sqrt(0), 0); 120 | assertEq(FixedPointMathLib.sqrt(1), 1); 121 | assertEq(FixedPointMathLib.sqrt(2704), 52); 122 | assertEq(FixedPointMathLib.sqrt(110889), 333); 123 | assertEq(FixedPointMathLib.sqrt(32239684), 5678); 124 | assertEq(FixedPointMathLib.sqrt(type(uint256).max), 340282366920938463463374607431768211455); 125 | } 126 | 127 | function testSqrtBackHashedSingle() public { 128 | testSqrtBackHashed(123); 129 | } 130 | 131 | function testMulWadDown(uint256 x, uint256 y) public { 132 | // Ignore cases where x * y overflows. 133 | unchecked { 134 | if (x != 0 && (x * y) / x != y) return; 135 | } 136 | 137 | assertEq(FixedPointMathLib.mulWadDown(x, y), (x * y) / 1e18); 138 | } 139 | 140 | function testFailMulWadDownOverflow(uint256 x, uint256 y) public pure { 141 | // Ignore cases where x * y does not overflow. 142 | unchecked { 143 | if ((x * y) / x == y) revert(); 144 | } 145 | 146 | FixedPointMathLib.mulWadDown(x, y); 147 | } 148 | 149 | function testMulWadUp(uint256 x, uint256 y) public { 150 | // Ignore cases where x * y overflows. 151 | unchecked { 152 | if (x != 0 && (x * y) / x != y) return; 153 | } 154 | 155 | assertEq(FixedPointMathLib.mulWadUp(x, y), x * y == 0 ? 0 : (x * y - 1) / 1e18 + 1); 156 | } 157 | 158 | function testFailMulWadUpOverflow(uint256 x, uint256 y) public pure { 159 | // Ignore cases where x * y does not overflow. 160 | unchecked { 161 | if ((x * y) / x == y) revert(); 162 | } 163 | 164 | FixedPointMathLib.mulWadUp(x, y); 165 | } 166 | 167 | function testDivWadDown(uint256 x, uint256 y) public { 168 | // Ignore cases where x * WAD overflows or y is 0. 169 | unchecked { 170 | if (y == 0 || (x != 0 && (x * 1e18) / 1e18 != x)) return; 171 | } 172 | 173 | assertEq(FixedPointMathLib.divWadDown(x, y), (x * 1e18) / y); 174 | } 175 | 176 | function testFailDivWadDownOverflow(uint256 x, uint256 y) public pure { 177 | // Ignore cases where x * WAD does not overflow or y is 0. 178 | unchecked { 179 | if (y == 0 || (x * 1e18) / 1e18 == x) revert(); 180 | } 181 | 182 | FixedPointMathLib.divWadDown(x, y); 183 | } 184 | 185 | function testFailDivWadDownZeroDenominator(uint256 x) public pure { 186 | FixedPointMathLib.divWadDown(x, 0); 187 | } 188 | 189 | function testDivWadUp(uint256 x, uint256 y) public { 190 | // Ignore cases where x * WAD overflows or y is 0. 191 | unchecked { 192 | if (y == 0 || (x != 0 && (x * 1e18) / 1e18 != x)) return; 193 | } 194 | 195 | assertEq(FixedPointMathLib.divWadUp(x, y), x == 0 ? 0 : (x * 1e18 - 1) / y + 1); 196 | } 197 | 198 | function testFailDivWadUpOverflow(uint256 x, uint256 y) public pure { 199 | // Ignore cases where x * WAD does not overflow or y is 0. 200 | unchecked { 201 | if (y == 0 || (x * 1e18) / 1e18 == x) revert(); 202 | } 203 | 204 | FixedPointMathLib.divWadUp(x, y); 205 | } 206 | 207 | function testFailDivWadUpZeroDenominator(uint256 x) public pure { 208 | FixedPointMathLib.divWadUp(x, 0); 209 | } 210 | 211 | function testMulDivDown( 212 | uint256 x, 213 | uint256 y, 214 | uint256 denominator 215 | ) public { 216 | // Ignore cases where x * y overflows or denominator is 0. 217 | unchecked { 218 | if (denominator == 0 || (x != 0 && (x * y) / x != y)) return; 219 | } 220 | 221 | assertEq(FixedPointMathLib.mulDivDown(x, y, denominator), (x * y) / denominator); 222 | } 223 | 224 | function testFailMulDivDownOverflow( 225 | uint256 x, 226 | uint256 y, 227 | uint256 denominator 228 | ) public pure { 229 | // Ignore cases where x * y does not overflow or denominator is 0. 230 | unchecked { 231 | if (denominator == 0 || (x * y) / x == y) revert(); 232 | } 233 | 234 | FixedPointMathLib.mulDivDown(x, y, denominator); 235 | } 236 | 237 | function testFailMulDivDownZeroDenominator(uint256 x, uint256 y) public pure { 238 | FixedPointMathLib.mulDivDown(x, y, 0); 239 | } 240 | 241 | function testMulDivUp( 242 | uint256 x, 243 | uint256 y, 244 | uint256 denominator 245 | ) public { 246 | // Ignore cases where x * y overflows or denominator is 0. 247 | unchecked { 248 | if (denominator == 0 || (x != 0 && (x * y) / x != y)) return; 249 | } 250 | 251 | assertEq(FixedPointMathLib.mulDivUp(x, y, denominator), x * y == 0 ? 0 : (x * y - 1) / denominator + 1); 252 | } 253 | 254 | function testFailMulDivUpOverflow( 255 | uint256 x, 256 | uint256 y, 257 | uint256 denominator 258 | ) public pure { 259 | // Ignore cases where x * y does not overflow or denominator is 0. 260 | unchecked { 261 | if (denominator == 0 || (x * y) / x == y) revert(); 262 | } 263 | 264 | FixedPointMathLib.mulDivUp(x, y, denominator); 265 | } 266 | 267 | function testFailMulDivUpZeroDenominator(uint256 x, uint256 y) public pure { 268 | FixedPointMathLib.mulDivUp(x, y, 0); 269 | } 270 | 271 | function testDifferentiallyFuzzSqrt(uint256 x) public { 272 | assertEq(FixedPointMathLib.sqrt(x), uniswapSqrt(x)); 273 | assertEq(FixedPointMathLib.sqrt(x), abdkSqrt(x)); 274 | } 275 | 276 | function testSqrt(uint256 x) public { 277 | uint256 root = FixedPointMathLib.sqrt(x); 278 | uint256 next = root + 1; 279 | 280 | // Ignore cases where next * next overflows. 281 | unchecked { 282 | if (next * next < next) return; 283 | } 284 | 285 | assertTrue(root * root <= x && next * next > x); 286 | } 287 | 288 | function testSqrtBack(uint256 x) public { 289 | unchecked { 290 | x >>= 128; 291 | while (x != 0) { 292 | assertEq(FixedPointMathLib.sqrt(x * x), x); 293 | x >>= 1; 294 | } 295 | } 296 | } 297 | 298 | function testSqrtBackHashed(uint256 x) public { 299 | testSqrtBack(uint256(keccak256(abi.encode(x)))); 300 | } 301 | 302 | function uniswapSqrt(uint256 y) internal pure returns (uint256 z) { 303 | if (y > 3) { 304 | z = y; 305 | uint256 x = y / 2 + 1; 306 | while (x < z) { 307 | z = x; 308 | x = (y / x + x) / 2; 309 | } 310 | } else if (y != 0) { 311 | z = 1; 312 | } 313 | } 314 | 315 | function abdkSqrt(uint256 x) private pure returns (uint256) { 316 | unchecked { 317 | if (x == 0) return 0; 318 | else { 319 | uint256 xx = x; 320 | uint256 r = 1; 321 | if (xx >= 0x100000000000000000000000000000000) { 322 | xx >>= 128; 323 | r <<= 64; 324 | } 325 | if (xx >= 0x10000000000000000) { 326 | xx >>= 64; 327 | r <<= 32; 328 | } 329 | if (xx >= 0x100000000) { 330 | xx >>= 32; 331 | r <<= 16; 332 | } 333 | if (xx >= 0x10000) { 334 | xx >>= 16; 335 | r <<= 8; 336 | } 337 | if (xx >= 0x100) { 338 | xx >>= 8; 339 | r <<= 4; 340 | } 341 | if (xx >= 0x10) { 342 | xx >>= 4; 343 | r <<= 2; 344 | } 345 | if (xx >= 0x8) { 346 | r <<= 1; 347 | } 348 | r = (r + x / r) >> 1; 349 | r = (r + x / r) >> 1; 350 | r = (r + x / r) >> 1; 351 | r = (r + x / r) >> 1; 352 | r = (r + x / r) >> 1; 353 | r = (r + x / r) >> 1; 354 | r = (r + x / r) >> 1; // Seven iterations should be enough 355 | uint256 r1 = x / r; 356 | return r < r1 ? r : r1; 357 | } 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/test/LibString.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | 6 | import {LibString} from "../utils/LibString.sol"; 7 | 8 | contract LibStringTest is DSTestPlus { 9 | function testToString() public { 10 | assertEq(LibString.toString(uint256(0)), "0"); 11 | assertEq(LibString.toString(uint256(1)), "1"); 12 | assertEq(LibString.toString(uint256(17)), "17"); 13 | assertEq(LibString.toString(uint256(99999999)), "99999999"); 14 | assertEq(LibString.toString(uint256(99999999999)), "99999999999"); 15 | assertEq(LibString.toString(uint256(2342343923423)), "2342343923423"); 16 | assertEq(LibString.toString(uint256(98765685434567)), "98765685434567"); 17 | } 18 | 19 | function testToStringIntPositive() public { 20 | assertEq(LibString.toString(int256(0)), "0"); 21 | assertEq(LibString.toString(int256(1)), "1"); 22 | assertEq(LibString.toString(int256(17)), "17"); 23 | assertEq(LibString.toString(int256(99999999)), "99999999"); 24 | assertEq(LibString.toString(int256(99999999999)), "99999999999"); 25 | assertEq(LibString.toString(int256(2342343923423)), "2342343923423"); 26 | assertEq(LibString.toString(int256(98765685434567)), "98765685434567"); 27 | } 28 | 29 | function testToStringIntNegative() public { 30 | assertEq(LibString.toString(int256(-0)), "0"); 31 | assertEq(LibString.toString(int256(-17)), "-17"); 32 | assertEq(LibString.toString(int256(-99999999)), "-99999999"); 33 | assertEq(LibString.toString(int256(-99999999999)), "-99999999999"); 34 | assertEq(LibString.toString(int256(-2342343923423)), "-2342343923423"); 35 | assertEq(LibString.toString(int256(-98765685434567)), "-98765685434567"); 36 | } 37 | 38 | function testDifferentiallyFuzzToString(uint256 value, bytes calldata brutalizeWith) 39 | public 40 | brutalizeMemory(brutalizeWith) 41 | { 42 | string memory libString = LibString.toString(value); 43 | string memory oz = toStringOZ(value); 44 | 45 | assertEq(bytes(libString).length, bytes(oz).length); 46 | assertEq(libString, oz); 47 | } 48 | 49 | function testDifferentiallyFuzzToStringInt(int256 value, bytes calldata brutalizeWith) 50 | public 51 | brutalizeMemory(brutalizeWith) 52 | { 53 | string memory libString = LibString.toString(value); 54 | string memory oz = toStringOZ(value); 55 | 56 | assertEq(bytes(libString).length, bytes(oz).length); 57 | assertEq(libString, oz); 58 | } 59 | 60 | function testToStringOverwrite() public { 61 | string memory str = LibString.toString(uint256(1)); 62 | 63 | bytes32 data; 64 | bytes32 expected; 65 | 66 | assembly { 67 | // Imagine a high level allocation writing something to the current free memory. 68 | // Should have sufficient higher order bits for this to be visible 69 | mstore(mload(0x40), not(0)) 70 | // Correctly allocate 32 more bytes, to avoid more interference 71 | mstore(0x40, add(mload(0x40), 32)) 72 | data := mload(add(str, 32)) 73 | 74 | // the expected value should be the uft-8 encoding of 1 (49), 75 | // followed by clean bits. We achieve this by taking the value and 76 | // shifting left to the end of the 32 byte word 77 | expected := shl(248, 49) 78 | } 79 | 80 | assertEq(data, expected); 81 | } 82 | 83 | function testToStringDirty() public { 84 | uint256 freememptr; 85 | // Make the next 4 bytes of the free memory dirty 86 | assembly { 87 | let dirty := not(0) 88 | freememptr := mload(0x40) 89 | mstore(freememptr, dirty) 90 | mstore(add(freememptr, 32), dirty) 91 | mstore(add(freememptr, 64), dirty) 92 | mstore(add(freememptr, 96), dirty) 93 | mstore(add(freememptr, 128), dirty) 94 | } 95 | string memory str = LibString.toString(uint256(1)); 96 | uint256 len; 97 | bytes32 data; 98 | bytes32 expected; 99 | assembly { 100 | freememptr := str 101 | len := mload(str) 102 | data := mload(add(str, 32)) 103 | // the expected value should be the uft-8 encoding of 1 (49), 104 | // followed by clean bits. We achieve this by taking the value and 105 | // shifting left to the end of the 32 byte word 106 | expected := shl(248, 49) 107 | } 108 | emit log_named_uint("str: ", freememptr); 109 | emit log_named_uint("len: ", len); 110 | emit log_named_bytes32("data: ", data); 111 | assembly { 112 | freememptr := mload(0x40) 113 | } 114 | emit log_named_uint("memptr: ", freememptr); 115 | 116 | assertEq(data, expected); 117 | } 118 | } 119 | 120 | function toStringOZ(int256 value) pure returns (string memory) { 121 | return string(abi.encodePacked(value < 0 ? "-" : "", toStringOZ(absOZ(value)))); 122 | } 123 | 124 | function toStringOZ(uint256 value) pure returns (string memory) { 125 | if (value == 0) { 126 | return "0"; 127 | } 128 | uint256 temp = value; 129 | uint256 digits; 130 | while (temp != 0) { 131 | digits++; 132 | temp /= 10; 133 | } 134 | bytes memory buffer = new bytes(digits); 135 | while (value != 0) { 136 | digits -= 1; 137 | buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); 138 | value /= 10; 139 | } 140 | return string(buffer); 141 | } 142 | 143 | function absOZ(int256 n) pure returns (uint256) { 144 | unchecked { 145 | // must be unchecked in order to support `n = type(int256).min` 146 | return uint256(n >= 0 ? n : -n); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/test/MerkleProofLib.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | 6 | import {MerkleProofLib} from "../utils/MerkleProofLib.sol"; 7 | 8 | contract MerkleProofLibTest is DSTestPlus { 9 | function testVerifyEmptyMerkleProofSuppliedLeafAndRootSame() public { 10 | bytes32[] memory proof; 11 | assertBoolEq(this.verify(proof, 0x00, 0x00), true); 12 | } 13 | 14 | function testVerifyEmptyMerkleProofSuppliedLeafAndRootDifferent() public { 15 | bytes32[] memory proof; 16 | bytes32 leaf = "a"; 17 | assertBoolEq(this.verify(proof, 0x00, leaf), false); 18 | } 19 | 20 | function testValidProofSupplied() public { 21 | // Merkle tree created from leaves ['a', 'b', 'c']. 22 | // Leaf is 'a'. 23 | bytes32[] memory proof = new bytes32[](2); 24 | proof[0] = 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510; 25 | proof[1] = 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2; 26 | bytes32 root = 0x5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7; 27 | bytes32 leaf = 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb; 28 | assertBoolEq(this.verify(proof, root, leaf), true); 29 | } 30 | 31 | function testVerifyInvalidProofSupplied() public { 32 | // Merkle tree created from leaves ['a', 'b', 'c']. 33 | // Leaf is 'a'. 34 | // Proof is same as testValidProofSupplied but last byte of first element is modified. 35 | bytes32[] memory proof = new bytes32[](2); 36 | proof[0] = 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5511; 37 | proof[1] = 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2; 38 | bytes32 root = 0x5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7; 39 | bytes32 leaf = 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb; 40 | assertBoolEq(this.verify(proof, root, leaf), false); 41 | } 42 | 43 | function verify( 44 | bytes32[] calldata proof, 45 | bytes32 root, 46 | bytes32 leaf 47 | ) external pure returns (bool) { 48 | return MerkleProofLib.verify(proof, root, leaf); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/Owned.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | import {MockOwned} from "./utils/mocks/MockOwned.sol"; 6 | 7 | contract OwnedTest is DSTestPlus { 8 | MockOwned mockOwned; 9 | 10 | function setUp() public { 11 | mockOwned = new MockOwned(); 12 | } 13 | 14 | function testTransferOwnership() public { 15 | testTransferOwnership(address(0xBEEF)); 16 | } 17 | 18 | function testCallFunctionAsNonOwner() public { 19 | testCallFunctionAsNonOwner(address(0)); 20 | } 21 | 22 | function testCallFunctionAsOwner() public { 23 | mockOwned.updateFlag(); 24 | } 25 | 26 | function testTransferOwnership(address newOwner) public { 27 | mockOwned.transferOwnership(newOwner); 28 | 29 | assertEq(mockOwned.owner(), newOwner); 30 | } 31 | 32 | function testCallFunctionAsNonOwner(address owner) public { 33 | hevm.assume(owner != address(this)); 34 | 35 | mockOwned.transferOwnership(owner); 36 | 37 | hevm.expectRevert("UNAUTHORIZED"); 38 | mockOwned.updateFlag(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/ReentrancyGuard.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | 6 | import {ReentrancyGuard} from "../utils/ReentrancyGuard.sol"; 7 | 8 | contract RiskyContract is ReentrancyGuard { 9 | uint256 public enterTimes; 10 | 11 | function unprotectedCall() public { 12 | enterTimes++; 13 | 14 | if (enterTimes > 1) return; 15 | 16 | this.protectedCall(); 17 | } 18 | 19 | function protectedCall() public nonReentrant { 20 | enterTimes++; 21 | 22 | if (enterTimes > 1) return; 23 | 24 | this.protectedCall(); 25 | } 26 | 27 | function overprotectedCall() public nonReentrant {} 28 | } 29 | 30 | contract ReentrancyGuardTest is DSTestPlus { 31 | RiskyContract riskyContract; 32 | 33 | function setUp() public { 34 | riskyContract = new RiskyContract(); 35 | } 36 | 37 | function invariantReentrancyStatusAlways1() public { 38 | assertEq(uint256(hevm.load(address(riskyContract), 0)), 1); 39 | } 40 | 41 | function testFailUnprotectedCall() public { 42 | riskyContract.unprotectedCall(); 43 | 44 | assertEq(riskyContract.enterTimes(), 1); 45 | } 46 | 47 | function testProtectedCall() public { 48 | try riskyContract.protectedCall() { 49 | fail("Reentrancy Guard Failed To Stop Attacker"); 50 | } catch {} 51 | } 52 | 53 | function testNoReentrancy() public { 54 | riskyContract.overprotectedCall(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/RolesAuthority.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | import {MockAuthority} from "./utils/mocks/MockAuthority.sol"; 6 | 7 | import {Authority} from "../auth/Auth.sol"; 8 | 9 | import {RolesAuthority} from "../auth/authorities/RolesAuthority.sol"; 10 | 11 | contract RolesAuthorityTest is DSTestPlus { 12 | RolesAuthority rolesAuthority; 13 | 14 | function setUp() public { 15 | rolesAuthority = new RolesAuthority(address(this), Authority(address(0))); 16 | } 17 | 18 | function testSetRoles() public { 19 | assertFalse(rolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); 20 | 21 | rolesAuthority.setUserRole(address(0xBEEF), 0, true); 22 | assertTrue(rolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); 23 | 24 | rolesAuthority.setUserRole(address(0xBEEF), 0, false); 25 | assertFalse(rolesAuthority.doesUserHaveRole(address(0xBEEF), 0)); 26 | } 27 | 28 | function testSetRoleCapabilities() public { 29 | assertFalse(rolesAuthority.doesRoleHaveCapability(0, address(0xCAFE), 0xBEEFCAFE)); 30 | 31 | rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, true); 32 | assertTrue(rolesAuthority.doesRoleHaveCapability(0, address(0xCAFE), 0xBEEFCAFE)); 33 | 34 | rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, false); 35 | assertFalse(rolesAuthority.doesRoleHaveCapability(0, address(0xCAFE), 0xBEEFCAFE)); 36 | } 37 | 38 | function testSetPublicCapabilities() public { 39 | assertFalse(rolesAuthority.isCapabilityPublic(address(0xCAFE), 0xBEEFCAFE)); 40 | 41 | rolesAuthority.setPublicCapability(address(0xCAFE), 0xBEEFCAFE, true); 42 | assertTrue(rolesAuthority.isCapabilityPublic(address(0xCAFE), 0xBEEFCAFE)); 43 | 44 | rolesAuthority.setPublicCapability(address(0xCAFE), 0xBEEFCAFE, false); 45 | assertFalse(rolesAuthority.isCapabilityPublic(address(0xCAFE), 0xBEEFCAFE)); 46 | } 47 | 48 | function testCanCallWithAuthorizedRole() public { 49 | assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 50 | 51 | rolesAuthority.setUserRole(address(0xBEEF), 0, true); 52 | assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 53 | 54 | rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, true); 55 | assertTrue(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 56 | 57 | rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, false); 58 | assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 59 | 60 | rolesAuthority.setRoleCapability(0, address(0xCAFE), 0xBEEFCAFE, true); 61 | assertTrue(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 62 | 63 | rolesAuthority.setUserRole(address(0xBEEF), 0, false); 64 | assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 65 | } 66 | 67 | function testCanCallPublicCapability() public { 68 | assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 69 | 70 | rolesAuthority.setPublicCapability(address(0xCAFE), 0xBEEFCAFE, true); 71 | assertTrue(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 72 | 73 | rolesAuthority.setPublicCapability(address(0xCAFE), 0xBEEFCAFE, false); 74 | assertFalse(rolesAuthority.canCall(address(0xBEEF), address(0xCAFE), 0xBEEFCAFE)); 75 | } 76 | 77 | function testSetRoles(address user, uint8 role) public { 78 | assertFalse(rolesAuthority.doesUserHaveRole(user, role)); 79 | 80 | rolesAuthority.setUserRole(user, role, true); 81 | assertTrue(rolesAuthority.doesUserHaveRole(user, role)); 82 | 83 | rolesAuthority.setUserRole(user, role, false); 84 | assertFalse(rolesAuthority.doesUserHaveRole(user, role)); 85 | } 86 | 87 | function testSetRoleCapabilities( 88 | uint8 role, 89 | address target, 90 | bytes4 functionSig 91 | ) public { 92 | assertFalse(rolesAuthority.doesRoleHaveCapability(role, target, functionSig)); 93 | 94 | rolesAuthority.setRoleCapability(role, target, functionSig, true); 95 | assertTrue(rolesAuthority.doesRoleHaveCapability(role, target, functionSig)); 96 | 97 | rolesAuthority.setRoleCapability(role, target, functionSig, false); 98 | assertFalse(rolesAuthority.doesRoleHaveCapability(role, target, functionSig)); 99 | } 100 | 101 | function testSetPublicCapabilities(address target, bytes4 functionSig) public { 102 | assertFalse(rolesAuthority.isCapabilityPublic(target, functionSig)); 103 | 104 | rolesAuthority.setPublicCapability(target, functionSig, true); 105 | assertTrue(rolesAuthority.isCapabilityPublic(target, functionSig)); 106 | 107 | rolesAuthority.setPublicCapability(target, functionSig, false); 108 | assertFalse(rolesAuthority.isCapabilityPublic(target, functionSig)); 109 | } 110 | 111 | function testCanCallWithAuthorizedRole( 112 | address user, 113 | uint8 role, 114 | address target, 115 | bytes4 functionSig 116 | ) public { 117 | assertFalse(rolesAuthority.canCall(user, target, functionSig)); 118 | 119 | rolesAuthority.setUserRole(user, role, true); 120 | assertFalse(rolesAuthority.canCall(user, target, functionSig)); 121 | 122 | rolesAuthority.setRoleCapability(role, target, functionSig, true); 123 | assertTrue(rolesAuthority.canCall(user, target, functionSig)); 124 | 125 | rolesAuthority.setRoleCapability(role, target, functionSig, false); 126 | assertFalse(rolesAuthority.canCall(user, target, functionSig)); 127 | 128 | rolesAuthority.setRoleCapability(role, target, functionSig, true); 129 | assertTrue(rolesAuthority.canCall(user, target, functionSig)); 130 | 131 | rolesAuthority.setUserRole(user, role, false); 132 | assertFalse(rolesAuthority.canCall(user, target, functionSig)); 133 | } 134 | 135 | function testCanCallPublicCapability( 136 | address user, 137 | address target, 138 | bytes4 functionSig 139 | ) public { 140 | assertFalse(rolesAuthority.canCall(user, target, functionSig)); 141 | 142 | rolesAuthority.setPublicCapability(target, functionSig, true); 143 | assertTrue(rolesAuthority.canCall(user, target, functionSig)); 144 | 145 | rolesAuthority.setPublicCapability(target, functionSig, false); 146 | assertFalse(rolesAuthority.canCall(user, target, functionSig)); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/test/SSTORE2.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | 6 | import {SSTORE2} from "../utils/SSTORE2.sol"; 7 | 8 | contract SSTORE2Test is DSTestPlus { 9 | function testWriteRead() public { 10 | bytes memory testBytes = abi.encode("this is a test"); 11 | 12 | address pointer = SSTORE2.write(testBytes); 13 | 14 | assertBytesEq(SSTORE2.read(pointer), testBytes); 15 | } 16 | 17 | function testWriteReadFullStartBound() public { 18 | assertBytesEq(SSTORE2.read(SSTORE2.write(hex"11223344"), 0), hex"11223344"); 19 | } 20 | 21 | function testWriteReadCustomStartBound() public { 22 | assertBytesEq(SSTORE2.read(SSTORE2.write(hex"11223344"), 1), hex"223344"); 23 | } 24 | 25 | function testWriteReadFullBoundedRead() public { 26 | bytes memory testBytes = abi.encode("this is a test"); 27 | 28 | assertBytesEq(SSTORE2.read(SSTORE2.write(testBytes), 0, testBytes.length), testBytes); 29 | } 30 | 31 | function testWriteReadCustomBounds() public { 32 | assertBytesEq(SSTORE2.read(SSTORE2.write(hex"11223344"), 1, 3), hex"2233"); 33 | } 34 | 35 | function testWriteReadEmptyBound() public { 36 | SSTORE2.read(SSTORE2.write(hex"11223344"), 3, 3); 37 | } 38 | 39 | function testFailReadInvalidPointer() public view { 40 | SSTORE2.read(DEAD_ADDRESS); 41 | } 42 | 43 | function testFailReadInvalidPointerCustomStartBound() public view { 44 | SSTORE2.read(DEAD_ADDRESS, 1); 45 | } 46 | 47 | function testFailReadInvalidPointerCustomBounds() public view { 48 | SSTORE2.read(DEAD_ADDRESS, 2, 4); 49 | } 50 | 51 | function testFailWriteReadOutOfStartBound() public { 52 | SSTORE2.read(SSTORE2.write(hex"11223344"), 41000); 53 | } 54 | 55 | function testFailWriteReadEmptyOutOfBounds() public { 56 | SSTORE2.read(SSTORE2.write(hex"11223344"), 42000, 42000); 57 | } 58 | 59 | function testFailWriteReadOutOfBounds() public { 60 | SSTORE2.read(SSTORE2.write(hex"11223344"), 41000, 42000); 61 | } 62 | 63 | function testWriteRead(bytes calldata testBytes, bytes calldata brutalizeWith) 64 | public 65 | brutalizeMemory(brutalizeWith) 66 | { 67 | assertBytesEq(SSTORE2.read(SSTORE2.write(testBytes)), testBytes); 68 | } 69 | 70 | function testWriteReadCustomStartBound( 71 | bytes calldata testBytes, 72 | uint256 startIndex, 73 | bytes calldata brutalizeWith 74 | ) public brutalizeMemory(brutalizeWith) { 75 | if (testBytes.length == 0) return; 76 | 77 | startIndex = bound(startIndex, 0, testBytes.length); 78 | 79 | assertBytesEq(SSTORE2.read(SSTORE2.write(testBytes), startIndex), bytes(testBytes[startIndex:])); 80 | } 81 | 82 | function testWriteReadCustomBounds( 83 | bytes calldata testBytes, 84 | uint256 startIndex, 85 | uint256 endIndex, 86 | bytes calldata brutalizeWith 87 | ) public brutalizeMemory(brutalizeWith) { 88 | if (testBytes.length == 0) return; 89 | 90 | endIndex = bound(endIndex, 0, testBytes.length); 91 | startIndex = bound(startIndex, 0, testBytes.length); 92 | 93 | if (startIndex > endIndex) return; 94 | 95 | assertBytesEq( 96 | SSTORE2.read(SSTORE2.write(testBytes), startIndex, endIndex), 97 | bytes(testBytes[startIndex:endIndex]) 98 | ); 99 | } 100 | 101 | function testFailReadInvalidPointer(address pointer, bytes calldata brutalizeWith) 102 | public 103 | view 104 | brutalizeMemory(brutalizeWith) 105 | { 106 | if (pointer.code.length > 0) revert(); 107 | 108 | SSTORE2.read(pointer); 109 | } 110 | 111 | function testFailReadInvalidPointerCustomStartBound( 112 | address pointer, 113 | uint256 startIndex, 114 | bytes calldata brutalizeWith 115 | ) public view brutalizeMemory(brutalizeWith) { 116 | if (pointer.code.length > 0) revert(); 117 | 118 | SSTORE2.read(pointer, startIndex); 119 | } 120 | 121 | function testFailReadInvalidPointerCustomBounds( 122 | address pointer, 123 | uint256 startIndex, 124 | uint256 endIndex, 125 | bytes calldata brutalizeWith 126 | ) public view brutalizeMemory(brutalizeWith) { 127 | if (pointer.code.length > 0) revert(); 128 | 129 | SSTORE2.read(pointer, startIndex, endIndex); 130 | } 131 | 132 | function testFailWriteReadCustomStartBoundOutOfRange( 133 | bytes calldata testBytes, 134 | uint256 startIndex, 135 | bytes calldata brutalizeWith 136 | ) public brutalizeMemory(brutalizeWith) { 137 | startIndex = bound(startIndex, testBytes.length + 1, type(uint256).max); 138 | 139 | SSTORE2.read(SSTORE2.write(testBytes), startIndex); 140 | } 141 | 142 | function testFailWriteReadCustomBoundsOutOfRange( 143 | bytes calldata testBytes, 144 | uint256 startIndex, 145 | uint256 endIndex, 146 | bytes calldata brutalizeWith 147 | ) public brutalizeMemory(brutalizeWith) { 148 | endIndex = bound(endIndex, testBytes.length + 1, type(uint256).max); 149 | 150 | SSTORE2.read(SSTORE2.write(testBytes), startIndex, endIndex); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/test/SignedWadMath.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | 6 | import {wadMul, wadDiv} from "../utils/SignedWadMath.sol"; 7 | 8 | contract SignedWadMathTest is DSTestPlus { 9 | function testWadMul( 10 | uint256 x, 11 | uint256 y, 12 | bool negX, 13 | bool negY 14 | ) public { 15 | x = bound(x, 0, 99999999999999e18); 16 | y = bound(x, 0, 99999999999999e18); 17 | 18 | int256 xPrime = negX ? -int256(x) : int256(x); 19 | int256 yPrime = negY ? -int256(y) : int256(y); 20 | 21 | assertEq(wadMul(xPrime, yPrime), (xPrime * yPrime) / 1e18); 22 | } 23 | 24 | function testFailWadMulEdgeCase() public pure { 25 | int256 x = -1; 26 | int256 y = type(int256).min; 27 | 28 | wadMul(x, y); 29 | } 30 | 31 | function testFailWadMulEdgeCase2() public pure { 32 | int256 x = type(int256).min; 33 | int256 y = -1; 34 | 35 | wadMul(x, y); 36 | } 37 | 38 | function testFailWadMulOverflow(int256 x, int256 y) public pure { 39 | // Ignore cases where x * y does not overflow. 40 | unchecked { 41 | if ((x * y) / x == y) revert(); 42 | } 43 | 44 | wadMul(x, y); 45 | } 46 | 47 | function testWadDiv( 48 | uint256 x, 49 | uint256 y, 50 | bool negX, 51 | bool negY 52 | ) public { 53 | x = bound(x, 0, 99999999e18); 54 | y = bound(x, 1, 99999999e18); 55 | 56 | int256 xPrime = negX ? -int256(x) : int256(x); 57 | int256 yPrime = negY ? -int256(y) : int256(y); 58 | 59 | assertEq(wadDiv(xPrime, yPrime), (xPrime * 1e18) / yPrime); 60 | } 61 | 62 | function testFailWadDivOverflow(int256 x, int256 y) public pure { 63 | // Ignore cases where x * WAD does not overflow or y is 0. 64 | unchecked { 65 | if (y == 0 || (x * 1e18) / 1e18 == x) revert(); 66 | } 67 | 68 | wadDiv(x, y); 69 | } 70 | 71 | function testFailWadDivZeroDenominator(int256 x) public pure { 72 | wadDiv(x, 0); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/WETH.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity 0.8.15; 3 | 4 | import {DSTestPlus} from "./utils/DSTestPlus.sol"; 5 | import {DSInvariantTest} from "./utils/DSInvariantTest.sol"; 6 | 7 | import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; 8 | 9 | import {WETH} from "../tokens/WETH.sol"; 10 | 11 | contract WETHTest is DSTestPlus { 12 | WETH weth; 13 | 14 | function setUp() public { 15 | weth = new WETH(); 16 | } 17 | 18 | function testFallbackDeposit() public { 19 | assertEq(weth.balanceOf(address(this)), 0); 20 | assertEq(weth.totalSupply(), 0); 21 | 22 | SafeTransferLib.safeTransferETH(address(weth), 1 ether); 23 | 24 | assertEq(weth.balanceOf(address(this)), 1 ether); 25 | assertEq(weth.totalSupply(), 1 ether); 26 | } 27 | 28 | function testDeposit() public { 29 | assertEq(weth.balanceOf(address(this)), 0); 30 | assertEq(weth.totalSupply(), 0); 31 | 32 | weth.deposit{value: 1 ether}(); 33 | 34 | assertEq(weth.balanceOf(address(this)), 1 ether); 35 | assertEq(weth.totalSupply(), 1 ether); 36 | } 37 | 38 | function testWithdraw() public { 39 | uint256 startingBalance = address(this).balance; 40 | 41 | weth.deposit{value: 1 ether}(); 42 | 43 | weth.withdraw(1 ether); 44 | 45 | uint256 balanceAfterWithdraw = address(this).balance; 46 | 47 | assertEq(balanceAfterWithdraw, startingBalance); 48 | assertEq(weth.balanceOf(address(this)), 0); 49 | assertEq(weth.totalSupply(), 0); 50 | } 51 | 52 | function testPartialWithdraw() public { 53 | weth.deposit{value: 1 ether}(); 54 | 55 | uint256 balanceBeforeWithdraw = address(this).balance; 56 | 57 | weth.withdraw(0.5 ether); 58 | 59 | uint256 balanceAfterWithdraw = address(this).balance; 60 | 61 | assertEq(balanceAfterWithdraw, balanceBeforeWithdraw + 0.5 ether); 62 | assertEq(weth.balanceOf(address(this)), 0.5 ether); 63 | assertEq(weth.totalSupply(), 0.5 ether); 64 | } 65 | 66 | function testFallbackDeposit(uint256 amount) public { 67 | amount = bound(amount, 0, address(this).balance); 68 | 69 | assertEq(weth.balanceOf(address(this)), 0); 70 | assertEq(weth.totalSupply(), 0); 71 | 72 | SafeTransferLib.safeTransferETH(address(weth), amount); 73 | 74 | assertEq(weth.balanceOf(address(this)), amount); 75 | assertEq(weth.totalSupply(), amount); 76 | } 77 | 78 | function testDeposit(uint256 amount) public { 79 | amount = bound(amount, 0, address(this).balance); 80 | 81 | assertEq(weth.balanceOf(address(this)), 0); 82 | assertEq(weth.totalSupply(), 0); 83 | 84 | weth.deposit{value: amount}(); 85 | 86 | assertEq(weth.balanceOf(address(this)), amount); 87 | assertEq(weth.totalSupply(), amount); 88 | } 89 | 90 | function testWithdraw(uint256 depositAmount, uint256 withdrawAmount) public { 91 | depositAmount = bound(depositAmount, 0, address(this).balance); 92 | withdrawAmount = bound(withdrawAmount, 0, depositAmount); 93 | 94 | weth.deposit{value: depositAmount}(); 95 | 96 | uint256 balanceBeforeWithdraw = address(this).balance; 97 | 98 | weth.withdraw(withdrawAmount); 99 | 100 | uint256 balanceAfterWithdraw = address(this).balance; 101 | 102 | assertEq(balanceAfterWithdraw, balanceBeforeWithdraw + withdrawAmount); 103 | assertEq(weth.balanceOf(address(this)), depositAmount - withdrawAmount); 104 | assertEq(weth.totalSupply(), depositAmount - withdrawAmount); 105 | } 106 | 107 | receive() external payable {} 108 | } 109 | 110 | contract WETHInvariants is DSTestPlus, DSInvariantTest { 111 | WETHTester wethTester; 112 | WETH weth; 113 | 114 | function setUp() public { 115 | weth = new WETH(); 116 | wethTester = new WETHTester{value: address(this).balance}(weth); 117 | 118 | addTargetContract(address(wethTester)); 119 | } 120 | 121 | function invariantTotalSupplyEqualsBalance() public { 122 | assertEq(address(weth).balance, weth.totalSupply()); 123 | } 124 | } 125 | 126 | contract WETHTester { 127 | WETH weth; 128 | 129 | constructor(WETH _weth) payable { 130 | weth = _weth; 131 | } 132 | 133 | function deposit(uint256 amount) public { 134 | weth.deposit{value: amount}(); 135 | } 136 | 137 | function fallbackDeposit(uint256 amount) public { 138 | SafeTransferLib.safeTransferETH(address(weth), amount); 139 | } 140 | 141 | function withdraw(uint256 amount) public { 142 | weth.withdraw(amount); 143 | } 144 | 145 | receive() external payable {} 146 | } 147 | -------------------------------------------------------------------------------- /src/test/utils/DSInvariantTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | contract DSInvariantTest { 5 | address[] private targets; 6 | 7 | function targetContracts() public view virtual returns (address[] memory) { 8 | require(targets.length > 0, "NO_TARGET_CONTRACTS"); 9 | 10 | return targets; 11 | } 12 | 13 | function addTargetContract(address newTargetContract) internal virtual { 14 | targets.push(newTargetContract); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/utils/DSTestPlus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {DSTest} from "ds-test/test.sol"; 5 | 6 | import {Hevm} from "./Hevm.sol"; 7 | 8 | /// @notice Extended testing framework for DappTools projects. 9 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/test/utils/DSTestPlus.sol) 10 | contract DSTestPlus is DSTest { 11 | Hevm internal constant hevm = Hevm(HEVM_ADDRESS); 12 | 13 | address internal constant DEAD_ADDRESS = 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF; 14 | 15 | string private checkpointLabel; 16 | uint256 private checkpointGasLeft = 1; // Start the slot warm. 17 | 18 | modifier brutalizeMemory(bytes memory brutalizeWith) { 19 | /// @solidity memory-safe-assembly 20 | assembly { 21 | // Fill the 64 bytes of scratch space with the data. 22 | pop( 23 | staticcall( 24 | gas(), // Pass along all the gas in the call. 25 | 0x04, // Call the identity precompile address. 26 | brutalizeWith, // Offset is the bytes' pointer. 27 | 64, // Copy enough to only fill the scratch space. 28 | 0, // Store the return value in the scratch space. 29 | 64 // Scratch space is only 64 bytes in size, we don't want to write further. 30 | ) 31 | ) 32 | 33 | let size := add(mload(brutalizeWith), 32) // Add 32 to include the 32 byte length slot. 34 | 35 | // Fill the free memory pointer's destination with the data. 36 | pop( 37 | staticcall( 38 | gas(), // Pass along all the gas in the call. 39 | 0x04, // Call the identity precompile address. 40 | brutalizeWith, // Offset is the bytes' pointer. 41 | size, // We want to pass the length of the bytes. 42 | mload(0x40), // Store the return value at the free memory pointer. 43 | size // Since the precompile just returns its input, we reuse size. 44 | ) 45 | ) 46 | } 47 | 48 | _; 49 | } 50 | 51 | function startMeasuringGas(string memory label) internal virtual { 52 | checkpointLabel = label; 53 | 54 | checkpointGasLeft = gasleft(); 55 | } 56 | 57 | function stopMeasuringGas() internal virtual { 58 | uint256 checkpointGasLeft2 = gasleft(); 59 | 60 | // Subtract 100 to account for the warm SLOAD in startMeasuringGas. 61 | uint256 gasDelta = checkpointGasLeft - checkpointGasLeft2 - 100; 62 | 63 | emit log_named_uint(string(abi.encodePacked(checkpointLabel, " Gas")), gasDelta); 64 | } 65 | 66 | function fail(string memory err) internal virtual { 67 | emit log_named_string("Error", err); 68 | fail(); 69 | } 70 | 71 | function assertFalse(bool data) internal virtual { 72 | assertTrue(!data); 73 | } 74 | 75 | function assertUint128Eq(uint128 a, uint128 b) internal virtual { 76 | assertEq(uint256(a), uint256(b)); 77 | } 78 | 79 | function assertUint64Eq(uint64 a, uint64 b) internal virtual { 80 | assertEq(uint256(a), uint256(b)); 81 | } 82 | 83 | function assertUint96Eq(uint96 a, uint96 b) internal virtual { 84 | assertEq(uint256(a), uint256(b)); 85 | } 86 | 87 | function assertUint32Eq(uint32 a, uint32 b) internal virtual { 88 | assertEq(uint256(a), uint256(b)); 89 | } 90 | 91 | function assertBoolEq(bool a, bool b) internal virtual { 92 | b ? assertTrue(a) : assertFalse(a); 93 | } 94 | 95 | function assertApproxEq( 96 | uint256 a, 97 | uint256 b, 98 | uint256 maxDelta 99 | ) internal virtual { 100 | uint256 delta = a > b ? a - b : b - a; 101 | 102 | if (delta > maxDelta) { 103 | emit log("Error: a ~= b not satisfied [uint]"); 104 | emit log_named_uint(" Expected", b); 105 | emit log_named_uint(" Actual", a); 106 | emit log_named_uint(" Max Delta", maxDelta); 107 | emit log_named_uint(" Delta", delta); 108 | fail(); 109 | } 110 | } 111 | 112 | function assertRelApproxEq( 113 | uint256 a, 114 | uint256 b, 115 | uint256 maxPercentDelta // An 18 decimal fixed point number, where 1e18 == 100% 116 | ) internal virtual { 117 | if (b == 0) return assertEq(a, b); // If the expected is 0, actual must be too. 118 | 119 | uint256 percentDelta = ((a > b ? a - b : b - a) * 1e18) / b; 120 | 121 | if (percentDelta > maxPercentDelta) { 122 | emit log("Error: a ~= b not satisfied [uint]"); 123 | emit log_named_uint(" Expected", b); 124 | emit log_named_uint(" Actual", a); 125 | emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, 18); 126 | emit log_named_decimal_uint(" % Delta", percentDelta, 18); 127 | fail(); 128 | } 129 | } 130 | 131 | function assertBytesEq(bytes memory a, bytes memory b) internal virtual { 132 | if (keccak256(a) != keccak256(b)) { 133 | emit log("Error: a == b not satisfied [bytes]"); 134 | emit log_named_bytes(" Expected", b); 135 | emit log_named_bytes(" Actual", a); 136 | fail(); 137 | } 138 | } 139 | 140 | function assertUintArrayEq(uint256[] memory a, uint256[] memory b) internal virtual { 141 | require(a.length == b.length, "LENGTH_MISMATCH"); 142 | 143 | for (uint256 i = 0; i < a.length; i++) { 144 | assertEq(a[i], b[i]); 145 | } 146 | } 147 | 148 | function bound( 149 | uint256 x, 150 | uint256 min, 151 | uint256 max 152 | ) internal virtual returns (uint256 result) { 153 | require(max >= min, "MAX_LESS_THAN_MIN"); 154 | 155 | uint256 size = max - min; 156 | 157 | if (size == 0) result = min; 158 | else if (size == type(uint256).max) result = x; 159 | else { 160 | ++size; // Make max inclusive. 161 | uint256 mod = x % size; 162 | result = min + mod; 163 | } 164 | 165 | emit log_named_uint("Bound Result", result); 166 | } 167 | 168 | function min3( 169 | uint256 a, 170 | uint256 b, 171 | uint256 c 172 | ) internal pure returns (uint256) { 173 | return a > b ? (b > c ? c : b) : (a > c ? c : a); 174 | } 175 | 176 | function min2(uint256 a, uint256 b) internal pure returns (uint256) { 177 | return a > b ? b : a; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/test/utils/Hevm.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | interface Hevm { 5 | /// @notice Sets the block timestamp. 6 | function warp(uint256) external; 7 | 8 | /// @notice Sets the block height. 9 | function roll(uint256) external; 10 | 11 | /// @notice Sets the block base fee. 12 | function fee(uint256) external; 13 | 14 | /// @notice Loads a storage slot from an address. 15 | function load(address, bytes32) external returns (bytes32); 16 | 17 | /// @notice Stores a value to an address' storage slot. 18 | function store( 19 | address, 20 | bytes32, 21 | bytes32 22 | ) external; 23 | 24 | /// @notice Signs a digest with a private key, returns v r s. 25 | function sign(uint256, bytes32) 26 | external 27 | returns ( 28 | uint8, 29 | bytes32, 30 | bytes32 31 | ); 32 | 33 | /// @notice Gets address for a given private key. 34 | function addr(uint256) external returns (address); 35 | 36 | /// @notice Performs a foreign function call via a terminal call. 37 | function ffi(string[] calldata) external returns (bytes memory); 38 | 39 | /// @notice Sets the next call's msg.sender to be the input address. 40 | function prank(address) external; 41 | 42 | /// @notice Sets all subsequent calls' msg.sender to be the input address until stopPrank is called. 43 | function startPrank(address) external; 44 | 45 | /// @notice Sets the next call's msg.sender to be the input address and the tx.origin to be the second input. 46 | function prank(address, address) external; 47 | 48 | /// @notice Sets all subsequent calls' msg.sender to be the input address and 49 | /// sets tx.origin to be the second address inputted until stopPrank is called. 50 | function startPrank(address, address) external; 51 | 52 | /// @notice Resets msg.sender to its original value before a prank. 53 | function stopPrank() external; 54 | 55 | /// @notice Sets an address' balance. 56 | function deal(address, uint256) external; 57 | 58 | /// @notice Sets an address' code. 59 | function etch(address, bytes calldata) external; 60 | 61 | /// @notice Expects an error from the next call. 62 | function expectRevert(bytes calldata) external; 63 | 64 | /// @notice Expects a revert from the next call. 65 | function expectRevert(bytes4) external; 66 | 67 | /// @notice Record all storage reads and writes. 68 | function record() external; 69 | 70 | /// @notice Gets all accessed reads and write slots from a recording session, for a given address. 71 | function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); 72 | 73 | /// @notice Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). 74 | /// @notice Call this function, then emit an event, then call a function. Internally after the call, we check 75 | /// if logs were emitted in the expected order with the expected topics and data as specified by the booleans. 76 | function expectEmit( 77 | bool, 78 | bool, 79 | bool, 80 | bool 81 | ) external; 82 | 83 | /// @notice Mocks the behavior of a contract call, setting the input and output for a function. 84 | /// @notice Calldata can either be strict or a partial match, e.g. if only passed 85 | /// a selector to the expected calldata, then the entire function will be mocked. 86 | function mockCall( 87 | address, 88 | bytes calldata, 89 | bytes calldata 90 | ) external; 91 | 92 | /// @notice Clears all mocked calls. 93 | function clearMockedCalls() external; 94 | 95 | /// @notice Expect a call to an address with the specified calldata. 96 | /// @notice Calldata can either be strict or a partial match. 97 | function expectCall(address, bytes calldata) external; 98 | 99 | /// @notice Fetches the contract bytecode from its artifact file. 100 | function getCode(string calldata) external returns (bytes memory); 101 | 102 | /// @notice Label an address in test traces. 103 | function label(address addr, string calldata label) external; 104 | 105 | /// @notice When fuzzing, generate new inputs if the input conditional is not met. 106 | function assume(bool) external; 107 | } 108 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MockAuthChild.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {Auth, Authority} from "../../../auth/Auth.sol"; 5 | 6 | contract MockAuthChild is Auth(msg.sender, Authority(address(0))) { 7 | bool public flag; 8 | 9 | function updateFlag() public virtual requiresAuth { 10 | flag = true; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MockAuthority.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {Authority} from "../../../auth/Auth.sol"; 5 | 6 | contract MockAuthority is Authority { 7 | bool immutable allowCalls; 8 | 9 | constructor(bool _allowCalls) { 10 | allowCalls = _allowCalls; 11 | } 12 | 13 | function canCall( 14 | address, 15 | address, 16 | bytes4 17 | ) public view override returns (bool) { 18 | return allowCalls; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MockERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {ERC1155} from "../../../tokens/ERC1155.sol"; 5 | 6 | contract MockERC1155 is ERC1155 { 7 | function uri(uint256) public pure virtual override returns (string memory) {} 8 | 9 | function mint( 10 | address to, 11 | uint256 id, 12 | uint256 amount, 13 | bytes memory data 14 | ) public virtual { 15 | _mint(to, id, amount, data); 16 | } 17 | 18 | function batchMint( 19 | address to, 20 | uint256[] memory ids, 21 | uint256[] memory amounts, 22 | bytes memory data 23 | ) public virtual { 24 | _batchMint(to, ids, amounts, data); 25 | } 26 | 27 | function burn( 28 | address from, 29 | uint256 id, 30 | uint256 amount 31 | ) public virtual { 32 | _burn(from, id, amount); 33 | } 34 | 35 | function batchBurn( 36 | address from, 37 | uint256[] memory ids, 38 | uint256[] memory amounts 39 | ) public virtual { 40 | _batchBurn(from, ids, amounts); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {ERC20} from "../../../tokens/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20 { 7 | constructor( 8 | string memory _name, 9 | string memory _symbol, 10 | uint8 _decimals 11 | ) ERC20(_name, _symbol, _decimals) {} 12 | 13 | function mint(address to, uint256 value) public virtual { 14 | _mint(to, value); 15 | } 16 | 17 | function burn(address from, uint256 value) public virtual { 18 | _burn(from, value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MockERC4626.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {ERC20} from "../../../tokens/ERC20.sol"; 5 | import {ERC4626} from "../../../tokens/ERC4626.sol"; 6 | 7 | contract MockERC4626 is ERC4626 { 8 | uint256 public beforeWithdrawHookCalledCounter = 0; 9 | uint256 public afterDepositHookCalledCounter = 0; 10 | 11 | constructor( 12 | ERC20 _underlying, 13 | string memory _name, 14 | string memory _symbol 15 | ) ERC4626(_underlying, _name, _symbol) {} 16 | 17 | function totalAssets() public view override returns (uint256) { 18 | return asset.balanceOf(address(this)); 19 | } 20 | 21 | function beforeWithdraw(uint256, uint256) internal override { 22 | beforeWithdrawHookCalledCounter++; 23 | } 24 | 25 | function afterDeposit(uint256, uint256) internal override { 26 | afterDepositHookCalledCounter++; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MockERC6909.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | import {ERC6909} from "../../../tokens/ERC6909.sol"; 5 | 6 | contract MockERC6909 is ERC6909 { 7 | function mint( 8 | address receiver, 9 | uint256 id, 10 | uint256 amount 11 | ) public virtual { 12 | _mint(receiver, id, amount); 13 | } 14 | 15 | function burn( 16 | address sender, 17 | uint256 id, 18 | uint256 amount 19 | ) public virtual { 20 | _burn(sender, id, amount); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MockERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {ERC721} from "../../../tokens/ERC721.sol"; 5 | 6 | contract MockERC721 is ERC721 { 7 | constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} 8 | 9 | function tokenURI(uint256) public pure virtual override returns (string memory) {} 10 | 11 | function mint(address to, uint256 tokenId) public virtual { 12 | _mint(to, tokenId); 13 | } 14 | 15 | function burn(uint256 tokenId) public virtual { 16 | _burn(tokenId); 17 | } 18 | 19 | function safeMint(address to, uint256 tokenId) public virtual { 20 | _safeMint(to, tokenId); 21 | } 22 | 23 | function safeMint( 24 | address to, 25 | uint256 tokenId, 26 | bytes memory data 27 | ) public virtual { 28 | _safeMint(to, tokenId, data); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/utils/mocks/MockOwned.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {Owned} from "../../../auth/Owned.sol"; 5 | 6 | contract MockOwned is Owned(msg.sender) { 7 | bool public flag; 8 | 9 | function updateFlag() public virtual onlyOwner { 10 | flag = true; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/utils/weird-tokens/MissingReturnToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | contract MissingReturnToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "MissingReturnToken"; 18 | 19 | string public constant symbol = "MRT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address spender, uint256 amount) public virtual { 47 | allowance[msg.sender][spender] = amount; 48 | 49 | emit Approval(msg.sender, spender, amount); 50 | } 51 | 52 | function transfer(address to, uint256 amount) public virtual { 53 | balanceOf[msg.sender] -= amount; 54 | 55 | // Cannot overflow because the sum of all user 56 | // balances can't exceed the max uint256 value. 57 | unchecked { 58 | balanceOf[to] += amount; 59 | } 60 | 61 | emit Transfer(msg.sender, to, amount); 62 | } 63 | 64 | function transferFrom( 65 | address from, 66 | address to, 67 | uint256 amount 68 | ) public virtual { 69 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 70 | 71 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 72 | 73 | balanceOf[from] -= amount; 74 | 75 | // Cannot overflow because the sum of all user 76 | // balances can't exceed the max uint256 value. 77 | unchecked { 78 | balanceOf[to] += amount; 79 | } 80 | 81 | emit Transfer(from, to, amount); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/utils/weird-tokens/ReturnsFalseToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | contract ReturnsFalseToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsFalseToken"; 18 | 19 | string public constant symbol = "RFT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address, uint256) public virtual returns (bool) { 47 | return false; 48 | } 49 | 50 | function transfer(address, uint256) public virtual returns (bool) { 51 | return false; 52 | } 53 | 54 | function transferFrom( 55 | address, 56 | address, 57 | uint256 58 | ) public virtual returns (bool) { 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/utils/weird-tokens/ReturnsGarbageToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | contract ReturnsGarbageToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsGarbageToken"; 18 | 19 | string public constant symbol = "RGT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | MOCK STORAGE 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | bytes garbage; 38 | 39 | /*/////////////////////////////////////////////////////////////// 40 | CONSTRUCTOR 41 | //////////////////////////////////////////////////////////////*/ 42 | 43 | constructor() { 44 | totalSupply = type(uint256).max; 45 | balanceOf[msg.sender] = type(uint256).max; 46 | } 47 | 48 | /*/////////////////////////////////////////////////////////////// 49 | ERC20 LOGIC 50 | //////////////////////////////////////////////////////////////*/ 51 | 52 | function approve(address spender, uint256 amount) public virtual { 53 | allowance[msg.sender][spender] = amount; 54 | 55 | emit Approval(msg.sender, spender, amount); 56 | 57 | bytes memory _garbage = garbage; 58 | 59 | assembly { 60 | return(add(_garbage, 32), mload(_garbage)) 61 | } 62 | } 63 | 64 | function transfer(address to, uint256 amount) public virtual { 65 | balanceOf[msg.sender] -= amount; 66 | 67 | // Cannot overflow because the sum of all user 68 | // balances can't exceed the max uint256 value. 69 | unchecked { 70 | balanceOf[to] += amount; 71 | } 72 | 73 | emit Transfer(msg.sender, to, amount); 74 | 75 | bytes memory _garbage = garbage; 76 | 77 | assembly { 78 | return(add(_garbage, 32), mload(_garbage)) 79 | } 80 | } 81 | 82 | function transferFrom( 83 | address from, 84 | address to, 85 | uint256 amount 86 | ) public virtual { 87 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 88 | 89 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 90 | 91 | balanceOf[from] -= amount; 92 | 93 | // Cannot overflow because the sum of all user 94 | // balances can't exceed the max uint256 value. 95 | unchecked { 96 | balanceOf[to] += amount; 97 | } 98 | 99 | emit Transfer(from, to, amount); 100 | 101 | bytes memory _garbage = garbage; 102 | 103 | assembly { 104 | return(add(_garbage, 32), mload(_garbage)) 105 | } 106 | } 107 | 108 | /*/////////////////////////////////////////////////////////////// 109 | MOCK LOGIC 110 | //////////////////////////////////////////////////////////////*/ 111 | 112 | function setGarbage(bytes memory _garbage) public virtual { 113 | garbage = _garbage; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/utils/weird-tokens/ReturnsTooLittleToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | contract ReturnsTooLittleToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsTooLittleToken"; 18 | 19 | string public constant symbol = "RTLT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address, uint256) public virtual { 47 | assembly { 48 | mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) 49 | return(0, 8) 50 | } 51 | } 52 | 53 | function transfer(address, uint256) public virtual { 54 | assembly { 55 | mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) 56 | return(0, 8) 57 | } 58 | } 59 | 60 | function transferFrom( 61 | address, 62 | address, 63 | uint256 64 | ) public virtual { 65 | assembly { 66 | mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) 67 | return(0, 8) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/utils/weird-tokens/ReturnsTooMuchToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | contract ReturnsTooMuchToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsTooMuchToken"; 18 | 19 | string public constant symbol = "RTMT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address spender, uint256 amount) public virtual { 47 | allowance[msg.sender][spender] = amount; 48 | 49 | emit Approval(msg.sender, spender, amount); 50 | 51 | assembly { 52 | mstore(0, 1) 53 | return(0, 4096) 54 | } 55 | } 56 | 57 | function transfer(address to, uint256 amount) public virtual { 58 | balanceOf[msg.sender] -= amount; 59 | 60 | // Cannot overflow because the sum of all user 61 | // balances can't exceed the max uint256 value. 62 | unchecked { 63 | balanceOf[to] += amount; 64 | } 65 | 66 | emit Transfer(msg.sender, to, amount); 67 | 68 | assembly { 69 | mstore(0, 1) 70 | return(0, 4096) 71 | } 72 | } 73 | 74 | function transferFrom( 75 | address from, 76 | address to, 77 | uint256 amount 78 | ) public virtual { 79 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 80 | 81 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 82 | 83 | balanceOf[from] -= amount; 84 | 85 | // Cannot overflow because the sum of all user 86 | // balances can't exceed the max uint256 value. 87 | unchecked { 88 | balanceOf[to] += amount; 89 | } 90 | 91 | emit Transfer(from, to, amount); 92 | 93 | assembly { 94 | mstore(0, 1) 95 | return(0, 4096) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/utils/weird-tokens/ReturnsTwoToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | contract ReturnsTwoToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsFalseToken"; 18 | 19 | string public constant symbol = "RTT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address, uint256) public virtual returns (uint256) { 47 | return 2; 48 | } 49 | 50 | function transfer(address, uint256) public virtual returns (uint256) { 51 | return 2; 52 | } 53 | 54 | function transferFrom( 55 | address, 56 | address, 57 | uint256 58 | ) public virtual returns (uint256) { 59 | return 2; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/utils/weird-tokens/RevertingToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | contract RevertingToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "RevertingToken"; 18 | 19 | string public constant symbol = "RT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address, uint256) public virtual { 47 | revert(); 48 | } 49 | 50 | function transfer(address, uint256) public virtual { 51 | revert(); 52 | } 53 | 54 | function transferFrom( 55 | address, 56 | address, 57 | uint256 58 | ) public virtual { 59 | revert(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/tokens/ERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Minimalist and gas efficient standard ERC1155 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol) 6 | abstract contract ERC1155 { 7 | /*////////////////////////////////////////////////////////////// 8 | EVENTS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | event TransferSingle( 12 | address indexed operator, 13 | address indexed from, 14 | address indexed to, 15 | uint256 id, 16 | uint256 amount 17 | ); 18 | 19 | event TransferBatch( 20 | address indexed operator, 21 | address indexed from, 22 | address indexed to, 23 | uint256[] ids, 24 | uint256[] amounts 25 | ); 26 | 27 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 28 | 29 | event URI(string value, uint256 indexed id); 30 | 31 | /*////////////////////////////////////////////////////////////// 32 | ERC1155 STORAGE 33 | //////////////////////////////////////////////////////////////*/ 34 | 35 | mapping(address => mapping(uint256 => uint256)) public balanceOf; 36 | 37 | mapping(address => mapping(address => bool)) public isApprovedForAll; 38 | 39 | /*////////////////////////////////////////////////////////////// 40 | METADATA LOGIC 41 | //////////////////////////////////////////////////////////////*/ 42 | 43 | function uri(uint256 id) public view virtual returns (string memory); 44 | 45 | /*////////////////////////////////////////////////////////////// 46 | ERC1155 LOGIC 47 | //////////////////////////////////////////////////////////////*/ 48 | 49 | function setApprovalForAll(address operator, bool approved) public virtual { 50 | isApprovedForAll[msg.sender][operator] = approved; 51 | 52 | emit ApprovalForAll(msg.sender, operator, approved); 53 | } 54 | 55 | function safeTransferFrom( 56 | address from, 57 | address to, 58 | uint256 id, 59 | uint256 amount, 60 | bytes calldata data 61 | ) public virtual { 62 | require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED"); 63 | 64 | balanceOf[from][id] -= amount; 65 | balanceOf[to][id] += amount; 66 | 67 | emit TransferSingle(msg.sender, from, to, id, amount); 68 | 69 | require( 70 | to.code.length == 0 71 | ? to != address(0) 72 | : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) == 73 | ERC1155TokenReceiver.onERC1155Received.selector, 74 | "UNSAFE_RECIPIENT" 75 | ); 76 | } 77 | 78 | function safeBatchTransferFrom( 79 | address from, 80 | address to, 81 | uint256[] calldata ids, 82 | uint256[] calldata amounts, 83 | bytes calldata data 84 | ) public virtual { 85 | require(ids.length == amounts.length, "LENGTH_MISMATCH"); 86 | 87 | require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED"); 88 | 89 | // Storing these outside the loop saves ~15 gas per iteration. 90 | uint256 id; 91 | uint256 amount; 92 | 93 | for (uint256 i = 0; i < ids.length; ) { 94 | id = ids[i]; 95 | amount = amounts[i]; 96 | 97 | balanceOf[from][id] -= amount; 98 | balanceOf[to][id] += amount; 99 | 100 | // An array can't have a total length 101 | // larger than the max uint256 value. 102 | unchecked { 103 | ++i; 104 | } 105 | } 106 | 107 | emit TransferBatch(msg.sender, from, to, ids, amounts); 108 | 109 | require( 110 | to.code.length == 0 111 | ? to != address(0) 112 | : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) == 113 | ERC1155TokenReceiver.onERC1155BatchReceived.selector, 114 | "UNSAFE_RECIPIENT" 115 | ); 116 | } 117 | 118 | function balanceOfBatch(address[] calldata owners, uint256[] calldata ids) 119 | public 120 | view 121 | virtual 122 | returns (uint256[] memory balances) 123 | { 124 | require(owners.length == ids.length, "LENGTH_MISMATCH"); 125 | 126 | balances = new uint256[](owners.length); 127 | 128 | // Unchecked because the only math done is incrementing 129 | // the array index counter which cannot possibly overflow. 130 | unchecked { 131 | for (uint256 i = 0; i < owners.length; ++i) { 132 | balances[i] = balanceOf[owners[i]][ids[i]]; 133 | } 134 | } 135 | } 136 | 137 | /*////////////////////////////////////////////////////////////// 138 | ERC165 LOGIC 139 | //////////////////////////////////////////////////////////////*/ 140 | 141 | function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { 142 | return 143 | interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 144 | interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155 145 | interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI 146 | } 147 | 148 | /*////////////////////////////////////////////////////////////// 149 | INTERNAL MINT/BURN LOGIC 150 | //////////////////////////////////////////////////////////////*/ 151 | 152 | function _mint( 153 | address to, 154 | uint256 id, 155 | uint256 amount, 156 | bytes memory data 157 | ) internal virtual { 158 | balanceOf[to][id] += amount; 159 | 160 | emit TransferSingle(msg.sender, address(0), to, id, amount); 161 | 162 | require( 163 | to.code.length == 0 164 | ? to != address(0) 165 | : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, address(0), id, amount, data) == 166 | ERC1155TokenReceiver.onERC1155Received.selector, 167 | "UNSAFE_RECIPIENT" 168 | ); 169 | } 170 | 171 | function _batchMint( 172 | address to, 173 | uint256[] memory ids, 174 | uint256[] memory amounts, 175 | bytes memory data 176 | ) internal virtual { 177 | uint256 idsLength = ids.length; // Saves MLOADs. 178 | 179 | require(idsLength == amounts.length, "LENGTH_MISMATCH"); 180 | 181 | for (uint256 i = 0; i < idsLength; ) { 182 | balanceOf[to][ids[i]] += amounts[i]; 183 | 184 | // An array can't have a total length 185 | // larger than the max uint256 value. 186 | unchecked { 187 | ++i; 188 | } 189 | } 190 | 191 | emit TransferBatch(msg.sender, address(0), to, ids, amounts); 192 | 193 | require( 194 | to.code.length == 0 195 | ? to != address(0) 196 | : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, address(0), ids, amounts, data) == 197 | ERC1155TokenReceiver.onERC1155BatchReceived.selector, 198 | "UNSAFE_RECIPIENT" 199 | ); 200 | } 201 | 202 | function _batchBurn( 203 | address from, 204 | uint256[] memory ids, 205 | uint256[] memory amounts 206 | ) internal virtual { 207 | uint256 idsLength = ids.length; // Saves MLOADs. 208 | 209 | require(idsLength == amounts.length, "LENGTH_MISMATCH"); 210 | 211 | for (uint256 i = 0; i < idsLength; ) { 212 | balanceOf[from][ids[i]] -= amounts[i]; 213 | 214 | // An array can't have a total length 215 | // larger than the max uint256 value. 216 | unchecked { 217 | ++i; 218 | } 219 | } 220 | 221 | emit TransferBatch(msg.sender, from, address(0), ids, amounts); 222 | } 223 | 224 | function _burn( 225 | address from, 226 | uint256 id, 227 | uint256 amount 228 | ) internal virtual { 229 | balanceOf[from][id] -= amount; 230 | 231 | emit TransferSingle(msg.sender, from, address(0), id, amount); 232 | } 233 | } 234 | 235 | /// @notice A generic interface for a contract which properly accepts ERC1155 tokens. 236 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol) 237 | abstract contract ERC1155TokenReceiver { 238 | function onERC1155Received( 239 | address, 240 | address, 241 | uint256, 242 | uint256, 243 | bytes calldata 244 | ) external virtual returns (bytes4) { 245 | return ERC1155TokenReceiver.onERC1155Received.selector; 246 | } 247 | 248 | function onERC1155BatchReceived( 249 | address, 250 | address, 251 | uint256[] calldata, 252 | uint256[] calldata, 253 | bytes calldata 254 | ) external virtual returns (bytes4) { 255 | return ERC1155TokenReceiver.onERC1155BatchReceived.selector; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/tokens/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) 6 | /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) 7 | /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. 8 | abstract contract ERC20 { 9 | /*////////////////////////////////////////////////////////////// 10 | EVENTS 11 | //////////////////////////////////////////////////////////////*/ 12 | 13 | event Transfer(address indexed from, address indexed to, uint256 amount); 14 | 15 | event Approval(address indexed owner, address indexed spender, uint256 amount); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | METADATA STORAGE 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | string public name; 22 | 23 | string public symbol; 24 | 25 | uint8 public immutable decimals; 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | ERC20 STORAGE 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | uint256 public totalSupply; 32 | 33 | mapping(address => uint256) public balanceOf; 34 | 35 | mapping(address => mapping(address => uint256)) public allowance; 36 | 37 | /*////////////////////////////////////////////////////////////// 38 | EIP-2612 STORAGE 39 | //////////////////////////////////////////////////////////////*/ 40 | 41 | uint256 internal immutable INITIAL_CHAIN_ID; 42 | 43 | bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; 44 | 45 | mapping(address => uint256) public nonces; 46 | 47 | /*////////////////////////////////////////////////////////////// 48 | CONSTRUCTOR 49 | //////////////////////////////////////////////////////////////*/ 50 | 51 | constructor( 52 | string memory _name, 53 | string memory _symbol, 54 | uint8 _decimals 55 | ) { 56 | name = _name; 57 | symbol = _symbol; 58 | decimals = _decimals; 59 | 60 | INITIAL_CHAIN_ID = block.chainid; 61 | INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); 62 | } 63 | 64 | /*////////////////////////////////////////////////////////////// 65 | ERC20 LOGIC 66 | //////////////////////////////////////////////////////////////*/ 67 | 68 | function approve(address spender, uint256 amount) public virtual returns (bool) { 69 | allowance[msg.sender][spender] = amount; 70 | 71 | emit Approval(msg.sender, spender, amount); 72 | 73 | return true; 74 | } 75 | 76 | function transfer(address to, uint256 amount) public virtual returns (bool) { 77 | balanceOf[msg.sender] -= amount; 78 | 79 | // Cannot overflow because the sum of all user 80 | // balances can't exceed the max uint256 value. 81 | unchecked { 82 | balanceOf[to] += amount; 83 | } 84 | 85 | emit Transfer(msg.sender, to, amount); 86 | 87 | return true; 88 | } 89 | 90 | function transferFrom( 91 | address from, 92 | address to, 93 | uint256 amount 94 | ) public virtual returns (bool) { 95 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 96 | 97 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 98 | 99 | balanceOf[from] -= amount; 100 | 101 | // Cannot overflow because the sum of all user 102 | // balances can't exceed the max uint256 value. 103 | unchecked { 104 | balanceOf[to] += amount; 105 | } 106 | 107 | emit Transfer(from, to, amount); 108 | 109 | return true; 110 | } 111 | 112 | /*////////////////////////////////////////////////////////////// 113 | EIP-2612 LOGIC 114 | //////////////////////////////////////////////////////////////*/ 115 | 116 | function permit( 117 | address owner, 118 | address spender, 119 | uint256 value, 120 | uint256 deadline, 121 | uint8 v, 122 | bytes32 r, 123 | bytes32 s 124 | ) public virtual { 125 | require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); 126 | 127 | // Unchecked because the only math done is incrementing 128 | // the owner's nonce which cannot realistically overflow. 129 | unchecked { 130 | address recoveredAddress = ecrecover( 131 | keccak256( 132 | abi.encodePacked( 133 | "\x19\x01", 134 | DOMAIN_SEPARATOR(), 135 | keccak256( 136 | abi.encode( 137 | keccak256( 138 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 139 | ), 140 | owner, 141 | spender, 142 | value, 143 | nonces[owner]++, 144 | deadline 145 | ) 146 | ) 147 | ) 148 | ), 149 | v, 150 | r, 151 | s 152 | ); 153 | 154 | require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); 155 | 156 | allowance[recoveredAddress][spender] = value; 157 | } 158 | 159 | emit Approval(owner, spender, value); 160 | } 161 | 162 | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { 163 | return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); 164 | } 165 | 166 | function computeDomainSeparator() internal view virtual returns (bytes32) { 167 | return 168 | keccak256( 169 | abi.encode( 170 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 171 | keccak256(bytes(name)), 172 | keccak256("1"), 173 | block.chainid, 174 | address(this) 175 | ) 176 | ); 177 | } 178 | 179 | /*////////////////////////////////////////////////////////////// 180 | INTERNAL MINT/BURN LOGIC 181 | //////////////////////////////////////////////////////////////*/ 182 | 183 | function _mint(address to, uint256 amount) internal virtual { 184 | totalSupply += amount; 185 | 186 | // Cannot overflow because the sum of all user 187 | // balances can't exceed the max uint256 value. 188 | unchecked { 189 | balanceOf[to] += amount; 190 | } 191 | 192 | emit Transfer(address(0), to, amount); 193 | } 194 | 195 | function _burn(address from, uint256 amount) internal virtual { 196 | balanceOf[from] -= amount; 197 | 198 | // Cannot underflow because a user's balance 199 | // will never be larger than the total supply. 200 | unchecked { 201 | totalSupply -= amount; 202 | } 203 | 204 | emit Transfer(from, address(0), amount); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/tokens/ERC4626.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {ERC20} from "../tokens/ERC20.sol"; 5 | import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; 6 | import {FixedPointMathLib} from "../utils/FixedPointMathLib.sol"; 7 | 8 | /// @notice Minimal ERC4626 tokenized Vault implementation. 9 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC4626.sol) 10 | abstract contract ERC4626 is ERC20 { 11 | using SafeTransferLib for ERC20; 12 | using FixedPointMathLib for uint256; 13 | 14 | /*////////////////////////////////////////////////////////////// 15 | EVENTS 16 | //////////////////////////////////////////////////////////////*/ 17 | 18 | event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares); 19 | 20 | event Withdraw( 21 | address indexed caller, 22 | address indexed receiver, 23 | address indexed owner, 24 | uint256 assets, 25 | uint256 shares 26 | ); 27 | 28 | /*////////////////////////////////////////////////////////////// 29 | IMMUTABLES 30 | //////////////////////////////////////////////////////////////*/ 31 | 32 | ERC20 public immutable asset; 33 | 34 | constructor( 35 | ERC20 _asset, 36 | string memory _name, 37 | string memory _symbol 38 | ) ERC20(_name, _symbol, _asset.decimals()) { 39 | asset = _asset; 40 | } 41 | 42 | /*////////////////////////////////////////////////////////////// 43 | DEPOSIT/WITHDRAWAL LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { 47 | // Check for rounding error since we round down in previewDeposit. 48 | require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); 49 | 50 | // Need to transfer before minting or ERC777s could reenter. 51 | asset.safeTransferFrom(msg.sender, address(this), assets); 52 | 53 | _mint(receiver, shares); 54 | 55 | emit Deposit(msg.sender, receiver, assets, shares); 56 | 57 | afterDeposit(assets, shares); 58 | } 59 | 60 | function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) { 61 | assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up. 62 | 63 | // Need to transfer before minting or ERC777s could reenter. 64 | asset.safeTransferFrom(msg.sender, address(this), assets); 65 | 66 | _mint(receiver, shares); 67 | 68 | emit Deposit(msg.sender, receiver, assets, shares); 69 | 70 | afterDeposit(assets, shares); 71 | } 72 | 73 | function withdraw( 74 | uint256 assets, 75 | address receiver, 76 | address owner 77 | ) public virtual returns (uint256 shares) { 78 | shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up. 79 | 80 | if (msg.sender != owner) { 81 | uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. 82 | 83 | if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; 84 | } 85 | 86 | beforeWithdraw(assets, shares); 87 | 88 | _burn(owner, shares); 89 | 90 | emit Withdraw(msg.sender, receiver, owner, assets, shares); 91 | 92 | asset.safeTransfer(receiver, assets); 93 | } 94 | 95 | function redeem( 96 | uint256 shares, 97 | address receiver, 98 | address owner 99 | ) public virtual returns (uint256 assets) { 100 | if (msg.sender != owner) { 101 | uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. 102 | 103 | if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; 104 | } 105 | 106 | // Check for rounding error since we round down in previewRedeem. 107 | require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS"); 108 | 109 | beforeWithdraw(assets, shares); 110 | 111 | _burn(owner, shares); 112 | 113 | emit Withdraw(msg.sender, receiver, owner, assets, shares); 114 | 115 | asset.safeTransfer(receiver, assets); 116 | } 117 | 118 | /*////////////////////////////////////////////////////////////// 119 | ACCOUNTING LOGIC 120 | //////////////////////////////////////////////////////////////*/ 121 | 122 | function totalAssets() public view virtual returns (uint256); 123 | 124 | function convertToShares(uint256 assets) public view virtual returns (uint256) { 125 | uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. 126 | 127 | return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets()); 128 | } 129 | 130 | function convertToAssets(uint256 shares) public view virtual returns (uint256) { 131 | uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. 132 | 133 | return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply); 134 | } 135 | 136 | function previewDeposit(uint256 assets) public view virtual returns (uint256) { 137 | return convertToShares(assets); 138 | } 139 | 140 | function previewMint(uint256 shares) public view virtual returns (uint256) { 141 | uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. 142 | 143 | return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply); 144 | } 145 | 146 | function previewWithdraw(uint256 assets) public view virtual returns (uint256) { 147 | uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. 148 | 149 | return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets()); 150 | } 151 | 152 | function previewRedeem(uint256 shares) public view virtual returns (uint256) { 153 | return convertToAssets(shares); 154 | } 155 | 156 | /*////////////////////////////////////////////////////////////// 157 | DEPOSIT/WITHDRAWAL LIMIT LOGIC 158 | //////////////////////////////////////////////////////////////*/ 159 | 160 | function maxDeposit(address) public view virtual returns (uint256) { 161 | return type(uint256).max; 162 | } 163 | 164 | function maxMint(address) public view virtual returns (uint256) { 165 | return type(uint256).max; 166 | } 167 | 168 | function maxWithdraw(address owner) public view virtual returns (uint256) { 169 | return convertToAssets(balanceOf[owner]); 170 | } 171 | 172 | function maxRedeem(address owner) public view virtual returns (uint256) { 173 | return balanceOf[owner]; 174 | } 175 | 176 | /*////////////////////////////////////////////////////////////// 177 | INTERNAL HOOKS LOGIC 178 | //////////////////////////////////////////////////////////////*/ 179 | 180 | function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {} 181 | 182 | function afterDeposit(uint256 assets, uint256 shares) internal virtual {} 183 | } 184 | -------------------------------------------------------------------------------- /src/tokens/ERC6909.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Minimalist and gas efficient standard ERC6909 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC6909.sol) 6 | abstract contract ERC6909 { 7 | /*////////////////////////////////////////////////////////////// 8 | EVENTS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | event OperatorSet(address indexed owner, address indexed operator, bool approved); 12 | 13 | event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount); 14 | 15 | event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | ERC6909 STORAGE 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | mapping(address => mapping(address => bool)) public isOperator; 22 | 23 | mapping(address => mapping(uint256 => uint256)) public balanceOf; 24 | 25 | mapping(address => mapping(address => mapping(uint256 => uint256))) public allowance; 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | ERC6909 LOGIC 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | function transfer( 32 | address receiver, 33 | uint256 id, 34 | uint256 amount 35 | ) public virtual returns (bool) { 36 | balanceOf[msg.sender][id] -= amount; 37 | 38 | balanceOf[receiver][id] += amount; 39 | 40 | emit Transfer(msg.sender, msg.sender, receiver, id, amount); 41 | 42 | return true; 43 | } 44 | 45 | function transferFrom( 46 | address sender, 47 | address receiver, 48 | uint256 id, 49 | uint256 amount 50 | ) public virtual returns (bool) { 51 | if (msg.sender != sender && !isOperator[sender][msg.sender]) { 52 | uint256 allowed = allowance[sender][msg.sender][id]; 53 | if (allowed != type(uint256).max) allowance[sender][msg.sender][id] = allowed - amount; 54 | } 55 | 56 | balanceOf[sender][id] -= amount; 57 | 58 | balanceOf[receiver][id] += amount; 59 | 60 | emit Transfer(msg.sender, sender, receiver, id, amount); 61 | 62 | return true; 63 | } 64 | 65 | function approve( 66 | address spender, 67 | uint256 id, 68 | uint256 amount 69 | ) public virtual returns (bool) { 70 | allowance[msg.sender][spender][id] = amount; 71 | 72 | emit Approval(msg.sender, spender, id, amount); 73 | 74 | return true; 75 | } 76 | 77 | function setOperator(address operator, bool approved) public virtual returns (bool) { 78 | isOperator[msg.sender][operator] = approved; 79 | 80 | emit OperatorSet(msg.sender, operator, approved); 81 | 82 | return true; 83 | } 84 | 85 | /*////////////////////////////////////////////////////////////// 86 | ERC165 LOGIC 87 | //////////////////////////////////////////////////////////////*/ 88 | 89 | function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { 90 | return 91 | interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 92 | interfaceId == 0x0f632fb3; // ERC165 Interface ID for ERC6909 93 | } 94 | 95 | /*////////////////////////////////////////////////////////////// 96 | INTERNAL MINT/BURN LOGIC 97 | //////////////////////////////////////////////////////////////*/ 98 | 99 | function _mint( 100 | address receiver, 101 | uint256 id, 102 | uint256 amount 103 | ) internal virtual { 104 | balanceOf[receiver][id] += amount; 105 | 106 | emit Transfer(msg.sender, address(0), receiver, id, amount); 107 | } 108 | 109 | function _burn( 110 | address sender, 111 | uint256 id, 112 | uint256 amount 113 | ) internal virtual { 114 | balanceOf[sender][id] -= amount; 115 | 116 | emit Transfer(msg.sender, sender, address(0), id, amount); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/tokens/ERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Modern, minimalist, and gas efficient ERC-721 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) 6 | abstract contract ERC721 { 7 | /*////////////////////////////////////////////////////////////// 8 | EVENTS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | event Transfer(address indexed from, address indexed to, uint256 indexed id); 12 | 13 | event Approval(address indexed owner, address indexed spender, uint256 indexed id); 14 | 15 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | METADATA STORAGE/LOGIC 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | string public name; 22 | 23 | string public symbol; 24 | 25 | function tokenURI(uint256 id) public view virtual returns (string memory); 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | ERC721 BALANCE/OWNER STORAGE 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | mapping(uint256 => address) internal _ownerOf; 32 | 33 | mapping(address => uint256) internal _balanceOf; 34 | 35 | function ownerOf(uint256 id) public view virtual returns (address owner) { 36 | require((owner = _ownerOf[id]) != address(0), "NOT_MINTED"); 37 | } 38 | 39 | function balanceOf(address owner) public view virtual returns (uint256) { 40 | require(owner != address(0), "ZERO_ADDRESS"); 41 | 42 | return _balanceOf[owner]; 43 | } 44 | 45 | /*////////////////////////////////////////////////////////////// 46 | ERC721 APPROVAL STORAGE 47 | //////////////////////////////////////////////////////////////*/ 48 | 49 | mapping(uint256 => address) public getApproved; 50 | 51 | mapping(address => mapping(address => bool)) public isApprovedForAll; 52 | 53 | /*////////////////////////////////////////////////////////////// 54 | CONSTRUCTOR 55 | //////////////////////////////////////////////////////////////*/ 56 | 57 | constructor(string memory _name, string memory _symbol) { 58 | name = _name; 59 | symbol = _symbol; 60 | } 61 | 62 | /*////////////////////////////////////////////////////////////// 63 | ERC721 LOGIC 64 | //////////////////////////////////////////////////////////////*/ 65 | 66 | function approve(address spender, uint256 id) public virtual { 67 | address owner = _ownerOf[id]; 68 | 69 | require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"); 70 | 71 | getApproved[id] = spender; 72 | 73 | emit Approval(owner, spender, id); 74 | } 75 | 76 | function setApprovalForAll(address operator, bool approved) public virtual { 77 | isApprovedForAll[msg.sender][operator] = approved; 78 | 79 | emit ApprovalForAll(msg.sender, operator, approved); 80 | } 81 | 82 | function transferFrom( 83 | address from, 84 | address to, 85 | uint256 id 86 | ) public virtual { 87 | require(from == _ownerOf[id], "WRONG_FROM"); 88 | 89 | require(to != address(0), "INVALID_RECIPIENT"); 90 | 91 | require( 92 | msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id], 93 | "NOT_AUTHORIZED" 94 | ); 95 | 96 | // Underflow of the sender's balance is impossible because we check for 97 | // ownership above and the recipient's balance can't realistically overflow. 98 | unchecked { 99 | _balanceOf[from]--; 100 | 101 | _balanceOf[to]++; 102 | } 103 | 104 | _ownerOf[id] = to; 105 | 106 | delete getApproved[id]; 107 | 108 | emit Transfer(from, to, id); 109 | } 110 | 111 | function safeTransferFrom( 112 | address from, 113 | address to, 114 | uint256 id 115 | ) public virtual { 116 | transferFrom(from, to, id); 117 | 118 | require( 119 | to.code.length == 0 || 120 | ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") == 121 | ERC721TokenReceiver.onERC721Received.selector, 122 | "UNSAFE_RECIPIENT" 123 | ); 124 | } 125 | 126 | function safeTransferFrom( 127 | address from, 128 | address to, 129 | uint256 id, 130 | bytes calldata data 131 | ) public virtual { 132 | transferFrom(from, to, id); 133 | 134 | require( 135 | to.code.length == 0 || 136 | ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) == 137 | ERC721TokenReceiver.onERC721Received.selector, 138 | "UNSAFE_RECIPIENT" 139 | ); 140 | } 141 | 142 | /*////////////////////////////////////////////////////////////// 143 | ERC165 LOGIC 144 | //////////////////////////////////////////////////////////////*/ 145 | 146 | function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { 147 | return 148 | interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 149 | interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721 150 | interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata 151 | } 152 | 153 | /*////////////////////////////////////////////////////////////// 154 | INTERNAL MINT/BURN LOGIC 155 | //////////////////////////////////////////////////////////////*/ 156 | 157 | function _mint(address to, uint256 id) internal virtual { 158 | require(to != address(0), "INVALID_RECIPIENT"); 159 | 160 | require(_ownerOf[id] == address(0), "ALREADY_MINTED"); 161 | 162 | // Counter overflow is incredibly unrealistic. 163 | unchecked { 164 | _balanceOf[to]++; 165 | } 166 | 167 | _ownerOf[id] = to; 168 | 169 | emit Transfer(address(0), to, id); 170 | } 171 | 172 | function _burn(uint256 id) internal virtual { 173 | address owner = _ownerOf[id]; 174 | 175 | require(owner != address(0), "NOT_MINTED"); 176 | 177 | // Ownership check above ensures no underflow. 178 | unchecked { 179 | _balanceOf[owner]--; 180 | } 181 | 182 | delete _ownerOf[id]; 183 | 184 | delete getApproved[id]; 185 | 186 | emit Transfer(owner, address(0), id); 187 | } 188 | 189 | /*////////////////////////////////////////////////////////////// 190 | INTERNAL SAFE MINT LOGIC 191 | //////////////////////////////////////////////////////////////*/ 192 | 193 | function _safeMint(address to, uint256 id) internal virtual { 194 | _mint(to, id); 195 | 196 | require( 197 | to.code.length == 0 || 198 | ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") == 199 | ERC721TokenReceiver.onERC721Received.selector, 200 | "UNSAFE_RECIPIENT" 201 | ); 202 | } 203 | 204 | function _safeMint( 205 | address to, 206 | uint256 id, 207 | bytes memory data 208 | ) internal virtual { 209 | _mint(to, id); 210 | 211 | require( 212 | to.code.length == 0 || 213 | ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) == 214 | ERC721TokenReceiver.onERC721Received.selector, 215 | "UNSAFE_RECIPIENT" 216 | ); 217 | } 218 | } 219 | 220 | /// @notice A generic interface for a contract which properly accepts ERC721 tokens. 221 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) 222 | abstract contract ERC721TokenReceiver { 223 | function onERC721Received( 224 | address, 225 | address, 226 | uint256, 227 | bytes calldata 228 | ) external virtual returns (bytes4) { 229 | return ERC721TokenReceiver.onERC721Received.selector; 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/tokens/WETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {ERC20} from "./ERC20.sol"; 5 | 6 | import {SafeTransferLib} from "../utils/SafeTransferLib.sol"; 7 | 8 | /// @notice Minimalist and modern Wrapped Ether implementation. 9 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol) 10 | /// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol) 11 | contract WETH is ERC20("Wrapped Ether", "WETH", 18) { 12 | using SafeTransferLib for address; 13 | 14 | event Deposit(address indexed from, uint256 amount); 15 | 16 | event Withdrawal(address indexed to, uint256 amount); 17 | 18 | function deposit() public payable virtual { 19 | _mint(msg.sender, msg.value); 20 | 21 | emit Deposit(msg.sender, msg.value); 22 | } 23 | 24 | function withdraw(uint256 amount) public virtual { 25 | _burn(msg.sender, amount); 26 | 27 | emit Withdrawal(msg.sender, amount); 28 | 29 | msg.sender.safeTransferETH(amount); 30 | } 31 | 32 | receive() external payable virtual { 33 | deposit(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/Bytes32AddressLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Library for converting between addresses and bytes32 values. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Bytes32AddressLib.sol) 6 | library Bytes32AddressLib { 7 | function fromLast20Bytes(bytes32 bytesValue) internal pure returns (address) { 8 | return address(uint160(uint256(bytesValue))); 9 | } 10 | 11 | function fillLast12Bytes(address addressValue) internal pure returns (bytes32) { 12 | return bytes32(bytes20(addressValue)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/CREATE3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {Bytes32AddressLib} from "./Bytes32AddressLib.sol"; 5 | 6 | /// @notice Deploy to deterministic addresses without an initcode factor. 7 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/CREATE3.sol) 8 | /// @author Modified from 0xSequence (https://github.com/0xSequence/create3/blob/master/contracts/Create3.sol) 9 | library CREATE3 { 10 | using Bytes32AddressLib for bytes32; 11 | 12 | //--------------------------------------------------------------------------------// 13 | // Opcode | Opcode + Arguments | Description | Stack View // 14 | //--------------------------------------------------------------------------------// 15 | // 0x36 | 0x36 | CALLDATASIZE | size // 16 | // 0x3d | 0x3d | RETURNDATASIZE | 0 size // 17 | // 0x3d | 0x3d | RETURNDATASIZE | 0 0 size // 18 | // 0x37 | 0x37 | CALLDATACOPY | // 19 | // 0x36 | 0x36 | CALLDATASIZE | size // 20 | // 0x3d | 0x3d | RETURNDATASIZE | 0 size // 21 | // 0x34 | 0x34 | CALLVALUE | value 0 size // 22 | // 0xf0 | 0xf0 | CREATE | newContract // 23 | //--------------------------------------------------------------------------------// 24 | // Opcode | Opcode + Arguments | Description | Stack View // 25 | //--------------------------------------------------------------------------------// 26 | // 0x67 | 0x67XXXXXXXXXXXXXXXX | PUSH8 bytecode | bytecode // 27 | // 0x3d | 0x3d | RETURNDATASIZE | 0 bytecode // 28 | // 0x52 | 0x52 | MSTORE | // 29 | // 0x60 | 0x6008 | PUSH1 08 | 8 // 30 | // 0x60 | 0x6018 | PUSH1 18 | 24 8 // 31 | // 0xf3 | 0xf3 | RETURN | // 32 | //--------------------------------------------------------------------------------// 33 | bytes internal constant PROXY_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3"; 34 | 35 | bytes32 internal constant PROXY_BYTECODE_HASH = keccak256(PROXY_BYTECODE); 36 | 37 | function deploy( 38 | bytes32 salt, 39 | bytes memory creationCode, 40 | uint256 value 41 | ) internal returns (address deployed) { 42 | bytes memory proxyChildBytecode = PROXY_BYTECODE; 43 | 44 | address proxy; 45 | /// @solidity memory-safe-assembly 46 | assembly { 47 | // Deploy a new contract with our pre-made bytecode via CREATE2. 48 | // We start 32 bytes into the code to avoid copying the byte length. 49 | proxy := create2(0, add(proxyChildBytecode, 32), mload(proxyChildBytecode), salt) 50 | } 51 | require(proxy != address(0), "DEPLOYMENT_FAILED"); 52 | 53 | deployed = getDeployed(salt); 54 | (bool success, ) = proxy.call{value: value}(creationCode); 55 | require(success && deployed.code.length != 0, "INITIALIZATION_FAILED"); 56 | } 57 | 58 | function getDeployed(bytes32 salt) internal view returns (address) { 59 | return getDeployed(salt, address(this)); 60 | } 61 | 62 | function getDeployed(bytes32 salt, address creator) internal pure returns (address) { 63 | address proxy = keccak256( 64 | abi.encodePacked( 65 | // Prefix: 66 | bytes1(0xFF), 67 | // Creator: 68 | creator, 69 | // Salt: 70 | salt, 71 | // Bytecode hash: 72 | PROXY_BYTECODE_HASH 73 | ) 74 | ).fromLast20Bytes(); 75 | 76 | return 77 | keccak256( 78 | abi.encodePacked( 79 | // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01) 80 | // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex) 81 | hex"d6_94", 82 | proxy, 83 | hex"01" // Nonce of the proxy contract (1) 84 | ) 85 | ).fromLast20Bytes(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/utils/FixedPointMathLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Arithmetic library with operations for fixed-point numbers. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) 6 | /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) 7 | library FixedPointMathLib { 8 | /*////////////////////////////////////////////////////////////// 9 | SIMPLIFIED FIXED POINT OPERATIONS 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | uint256 internal constant MAX_UINT256 = 2**256 - 1; 13 | 14 | uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. 15 | 16 | function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { 17 | return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. 18 | } 19 | 20 | function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { 21 | return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. 22 | } 23 | 24 | function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { 25 | return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. 26 | } 27 | 28 | function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { 29 | return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. 30 | } 31 | 32 | /*////////////////////////////////////////////////////////////// 33 | LOW LEVEL FIXED POINT OPERATIONS 34 | //////////////////////////////////////////////////////////////*/ 35 | 36 | function mulDivDown( 37 | uint256 x, 38 | uint256 y, 39 | uint256 denominator 40 | ) internal pure returns (uint256 z) { 41 | /// @solidity memory-safe-assembly 42 | assembly { 43 | // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) 44 | if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { 45 | revert(0, 0) 46 | } 47 | 48 | // Divide x * y by the denominator. 49 | z := div(mul(x, y), denominator) 50 | } 51 | } 52 | 53 | function mulDivUp( 54 | uint256 x, 55 | uint256 y, 56 | uint256 denominator 57 | ) internal pure returns (uint256 z) { 58 | /// @solidity memory-safe-assembly 59 | assembly { 60 | // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) 61 | if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { 62 | revert(0, 0) 63 | } 64 | 65 | // If x * y modulo the denominator is strictly greater than 0, 66 | // 1 is added to round up the division of x * y by the denominator. 67 | z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator)) 68 | } 69 | } 70 | 71 | function rpow( 72 | uint256 x, 73 | uint256 n, 74 | uint256 scalar 75 | ) internal pure returns (uint256 z) { 76 | /// @solidity memory-safe-assembly 77 | assembly { 78 | switch x 79 | case 0 { 80 | switch n 81 | case 0 { 82 | // 0 ** 0 = 1 83 | z := scalar 84 | } 85 | default { 86 | // 0 ** n = 0 87 | z := 0 88 | } 89 | } 90 | default { 91 | switch mod(n, 2) 92 | case 0 { 93 | // If n is even, store scalar in z for now. 94 | z := scalar 95 | } 96 | default { 97 | // If n is odd, store x in z for now. 98 | z := x 99 | } 100 | 101 | // Shifting right by 1 is like dividing by 2. 102 | let half := shr(1, scalar) 103 | 104 | for { 105 | // Shift n right by 1 before looping to halve it. 106 | n := shr(1, n) 107 | } n { 108 | // Shift n right by 1 each iteration to halve it. 109 | n := shr(1, n) 110 | } { 111 | // Revert immediately if x ** 2 would overflow. 112 | // Equivalent to iszero(eq(div(xx, x), x)) here. 113 | if shr(128, x) { 114 | revert(0, 0) 115 | } 116 | 117 | // Store x squared. 118 | let xx := mul(x, x) 119 | 120 | // Round to the nearest number. 121 | let xxRound := add(xx, half) 122 | 123 | // Revert if xx + half overflowed. 124 | if lt(xxRound, xx) { 125 | revert(0, 0) 126 | } 127 | 128 | // Set x to scaled xxRound. 129 | x := div(xxRound, scalar) 130 | 131 | // If n is even: 132 | if mod(n, 2) { 133 | // Compute z * x. 134 | let zx := mul(z, x) 135 | 136 | // If z * x overflowed: 137 | if iszero(eq(div(zx, x), z)) { 138 | // Revert if x is non-zero. 139 | if iszero(iszero(x)) { 140 | revert(0, 0) 141 | } 142 | } 143 | 144 | // Round to the nearest number. 145 | let zxRound := add(zx, half) 146 | 147 | // Revert if zx + half overflowed. 148 | if lt(zxRound, zx) { 149 | revert(0, 0) 150 | } 151 | 152 | // Return properly scaled zxRound. 153 | z := div(zxRound, scalar) 154 | } 155 | } 156 | } 157 | } 158 | } 159 | 160 | /*////////////////////////////////////////////////////////////// 161 | GENERAL NUMBER UTILITIES 162 | //////////////////////////////////////////////////////////////*/ 163 | 164 | function sqrt(uint256 x) internal pure returns (uint256 z) { 165 | /// @solidity memory-safe-assembly 166 | assembly { 167 | let y := x // We start y at x, which will help us make our initial estimate. 168 | 169 | z := 181 // The "correct" value is 1, but this saves a multiplication later. 170 | 171 | // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad 172 | // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. 173 | 174 | // We check y >= 2^(k + 8) but shift right by k bits 175 | // each branch to ensure that if x >= 256, then y >= 256. 176 | if iszero(lt(y, 0x10000000000000000000000000000000000)) { 177 | y := shr(128, y) 178 | z := shl(64, z) 179 | } 180 | if iszero(lt(y, 0x1000000000000000000)) { 181 | y := shr(64, y) 182 | z := shl(32, z) 183 | } 184 | if iszero(lt(y, 0x10000000000)) { 185 | y := shr(32, y) 186 | z := shl(16, z) 187 | } 188 | if iszero(lt(y, 0x1000000)) { 189 | y := shr(16, y) 190 | z := shl(8, z) 191 | } 192 | 193 | // Goal was to get z*z*y within a small factor of x. More iterations could 194 | // get y in a tighter range. Currently, we will have y in [256, 256*2^16). 195 | // We ensured y >= 256 so that the relative difference between y and y+1 is small. 196 | // That's not possible if x < 256 but we can just verify those cases exhaustively. 197 | 198 | // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. 199 | // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. 200 | // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. 201 | 202 | // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range 203 | // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. 204 | 205 | // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate 206 | // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. 207 | 208 | // There is no overflow risk here since y < 2^136 after the first branch above. 209 | z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. 210 | 211 | // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. 212 | z := shr(1, add(z, div(x, z))) 213 | z := shr(1, add(z, div(x, z))) 214 | z := shr(1, add(z, div(x, z))) 215 | z := shr(1, add(z, div(x, z))) 216 | z := shr(1, add(z, div(x, z))) 217 | z := shr(1, add(z, div(x, z))) 218 | z := shr(1, add(z, div(x, z))) 219 | 220 | // If x+1 is a perfect square, the Babylonian method cycles between 221 | // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. 222 | // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division 223 | // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. 224 | // If you don't care whether the floor or ceil square root is returned, you can remove this statement. 225 | z := sub(z, lt(div(x, z), z)) 226 | } 227 | } 228 | 229 | function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) { 230 | /// @solidity memory-safe-assembly 231 | assembly { 232 | // Mod x by y. Note this will return 233 | // 0 instead of reverting if y is zero. 234 | z := mod(x, y) 235 | } 236 | } 237 | 238 | function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { 239 | /// @solidity memory-safe-assembly 240 | assembly { 241 | // Divide x by y. Note this will return 242 | // 0 instead of reverting if y is zero. 243 | r := div(x, y) 244 | } 245 | } 246 | 247 | function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) { 248 | /// @solidity memory-safe-assembly 249 | assembly { 250 | // Add 1 to x * y if x % y > 0. Note this will 251 | // return 0 instead of reverting if y is zero. 252 | z := add(gt(mod(x, y), 0), div(x, y)) 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/utils/LibString.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Efficient library for creating string representations of integers. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol) 6 | /// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol) 7 | library LibString { 8 | function toString(int256 value) internal pure returns (string memory str) { 9 | if (value >= 0) return toString(uint256(value)); 10 | 11 | unchecked { 12 | str = toString(uint256(-value)); 13 | 14 | /// @solidity memory-safe-assembly 15 | assembly { 16 | // Note: This is only safe because we over-allocate memory 17 | // and write the string from right to left in toString(uint256), 18 | // and thus can be sure that sub(str, 1) is an unused memory location. 19 | 20 | let length := mload(str) // Load the string length. 21 | // Put the - character at the start of the string contents. 22 | mstore(str, 45) // 45 is the ASCII code for the - character. 23 | str := sub(str, 1) // Move back the string pointer by a byte. 24 | mstore(str, add(length, 1)) // Update the string length. 25 | } 26 | } 27 | } 28 | 29 | function toString(uint256 value) internal pure returns (string memory str) { 30 | /// @solidity memory-safe-assembly 31 | assembly { 32 | // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes 33 | // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the 34 | // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes. 35 | let newFreeMemoryPointer := add(mload(0x40), 160) 36 | 37 | // Update the free memory pointer to avoid overriding our string. 38 | mstore(0x40, newFreeMemoryPointer) 39 | 40 | // Assign str to the end of the zone of newly allocated memory. 41 | str := sub(newFreeMemoryPointer, 32) 42 | 43 | // Clean the last word of memory it may not be overwritten. 44 | mstore(str, 0) 45 | 46 | // Cache the end of the memory to calculate the length later. 47 | let end := str 48 | 49 | // We write the string from rightmost digit to leftmost digit. 50 | // The following is essentially a do-while loop that also handles the zero case. 51 | // prettier-ignore 52 | for { let temp := value } 1 {} { 53 | // Move the pointer 1 byte to the left. 54 | str := sub(str, 1) 55 | 56 | // Write the character to the pointer. 57 | // The ASCII index of the '0' character is 48. 58 | mstore8(str, add(48, mod(temp, 10))) 59 | 60 | // Keep dividing temp until zero. 61 | temp := div(temp, 10) 62 | 63 | // prettier-ignore 64 | if iszero(temp) { break } 65 | } 66 | 67 | // Compute and cache the final total length of the string. 68 | let length := sub(end, str) 69 | 70 | // Move the pointer 32 bytes leftwards to make room for the length. 71 | str := sub(str, 32) 72 | 73 | // Store the string's length at the start of memory allocated for our string. 74 | mstore(str, length) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/utils/MerkleProofLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Gas optimized merkle proof verification library. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol) 6 | /// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/MerkleProofLib.sol) 7 | library MerkleProofLib { 8 | function verify( 9 | bytes32[] calldata proof, 10 | bytes32 root, 11 | bytes32 leaf 12 | ) internal pure returns (bool isValid) { 13 | /// @solidity memory-safe-assembly 14 | assembly { 15 | if proof.length { 16 | // Left shifting by 5 is like multiplying by 32. 17 | let end := add(proof.offset, shl(5, proof.length)) 18 | 19 | // Initialize offset to the offset of the proof in calldata. 20 | let offset := proof.offset 21 | 22 | // Iterate over proof elements to compute root hash. 23 | // prettier-ignore 24 | for {} 1 {} { 25 | // Slot where the leaf should be put in scratch space. If 26 | // leaf > calldataload(offset): slot 32, otherwise: slot 0. 27 | let leafSlot := shl(5, gt(leaf, calldataload(offset))) 28 | 29 | // Store elements to hash contiguously in scratch space. 30 | // The xor puts calldataload(offset) in whichever slot leaf 31 | // is not occupying, so 0 if leafSlot is 32, and 32 otherwise. 32 | mstore(leafSlot, leaf) 33 | mstore(xor(leafSlot, 32), calldataload(offset)) 34 | 35 | // Reuse leaf to store the hash to reduce stack operations. 36 | leaf := keccak256(0, 64) // Hash both slots of scratch space. 37 | 38 | offset := add(offset, 32) // Shift 1 word per cycle. 39 | 40 | // prettier-ignore 41 | if iszero(lt(offset, end)) { break } 42 | } 43 | } 44 | 45 | isValid := eq(leaf, root) // The proof is valid if the roots match. 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/ReentrancyGuard.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Gas optimized reentrancy protection for smart contracts. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol) 6 | /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) 7 | abstract contract ReentrancyGuard { 8 | uint256 private locked = 1; 9 | 10 | modifier nonReentrant() virtual { 11 | require(locked == 1, "REENTRANCY"); 12 | 13 | locked = 2; 14 | 15 | _; 16 | 17 | locked = 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/SSTORE2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Read and write to persistent storage at a fraction of the cost. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol) 6 | /// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol) 7 | library SSTORE2 { 8 | uint256 internal constant DATA_OFFSET = 1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called. 9 | 10 | /*////////////////////////////////////////////////////////////// 11 | WRITE LOGIC 12 | //////////////////////////////////////////////////////////////*/ 13 | 14 | function write(bytes memory data) internal returns (address pointer) { 15 | // Prefix the bytecode with a STOP opcode to ensure it cannot be called. 16 | bytes memory runtimeCode = abi.encodePacked(hex"00", data); 17 | 18 | bytes memory creationCode = abi.encodePacked( 19 | //---------------------------------------------------------------------------------------------------------------// 20 | // Opcode | Opcode + Arguments | Description | Stack View // 21 | //---------------------------------------------------------------------------------------------------------------// 22 | // 0x60 | 0x600B | PUSH1 11 | codeOffset // 23 | // 0x59 | 0x59 | MSIZE | 0 codeOffset // 24 | // 0x81 | 0x81 | DUP2 | codeOffset 0 codeOffset // 25 | // 0x38 | 0x38 | CODESIZE | codeSize codeOffset 0 codeOffset // 26 | // 0x03 | 0x03 | SUB | (codeSize - codeOffset) 0 codeOffset // 27 | // 0x80 | 0x80 | DUP | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset // 28 | // 0x92 | 0x92 | SWAP3 | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // 29 | // 0x59 | 0x59 | MSIZE | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // 30 | // 0x39 | 0x39 | CODECOPY | 0 (codeSize - codeOffset) // 31 | // 0xf3 | 0xf3 | RETURN | // 32 | //---------------------------------------------------------------------------------------------------------------// 33 | hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes. 34 | runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit. 35 | ); 36 | 37 | /// @solidity memory-safe-assembly 38 | assembly { 39 | // Deploy a new contract with the generated creation code. 40 | // We start 32 bytes into the code to avoid copying the byte length. 41 | pointer := create(0, add(creationCode, 32), mload(creationCode)) 42 | } 43 | 44 | require(pointer != address(0), "DEPLOYMENT_FAILED"); 45 | } 46 | 47 | /*////////////////////////////////////////////////////////////// 48 | READ LOGIC 49 | //////////////////////////////////////////////////////////////*/ 50 | 51 | function read(address pointer) internal view returns (bytes memory) { 52 | return readBytecode(pointer, DATA_OFFSET, pointer.code.length - DATA_OFFSET); 53 | } 54 | 55 | function read(address pointer, uint256 start) internal view returns (bytes memory) { 56 | start += DATA_OFFSET; 57 | 58 | return readBytecode(pointer, start, pointer.code.length - start); 59 | } 60 | 61 | function read( 62 | address pointer, 63 | uint256 start, 64 | uint256 end 65 | ) internal view returns (bytes memory) { 66 | start += DATA_OFFSET; 67 | end += DATA_OFFSET; 68 | 69 | require(pointer.code.length >= end, "OUT_OF_BOUNDS"); 70 | 71 | return readBytecode(pointer, start, end - start); 72 | } 73 | 74 | /*////////////////////////////////////////////////////////////// 75 | INTERNAL HELPER LOGIC 76 | //////////////////////////////////////////////////////////////*/ 77 | 78 | function readBytecode( 79 | address pointer, 80 | uint256 start, 81 | uint256 size 82 | ) private view returns (bytes memory data) { 83 | /// @solidity memory-safe-assembly 84 | assembly { 85 | // Get a pointer to some free memory. 86 | data := mload(0x40) 87 | 88 | // Update the free memory pointer to prevent overriding our data. 89 | // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)). 90 | // Adding 31 to size and running the result through the logic above ensures 91 | // the memory pointer remains word-aligned, following the Solidity convention. 92 | mstore(0x40, add(data, and(add(add(size, 32), 31), not(31)))) 93 | 94 | // Store the size of the data in the first 32 byte chunk of free memory. 95 | mstore(data, size) 96 | 97 | // Copy the code into memory right after the 32 bytes we used to store the size. 98 | extcodecopy(pointer, add(data, 32), start, size) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/utils/SafeCastLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Safe unsigned integer casting library that reverts on overflow. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeCastLib.sol) 6 | /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol) 7 | library SafeCastLib { 8 | function safeCastTo248(uint256 x) internal pure returns (uint248 y) { 9 | require(x < 1 << 248); 10 | 11 | y = uint248(x); 12 | } 13 | 14 | function safeCastTo240(uint256 x) internal pure returns (uint240 y) { 15 | require(x < 1 << 240); 16 | 17 | y = uint240(x); 18 | } 19 | 20 | function safeCastTo232(uint256 x) internal pure returns (uint232 y) { 21 | require(x < 1 << 232); 22 | 23 | y = uint232(x); 24 | } 25 | 26 | function safeCastTo224(uint256 x) internal pure returns (uint224 y) { 27 | require(x < 1 << 224); 28 | 29 | y = uint224(x); 30 | } 31 | 32 | function safeCastTo216(uint256 x) internal pure returns (uint216 y) { 33 | require(x < 1 << 216); 34 | 35 | y = uint216(x); 36 | } 37 | 38 | function safeCastTo208(uint256 x) internal pure returns (uint208 y) { 39 | require(x < 1 << 208); 40 | 41 | y = uint208(x); 42 | } 43 | 44 | function safeCastTo200(uint256 x) internal pure returns (uint200 y) { 45 | require(x < 1 << 200); 46 | 47 | y = uint200(x); 48 | } 49 | 50 | function safeCastTo192(uint256 x) internal pure returns (uint192 y) { 51 | require(x < 1 << 192); 52 | 53 | y = uint192(x); 54 | } 55 | 56 | function safeCastTo184(uint256 x) internal pure returns (uint184 y) { 57 | require(x < 1 << 184); 58 | 59 | y = uint184(x); 60 | } 61 | 62 | function safeCastTo176(uint256 x) internal pure returns (uint176 y) { 63 | require(x < 1 << 176); 64 | 65 | y = uint176(x); 66 | } 67 | 68 | function safeCastTo168(uint256 x) internal pure returns (uint168 y) { 69 | require(x < 1 << 168); 70 | 71 | y = uint168(x); 72 | } 73 | 74 | function safeCastTo160(uint256 x) internal pure returns (uint160 y) { 75 | require(x < 1 << 160); 76 | 77 | y = uint160(x); 78 | } 79 | 80 | function safeCastTo152(uint256 x) internal pure returns (uint152 y) { 81 | require(x < 1 << 152); 82 | 83 | y = uint152(x); 84 | } 85 | 86 | function safeCastTo144(uint256 x) internal pure returns (uint144 y) { 87 | require(x < 1 << 144); 88 | 89 | y = uint144(x); 90 | } 91 | 92 | function safeCastTo136(uint256 x) internal pure returns (uint136 y) { 93 | require(x < 1 << 136); 94 | 95 | y = uint136(x); 96 | } 97 | 98 | function safeCastTo128(uint256 x) internal pure returns (uint128 y) { 99 | require(x < 1 << 128); 100 | 101 | y = uint128(x); 102 | } 103 | 104 | function safeCastTo120(uint256 x) internal pure returns (uint120 y) { 105 | require(x < 1 << 120); 106 | 107 | y = uint120(x); 108 | } 109 | 110 | function safeCastTo112(uint256 x) internal pure returns (uint112 y) { 111 | require(x < 1 << 112); 112 | 113 | y = uint112(x); 114 | } 115 | 116 | function safeCastTo104(uint256 x) internal pure returns (uint104 y) { 117 | require(x < 1 << 104); 118 | 119 | y = uint104(x); 120 | } 121 | 122 | function safeCastTo96(uint256 x) internal pure returns (uint96 y) { 123 | require(x < 1 << 96); 124 | 125 | y = uint96(x); 126 | } 127 | 128 | function safeCastTo88(uint256 x) internal pure returns (uint88 y) { 129 | require(x < 1 << 88); 130 | 131 | y = uint88(x); 132 | } 133 | 134 | function safeCastTo80(uint256 x) internal pure returns (uint80 y) { 135 | require(x < 1 << 80); 136 | 137 | y = uint80(x); 138 | } 139 | 140 | function safeCastTo72(uint256 x) internal pure returns (uint72 y) { 141 | require(x < 1 << 72); 142 | 143 | y = uint72(x); 144 | } 145 | 146 | function safeCastTo64(uint256 x) internal pure returns (uint64 y) { 147 | require(x < 1 << 64); 148 | 149 | y = uint64(x); 150 | } 151 | 152 | function safeCastTo56(uint256 x) internal pure returns (uint56 y) { 153 | require(x < 1 << 56); 154 | 155 | y = uint56(x); 156 | } 157 | 158 | function safeCastTo48(uint256 x) internal pure returns (uint48 y) { 159 | require(x < 1 << 48); 160 | 161 | y = uint48(x); 162 | } 163 | 164 | function safeCastTo40(uint256 x) internal pure returns (uint40 y) { 165 | require(x < 1 << 40); 166 | 167 | y = uint40(x); 168 | } 169 | 170 | function safeCastTo32(uint256 x) internal pure returns (uint32 y) { 171 | require(x < 1 << 32); 172 | 173 | y = uint32(x); 174 | } 175 | 176 | function safeCastTo24(uint256 x) internal pure returns (uint24 y) { 177 | require(x < 1 << 24); 178 | 179 | y = uint24(x); 180 | } 181 | 182 | function safeCastTo16(uint256 x) internal pure returns (uint16 y) { 183 | require(x < 1 << 16); 184 | 185 | y = uint16(x); 186 | } 187 | 188 | function safeCastTo8(uint256 x) internal pure returns (uint8 y) { 189 | require(x < 1 << 8); 190 | 191 | y = uint8(x); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/utils/SafeTransferLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | import {ERC20} from "../tokens/ERC20.sol"; 5 | 6 | /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. 7 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) 8 | /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. 9 | library SafeTransferLib { 10 | /*////////////////////////////////////////////////////////////// 11 | ETH OPERATIONS 12 | //////////////////////////////////////////////////////////////*/ 13 | 14 | function safeTransferETH(address to, uint256 amount) internal { 15 | bool success; 16 | 17 | /// @solidity memory-safe-assembly 18 | assembly { 19 | // Transfer the ETH and store if it succeeded or not. 20 | success := call(gas(), to, amount, 0, 0, 0, 0) 21 | } 22 | 23 | require(success, "ETH_TRANSFER_FAILED"); 24 | } 25 | 26 | /*////////////////////////////////////////////////////////////// 27 | ERC20 OPERATIONS 28 | //////////////////////////////////////////////////////////////*/ 29 | 30 | function safeTransferFrom( 31 | ERC20 token, 32 | address from, 33 | address to, 34 | uint256 amount 35 | ) internal { 36 | bool success; 37 | 38 | /// @solidity memory-safe-assembly 39 | assembly { 40 | // Get a pointer to some free memory. 41 | let freeMemoryPointer := mload(0x40) 42 | 43 | // Write the abi-encoded calldata into memory, beginning with the function selector. 44 | mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) 45 | mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument. 46 | mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. 47 | mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. 48 | 49 | // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. 50 | // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. 51 | success := call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) 52 | 53 | // Set success to whether the call reverted, if not we check it either 54 | // returned exactly 1 (can't just be non-zero data), or had no return data and token has code. 55 | if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) { 56 | success := iszero(or(iszero(extcodesize(token)), returndatasize())) 57 | } 58 | } 59 | 60 | require(success, "TRANSFER_FROM_FAILED"); 61 | } 62 | 63 | function safeTransfer( 64 | ERC20 token, 65 | address to, 66 | uint256 amount 67 | ) internal { 68 | bool success; 69 | 70 | /// @solidity memory-safe-assembly 71 | assembly { 72 | // Get a pointer to some free memory. 73 | let freeMemoryPointer := mload(0x40) 74 | 75 | // Write the abi-encoded calldata into memory, beginning with the function selector. 76 | mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) 77 | mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. 78 | mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. 79 | 80 | // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. 81 | // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. 82 | success := call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) 83 | 84 | // Set success to whether the call reverted, if not we check it either 85 | // returned exactly 1 (can't just be non-zero data), or had no return data and token has code. 86 | if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) { 87 | success := iszero(or(iszero(extcodesize(token)), returndatasize())) 88 | } 89 | } 90 | 91 | require(success, "TRANSFER_FAILED"); 92 | } 93 | 94 | function safeApprove( 95 | ERC20 token, 96 | address to, 97 | uint256 amount 98 | ) internal { 99 | bool success; 100 | 101 | /// @solidity memory-safe-assembly 102 | assembly { 103 | // Get a pointer to some free memory. 104 | let freeMemoryPointer := mload(0x40) 105 | 106 | // Write the abi-encoded calldata into memory, beginning with the function selector. 107 | mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) 108 | mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. 109 | mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. 110 | 111 | // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. 112 | // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. 113 | success := call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) 114 | 115 | // Set success to whether the call reverted, if not we check it either 116 | // returned exactly 1 (can't just be non-zero data), or had no return data and token has code. 117 | if and(iszero(and(eq(mload(0), 1), gt(returndatasize(), 31))), success) { 118 | success := iszero(or(iszero(extcodesize(token)), returndatasize())) 119 | } 120 | } 121 | 122 | require(success, "APPROVE_FAILED"); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/utils/SignedWadMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Signed 18 decimal fixed point (wad) arithmetic library. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SignedWadMath.sol) 6 | /// @author Modified from Remco Bloemen (https://xn--2-umb.com/22/exp-ln/index.html) 7 | 8 | /// @dev Will not revert on overflow, only use where overflow is not possible. 9 | function toWadUnsafe(uint256 x) pure returns (int256 r) { 10 | /// @solidity memory-safe-assembly 11 | assembly { 12 | // Multiply x by 1e18. 13 | r := mul(x, 1000000000000000000) 14 | } 15 | } 16 | 17 | /// @dev Takes an integer amount of seconds and converts it to a wad amount of days. 18 | /// @dev Will not revert on overflow, only use where overflow is not possible. 19 | /// @dev Not meant for negative second amounts, it assumes x is positive. 20 | function toDaysWadUnsafe(uint256 x) pure returns (int256 r) { 21 | /// @solidity memory-safe-assembly 22 | assembly { 23 | // Multiply x by 1e18 and then divide it by 86400. 24 | r := div(mul(x, 1000000000000000000), 86400) 25 | } 26 | } 27 | 28 | /// @dev Takes a wad amount of days and converts it to an integer amount of seconds. 29 | /// @dev Will not revert on overflow, only use where overflow is not possible. 30 | /// @dev Not meant for negative day amounts, it assumes x is positive. 31 | function fromDaysWadUnsafe(int256 x) pure returns (uint256 r) { 32 | /// @solidity memory-safe-assembly 33 | assembly { 34 | // Multiply x by 86400 and then divide it by 1e18. 35 | r := div(mul(x, 86400), 1000000000000000000) 36 | } 37 | } 38 | 39 | /// @dev Will not revert on overflow, only use where overflow is not possible. 40 | function unsafeWadMul(int256 x, int256 y) pure returns (int256 r) { 41 | /// @solidity memory-safe-assembly 42 | assembly { 43 | // Multiply x by y and divide by 1e18. 44 | r := sdiv(mul(x, y), 1000000000000000000) 45 | } 46 | } 47 | 48 | /// @dev Will return 0 instead of reverting if y is zero and will 49 | /// not revert on overflow, only use where overflow is not possible. 50 | function unsafeWadDiv(int256 x, int256 y) pure returns (int256 r) { 51 | /// @solidity memory-safe-assembly 52 | assembly { 53 | // Multiply x by 1e18 and divide it by y. 54 | r := sdiv(mul(x, 1000000000000000000), y) 55 | } 56 | } 57 | 58 | function wadMul(int256 x, int256 y) pure returns (int256 r) { 59 | /// @solidity memory-safe-assembly 60 | assembly { 61 | // Store x * y in r for now. 62 | r := mul(x, y) 63 | 64 | // Combined overflow check (`x == 0 || (x * y) / x == y`) and edge case check 65 | // where x == -1 and y == type(int256).min, for y == -1 and x == min int256, 66 | // the second overflow check will catch this. 67 | // See: https://secure-contracts.com/learn_evm/arithmetic-checks.html#arithmetic-checks-for-int256-multiplication 68 | // Combining into 1 expression saves gas as resulting bytecode will only have 1 `JUMPI` 69 | // rather than 2. 70 | if iszero( 71 | and( 72 | or(iszero(x), eq(sdiv(r, x), y)), 73 | or(lt(x, not(0)), sgt(y, 0x8000000000000000000000000000000000000000000000000000000000000000)) 74 | ) 75 | ) { 76 | revert(0, 0) 77 | } 78 | 79 | // Scale the result down by 1e18. 80 | r := sdiv(r, 1000000000000000000) 81 | } 82 | } 83 | 84 | function wadDiv(int256 x, int256 y) pure returns (int256 r) { 85 | /// @solidity memory-safe-assembly 86 | assembly { 87 | // Store x * 1e18 in r for now. 88 | r := mul(x, 1000000000000000000) 89 | 90 | // Equivalent to require(y != 0 && ((x * 1e18) / 1e18 == x)) 91 | if iszero(and(iszero(iszero(y)), eq(sdiv(r, 1000000000000000000), x))) { 92 | revert(0, 0) 93 | } 94 | 95 | // Divide r by y. 96 | r := sdiv(r, y) 97 | } 98 | } 99 | 100 | /// @dev Will not work with negative bases, only use when x is positive. 101 | function wadPow(int256 x, int256 y) pure returns (int256) { 102 | // Equivalent to x to the power of y because x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y) 103 | return wadExp((wadLn(x) * y) / 1e18); // Using ln(x) means x must be greater than 0. 104 | } 105 | 106 | function wadExp(int256 x) pure returns (int256 r) { 107 | unchecked { 108 | // When the result is < 0.5 we return zero. This happens when 109 | // x <= floor(log(0.5e18) * 1e18) ~ -42e18 110 | if (x <= -42139678854452767551) return 0; 111 | 112 | // When the result is > (2**255 - 1) / 1e18 we can not represent it as an 113 | // int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135. 114 | if (x >= 135305999368893231589) revert("EXP_OVERFLOW"); 115 | 116 | // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96 117 | // for more intermediate precision and a binary basis. This base conversion 118 | // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78. 119 | x = (x << 78) / 5**18; 120 | 121 | // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers 122 | // of two such that exp(x) = exp(x') * 2**k, where k is an integer. 123 | // Solving this gives k = round(x / log(2)) and x' = x - k * log(2). 124 | int256 k = ((x << 96) / 54916777467707473351141471128 + 2**95) >> 96; 125 | x = x - k * 54916777467707473351141471128; 126 | 127 | // k is in the range [-61, 195]. 128 | 129 | // Evaluate using a (6, 7)-term rational approximation. 130 | // p is made monic, we'll multiply by a scale factor later. 131 | int256 y = x + 1346386616545796478920950773328; 132 | y = ((y * x) >> 96) + 57155421227552351082224309758442; 133 | int256 p = y + x - 94201549194550492254356042504812; 134 | p = ((p * y) >> 96) + 28719021644029726153956944680412240; 135 | p = p * x + (4385272521454847904659076985693276 << 96); 136 | 137 | // We leave p in 2**192 basis so we don't need to scale it back up for the division. 138 | int256 q = x - 2855989394907223263936484059900; 139 | q = ((q * x) >> 96) + 50020603652535783019961831881945; 140 | q = ((q * x) >> 96) - 533845033583426703283633433725380; 141 | q = ((q * x) >> 96) + 3604857256930695427073651918091429; 142 | q = ((q * x) >> 96) - 14423608567350463180887372962807573; 143 | q = ((q * x) >> 96) + 26449188498355588339934803723976023; 144 | 145 | /// @solidity memory-safe-assembly 146 | assembly { 147 | // Div in assembly because solidity adds a zero check despite the unchecked. 148 | // The q polynomial won't have zeros in the domain as all its roots are complex. 149 | // No scaling is necessary because p is already 2**96 too large. 150 | r := sdiv(p, q) 151 | } 152 | 153 | // r should be in the range (0.09, 0.25) * 2**96. 154 | 155 | // We now need to multiply r by: 156 | // * the scale factor s = ~6.031367120. 157 | // * the 2**k factor from the range reduction. 158 | // * the 1e18 / 2**96 factor for base conversion. 159 | // We do this all at once, with an intermediate result in 2**213 160 | // basis, so the final right shift is always by a positive amount. 161 | r = int256((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)); 162 | } 163 | } 164 | 165 | function wadLn(int256 x) pure returns (int256 r) { 166 | unchecked { 167 | require(x > 0, "UNDEFINED"); 168 | 169 | // We want to convert x from 10**18 fixed point to 2**96 fixed point. 170 | // We do this by multiplying by 2**96 / 10**18. But since 171 | // ln(x * C) = ln(x) + ln(C), we can simply do nothing here 172 | // and add ln(2**96 / 10**18) at the end. 173 | 174 | /// @solidity memory-safe-assembly 175 | assembly { 176 | r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x)) 177 | r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x)))) 178 | r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) 179 | r := or(r, shl(4, lt(0xffff, shr(r, x)))) 180 | r := or(r, shl(3, lt(0xff, shr(r, x)))) 181 | r := or(r, shl(2, lt(0xf, shr(r, x)))) 182 | r := or(r, shl(1, lt(0x3, shr(r, x)))) 183 | r := or(r, lt(0x1, shr(r, x))) 184 | } 185 | 186 | // Reduce range of x to (1, 2) * 2**96 187 | // ln(2^k * x) = k * ln(2) + ln(x) 188 | int256 k = r - 96; 189 | x <<= uint256(159 - k); 190 | x = int256(uint256(x) >> 159); 191 | 192 | // Evaluate using a (8, 8)-term rational approximation. 193 | // p is made monic, we will multiply by a scale factor later. 194 | int256 p = x + 3273285459638523848632254066296; 195 | p = ((p * x) >> 96) + 24828157081833163892658089445524; 196 | p = ((p * x) >> 96) + 43456485725739037958740375743393; 197 | p = ((p * x) >> 96) - 11111509109440967052023855526967; 198 | p = ((p * x) >> 96) - 45023709667254063763336534515857; 199 | p = ((p * x) >> 96) - 14706773417378608786704636184526; 200 | p = p * x - (795164235651350426258249787498 << 96); 201 | 202 | // We leave p in 2**192 basis so we don't need to scale it back up for the division. 203 | // q is monic by convention. 204 | int256 q = x + 5573035233440673466300451813936; 205 | q = ((q * x) >> 96) + 71694874799317883764090561454958; 206 | q = ((q * x) >> 96) + 283447036172924575727196451306956; 207 | q = ((q * x) >> 96) + 401686690394027663651624208769553; 208 | q = ((q * x) >> 96) + 204048457590392012362485061816622; 209 | q = ((q * x) >> 96) + 31853899698501571402653359427138; 210 | q = ((q * x) >> 96) + 909429971244387300277376558375; 211 | /// @solidity memory-safe-assembly 212 | assembly { 213 | // Div in assembly because solidity adds a zero check despite the unchecked. 214 | // The q polynomial is known not to have zeros in the domain. 215 | // No scaling required because p is already 2**96 too large. 216 | r := sdiv(p, q) 217 | } 218 | 219 | // r is in the range (0, 0.125) * 2**96 220 | 221 | // Finalization, we need to: 222 | // * multiply by the scale factor s = 5.549… 223 | // * add ln(2**96 / 10**18) 224 | // * add k * ln(2) 225 | // * multiply by 10**18 / 2**96 = 5**18 >> 78 226 | 227 | // mul s * 5e18 * 2**96, base is now 5**18 * 2**192 228 | r *= 1677202110996718588342820967067443963516166; 229 | // add ln(2) * k * 5e18 * 2**192 230 | r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k; 231 | // add ln(2**96 / 10**18) * 5e18 * 2**192 232 | r += 600920179829731861736702779321621459595472258049074101567377883020018308; 233 | // base conversion: mul 2**18 / 2**192 234 | r >>= 174; 235 | } 236 | } 237 | 238 | /// @dev Will return 0 instead of reverting if y is zero. 239 | function unsafeDiv(int256 x, int256 y) pure returns (int256 r) { 240 | /// @solidity memory-safe-assembly 241 | assembly { 242 | // Divide x by y. 243 | r := sdiv(x, y) 244 | } 245 | } 246 | --------------------------------------------------------------------------------